Handling Multiple ClickableSpan in a TextView - android

I've been stuck at this problem for long. What I'm having is a simple string "This is a link and this is another link". I want to have both "link" words click-able, having different URLs to open in browser.
The simplest way I can do is to set Click-able Span on both "link"
words with different URLs, but the problem I'm facing is finding the
start and end positions of the span. The text is dynamic, and I have
to programmatically find the positions.
One approach would be to find the first occurrence of the word
'link', find the start and end positions and set the span, and then
the second occurrence. But that is not reliable. The text may contain
more than one kind of repeated words, like "This is a cat link and
this is another cat link". Here I have to link both "cat" and "link"
words with different URLs via Click-able Span. How do I go about it?

Try in this manner
String s="Cat link 1 Cat link 2 Cat link 3";
SpannableString ss = new SpannableString(s);
String first ="Cat link 1";
String second ="Cat link 2";
String third ="Cat link 3";
int firstIndex = s.toString().indexOf(first);
int secondIndex = s.toString().indexOf(second);
ClickableSpan firstwordClick = new ClickableSpan() {
#Override
public void onClick(View widget) {
///............
}
};
ClickableSpan secondwordClick = new ClickableSpan() {
#Override
public void onClick(View widget) {
///............
}
};
ss.setSpan(firstwordClick,firstIndex, firstIndex+first.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
ss.setSpan(secondwordClick,secondIndex, secondIndex+second.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setLinksClickable(true);
textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setText(ss,BufferType.SPANNABLE);

If you cannot programmatically find the difference in the links, then you cannot expect anything else to be able to do it. As the developer, you need a system.
You need to be able to identify the clickable spans - since the links are unique, the text that identifies them must also be unique. This would be a problem for your users, most likely.
You can get an ordered list of URLs and then if the links are indistinguishable, simply use them in the order you receive. Or, you need to change the rules of creating the links or the order in which they are displayed.

One simple way to do this would be to include an identifier before the link say /*/ then using this find the start and end position for link. Once you have that first replace the identifier with a "" and then click away.

I have string something such "you order with orderId {b1j2gh4b} has been claimed by bla bla sotre with phone number (1234124124)"
I am using these braces so as to find out the index of of orderID and Phone number
String notificationMessage = mArrListNotification.get(position).getMessage();
boolean isPhoneNumberAvailable = false, isOrderIdAvailable = false;
int phoneStartIndex = 0, phoneEndIndex = 0, orderIdStartIndex = 0, orderIdEndIndex = 0;
//getting index on the basis of braces added to text
if (notificationMessage.contains("(")) {
isPhoneNumberAvailable = true;
phoneStartIndex = notificationMessage.indexOf("(");
phoneEndIndex = notificationMessage.indexOf(")");
}
if (notificationMessage.contains("{")) {
orderIdStartIndex = notificationMessage.indexOf("{");
orderIdEndIndex = notificationMessage.indexOf("}");
}
// we got the index so remove braces
notificationMessage = notificationMessage.replace("(", " ");
notificationMessage = notificationMessage.replace(")", " ");
notificationMessage = notificationMessage.replace("{", " ");
notificationMessage = notificationMessage.replace("}", " ");
viewHolder.txtNotificationMessage.setText(notificationMessage, TextView.BufferType.SPANNABLE);
Spannable mySpannablePhoneNumber = (Spannable) viewHolder.txtNotificationMessage.getText();
Spannable mySpannableOrderID = (Spannable) viewHolder.txtNotificationMessage.getText();
ClickableSpan mySpanPhoneClick = new ClickableSpan() {
#Override
public void onClick(View widget) {
currentPosition = (Integer) widget.getTag();
String message = mArrListNotification.get(currentPosition).getMessage();
int startIndex = message.indexOf("(");
int endIndex = message.indexOf(")");
phoneNumber = message.substring(startIndex + 1, endIndex);
Log.i("Phone Number", phoneNumber clicked)
}
};
ClickableSpan mySpanOrderClick = new ClickableSpan() {
#Override
public void onClick(View widget) {
currentPosition = (Integer) widget.getTag();
String message = mArrListNotification.get(currentPosition).getMessage();
int startIndex = message.indexOf("{");
int endIndex = message.indexOf("}");
String orderID = message.substring(startIndex + 1, endIndex);
// Log.i("Order id", orderID clicked)
}
};
if (isPhoneNumberAvailable) {
mySpannablePhoneNumber.setSpan(mySpanPhoneClick, phoneStartIndex + 1, phoneEndIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (isOrderIdAvailable) {
mySpannableOrderID.setSpan(mySpanOrderClick, orderIdStartIndex + 1, orderIdEndIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}

Related

How to give specific colors to words using Spannable?

I have a TextView and contains the below text
The -[[community]]- is here to help you with -[[specific]]- coding, -[[algorithm]]-, or -[[language]]- problems.
I want anything inside -[[]]- take red color, How can I do that using Spannable?
And I don't want to show -[[ and ]]- in TextView
You can use SpannableStringBuilder and append parts of String colorizing them when necessary. For example,
static CharSequence colorize(
String input, String open, String close, #ColorInt int color
) {
SpannableStringBuilder builder = new SpannableStringBuilder();
int openLen = open.length(), closeLen = close.length();
int openAt, contentAt, closeAt, last = 0;
while ((openAt = input.indexOf(open, last)) >= 0 &&
(closeAt = input
.indexOf(close, contentAt = openAt + openLen)) >= 0) {
int start = builder.append(input, last, openAt).length();
int len = builder.append(input, contentAt, closeAt).length();
builder.setSpan(
new ForegroundColorSpan(color),
start, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
);
last = closeAt + closeLen;
}
return builder.append(input, last, input.length());
}
You can use the CodeView library to highlight many patterns with different colors, in your case for example the code will be like this
CodeView codeView = findViewById(R.id.codeview);
codeView.addSyntaxPattern(Pattern.compile("-\\[\\[[a-zA-Z]+]]-"), Color.GREEN);
codeView.setTextHighlighted(text);
And the result will be:
If the highlighted keywords are unique you can highlight them without using -[[]]- just create a pattern that can cover them
You can change the color, add or remove patterns in the runtime
CodeView Repository URL: https://github.com/amrdeveloper/codeview
The value in the variable who named open must be different from the value in the variable who named close, If the value was the same will cause a problem. You need to change variables values only to work well.
String open = "-[[";
String close = "]]-";
int color = Color.RED;
String s1 = "The -[[community]]- is here to help you with -[[specific]]- coding, -[[algorithm]]-, or -[[language]]- problems.";
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(s1);
while (spannableStringBuilder.toString().contains(open) && spannableStringBuilder.toString().contains(close)) {
spannableStringBuilder.setSpan(new ForegroundColorSpan(color), spannableStringBuilder.toString().indexOf(open) + open.length(), spannableStringBuilder.toString().indexOf(close) + close.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannableStringBuilder.replace(spannableStringBuilder.toString().indexOf(open), spannableStringBuilder.toString().indexOf(open) + open.length(), "").replace(spannableStringBuilder.toString().indexOf(close), spannableStringBuilder.toString().indexOf(close) + close.length(), "");
}
yourTextView.setText(spannableStringBuilder);

How to determine if a link or plain text was touched in a TextView

I have a TextView which could potentially contain a clickable link. I want to add a click listener to the TextView but still when a link is clicked, I want it to be handled normally by Linkify.
It took me a while to figure this out and I wanted to share the answer since it's been working well so, enjoy!
This code traverses the string by separating characters at the space character: " ".
It then checks each 'word' for a link.
TextView textView = new TextView(context) {
#Override
public boolean onTouchEvent(MotionEvent event) {
final String text = getText().toString();
final SpannableString spannableString = new SpannableString(text);
Linkify.addLinks(spannableString, Linkify.ALL);
final URLSpan[] spans = spannableString.getSpans(0, text.length(), URLSpan.class);
final int indexOfCharClicked = getOffsetForPosition(event.getX(), event.getY()) + 1; //Change 0-index to 1-index
final String [] words = text.split(" ");
int numCharsTraversed = 0;
//Find the word that was clicked and check if it's a link
for (String word : words) {
if (numCharsTraversed + word.length() < indexOfCharClicked) {
numCharsTraversed += word.length() + 1; // + 1 for the space
} else {
for (URLSpan span : spans) {
if (span.getURL().contains(word) || word.contains(span.getURL())) {
//If the clicked word is a link, calling super will invoke the appropriate action
return super.onTouchEvent(event);
}
}
break;
}
}
//If we're here, it means regular text was clicked, not a link
doSomeAction();
return true;
}
};

How to split up a SpannableStringBuilder while keeping its formating?

I am working on an android project that involves parsing some HTML (parsed by Jsoup) into a SpannableStringBuilder class.
However, I need this SpannableStringBuilder class to be divided up by each new line character into a List once it is done parsing, while keeping its formatting.
Such that a spanned text of
{"I am a spanned text,\n hear me roar"}
would turn into
{
"I am a spanned text,"
"hear me roar"
}
I am fairly new to developing on Android, and could not find anything in the documentation about spitting spans or even getting a listing of all formatting on a spanned class to build my own. So any help is much appreciated.
I managed to figure this out on my own, after looking into the method that pskink suggested.
My solution to this was
#Override
public List<Spanned> parse() {
List<Spanned> spans = new ArrayList<Spanned>();
Spannable unsegmented = (Spannable) Html.fromHtml(base.html(), null, new ReaderTagHandler());
//Set ColorSpan because it defaults to white text color
unsegmented.setSpan(new ForegroundColorSpan(Color.BLACK), 0, unsegmented.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
//get locations of '/n'
Stack<Integer> loc = getNewLineLocations(unsegmented);
loc.push(unsegmented.length());
//divides up a span by each new line character position in loc
while (!loc.isEmpty()) {
Integer end = loc.pop();
Integer start = loc.isEmpty() ? 0 : loc.peek();
spans.add(0,(Spanned) unsegmented.subSequence(start, end));
}
return spans;
}
private Stack<Integer> getNewLineLocations(Spanned unsegmented) {
Stack<Integer> loc = new Stack<>();
String string = unsegmented.toString();
int next = string.indexOf('\n');
while (next > 0) {
//avoid chains of newline characters
if (string.charAt(next - 1) != '\n') {
loc.push(next);
next = string.indexOf('\n', loc.peek() + 1);
} else {
next = string.indexOf('\n', next + 1);
}
if (next >= string.length()) next = -1;
}
return loc;
}

clickable span in a ListView

I'm trying to implement a comment like list where you can mention other users in the comments, and then be able to click on that to go to their profile. For example, when posting a comment on reddit you can mention another user with /u/username.
The one problem i'm running into is how to make it so I can click on the text to load that user's profile.
For the comment list I essentially have a custom list of textviews. From googling around I saw that it is possible to be able to click on some text in a textview using a clickable span. However, I haven't been able to get this to work in a list of textviews. Can someone help?
This is the relevant sections of my code:
Clickable span I declared as a member variable:
ClickableSpan clickableSpan = new ClickableSpan() {
#Override
public void onClick(View widget) {
Intent intent = new Intent(context, ProfileActivity.class);
// ParseUser.getQuery().whereEqualTo(Constants.kQollegeUserPreferredUsernameKey, sta)
intent.putExtra("User", ParseUser.getCurrentUser().getObjectId());
context.startActivity(intent);
}
};
Inside getView:
TextView answer_text_view = answerView.getAnswerTextView();
String text = answer.getAnswerText();
if(text.contains("#")) {
text += " ";
final SpannableStringBuilder sb = new SpannableStringBuilder(text);
List<Integer> start = new ArrayList<>();
int index = text.indexOf("#");
while (index >= 0) {
start.add(index);
index = text.indexOf("#", index + 1);
}
for(int i = 0; i<start.size(); i++){
sb.setSpan(fcs, start.get(i), text.indexOf(" ", start.get(i)), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
sb.setSpan(clickableSpan, start.get(i), text.indexOf(" ", start.get(i)), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
answer_text_view.setText(sb);
}
I have applied following to the textview where clickable span is present to make it work
textView.setMovementMethod(LinkMovementMethod.getInstance());

Android: Launch activity from clickable text

Is there any way I can launch an activity from a portion of a string.
eg
I have this in my strings.xml file:
<string name="clickable_string">This is a <u>clickable string</u></string>
I would like the text between the u tags to be underlined and launch an activity when clicked when inserted in to a TextView
Try this,
final Context context = ... // whereever your context is
CharSequence sequence = Html.fromSource(context.getString(R.string.clickable_string));
SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
UnderlineSpan[] underlines = strBuilder.getSpans(UnderlineSpan.class);
for(UnderlineSpan span : underlines) {
int start = strBuilder.getSpanStart(span);
int end = strBuilder.getSpanEnd(span);
int flags = strBuilder.getSpanFlags(span);
ClickableSpan myActivityLauncher = new ClickableSpan() {
public void onClick(View view) {
context.startActivity(getIntentForActivityToStart());
}
};
strBuilder.setSpan(myActivityLauncher, start, end, flags);
}
TextView textView = ...
textView.setText(strBuilder);
textView.setMovementMethod(LinkMovementMethod.getInstance());
Basically you have to attach a Span object to the range of characters you want to be clickable. Since you are using HTML anyways, you can use the underline spans placed by the Html.fromSource() as markers for your own spans.
Alternatively you could also define a Tag within the string that only you know of.
i.e. <activity>
And supply your own tag handler to the Html.fromSource() method. This way your TagHandler instance could do something like, surround the tagged text with a specific color, underline, bold and make it clickable. However I would only recommend the TagHandler approach if you find yourself writing this type of code a lot.
assign this string to one of your xml layout and then in your code get the id of TextView and then implement OnClickListener for this Textview,inside of it you can start your new activity you want.
Answered here Make parts of textview clickable (not url)
I just made a modification if you want to use it with a HTML Message do the following
In your Display function
public void displayText(String message) {
chapterTextView.setText(Html.fromHtml(message),TextView.BufferType.SPANNABLE);
chapterTextView.setMovementMethod(LinkMovementMethod.getInstance());
Spannable clickableMessage = (Spannable) chapterTextView.getText();
chapterTextView.setText(addClickablePart(clickableMessage), BufferType.SPANNABLE);
}
The Modified function of addClickablePart
private SpannableStringBuilder addClickablePart(Spannable charSequence) {
SpannableStringBuilder ssb = new SpannableStringBuilder(charSequence);
int idx1 = charSequence.toString().indexOf("(");
int idx2 = 0;
while (idx1 != -1) {
idx2 = charSequence.toString().indexOf(")", idx1) + 1;
final String clickString = charSequence.toString().substring(idx1, idx2);
ssb.setSpan(new ClickableSpan() {
#Override
public void onClick(View widget) {
Toast.makeText(getActivity(), clickString,
Toast.LENGTH_SHORT).show();
}
}, idx1, idx2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
idx1 = charSequence.toString().indexOf("(", idx2);
}
return ssb;
}
Hope this help someone.

Categories

Resources