Reputation: 61
I'm trying to make a widget for in-line links in Flutter. For that I'm making a widget called EmbeddedUrlText which takes in a string. For hyperlinks I'm adding a tag around the text which needs to be highlighted as a link. I'm using a RichText widget with text spans inside. The links(TextSpans) have a TapGestureRecognizer which listens to clicks. I want to get the text inside the TextSpan or some way to get which text span was clicked.
The widget would be called like
EmbeddedUrlText("This is the <l>link</l>", ["https://www.google.com"])
So when the word "link" is clicked, google.com would open. The index of the link would open the appropriate index of links.
As of now, the links are highlighted and displayed and also open if a specific index if the links list is given, but TapGestureRecognizer does not return any information of WHICH text span was clicked. Please suggest any ways I can.
Here is the code for the widget:
class EmbeddedUrlText extends StatelessWidget {
List<TextSpan> widgets = [];
//Keeps a count of the number of links in the text.
var linksAdded = 0;
EmbeddedUrlText(String text, List<String> links,
{TextStyle style = const TextStyle(color: Colors.black)}) {
//Find text between <l></l> tags and add it as a separate text span
String widgetText = "";
for (var i = 0; i < text.length; i++) {
// Beyond this limit, a complete <l> tag cannot fit, hence do not check for tags.
if (i < text.length - 6) {
if (text[i] == "<" && text[i + 1] == "l" && text[i + 2] == ">") {
widgets.add(TextSpan(
text: widgetText, style: TextStyle(color: Colors.black)));
widgetText = "";
i += 3;
while (text[i] != "<" && i < text.length) {
widgetText = widgetText + text[i];
i++;
}
i += 3;
widgets.add(
TextSpan(
style: TextStyle(color: Colors.blue),
text: widgetText,
recognizer: TapGestureRecognizer()..onTap = () {
//I want to open URL of the clicked index of TextSpan
}
)
);
widgetText = "";
} else {
widgetText = widgetText + text[i];
}
} else {
widgetText = widgetText + text[i];
}
}
// At the end add the remaining text to the text
widgets.add(TextSpan(text: widgetText, style: TextStyle(color: Colors.black)));
}
@override
Widget build(BuildContext context) {
return RichText(text: TextSpan(children: widgets));
}
_launchURL(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
}
Upvotes: 1
Views: 1835
Reputation: 2257
You are making a new and unique TapGestureRecognizer
for each span. If you want the recognizer to distinguish between spans, I'm thinking you can keep track of how many spans were discovered before the current one. In code, it would look like this, by adding a currentIndex
:
class EmbeddedUrlText extends StatelessWidget {
List<TextSpan> widgets = [];
//Keeps a count of the number of links in the text.
var linksAdded = 0;
EmbeddedUrlText(String text, List<String> links,
{TextStyle style = const TextStyle(color: Colors.black)}) {
//Find text between <l></l> tags and add it as a separate text span
String widgetText = "";
int currentIndex = 0;
for (var i = 0; i < text.length; i++) {
// Beyond this limit, a complete <l> tag cannot fit, hence do not check for tags.
if (i < text.length - 6) {
if (text[i] == "<" && text[i + 1] == "l" && text[i + 2] == ">") {
widgets.add(TextSpan(
text: widgetText, style: TextStyle(color: Colors.black)));
widgetText = "";
i += 3;
while (text[i] != "<" && i < text.length) {
widgetText = widgetText + text[i];
i++;
}
i += 3;
widgets.add(
TextSpan(
style: TextStyle(color: Colors.blue),
text: widgetText,
recognizer: TapGestureRecognizer()..onTap = () {
//Also have logic for exceeding the array bounds
_launchURL(links[currentIndex]);
}
)
);
widgetText = "";
currentIndex = currentIndex + 1;
} else {
widgetText = widgetText + text[i];
}
} else {
widgetText = widgetText + text[i];
}
}
// At the end add the remaining text to the text
widgets.add(TextSpan(text: widgetText, style: TextStyle(color: Colors.black)));
}
@override
Widget build(BuildContext context) {
return RichText(text: TextSpan(children: widgets));
}
_launchURL(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
}
I might have understood the problem wrong, but I assume that at the time, you expected the TapGestureRecognizer
to give you some information about the span. Since this is declarative UI, you have the span at your disposal when declaring it has this gesture recognizer attached to it - hence you are free to retain the info you need and pass it down to the recognizer.
Upvotes: 0