Highlight words in a textview when pressed - android

I have a TextView to display a paragraph of text and I want my application to speak individual words when they are pressed, using TTS. It would look better if words can be highlighted when pressed. I have implemented it using a ClickableSpan for each word. It works almost fine except that I do not see how to reset the highlighted state back to normal once playback is done. Each time I click a new word the previous word loses the highlight and the new one gets highlighted, but I do not know how to remove the highlight once TTS calls back:
My TextView:
<TextView
android:id="#+id/sentence"
...
android:textColorHighlight="#color/i_blue"
/>
To fill in the TextView, I use:
SpannableStringBuilder strBuilder = new SpannableStringBuilder();
Iterator<Word> iterator = e.getWordList().iterator();
int wordStart, wordEnd;
while (iterator.hasNext()) {
Word w = iterator.next();
wordStart = strBuilder.length() + w.getPrefix().length();
wordEnd = wordStart + w.getWord().length();
strBuilder.append(w.getPrefix() + w.getWord() + w.getSuffix());
final String currentWord = w.getWord();
ClickableSpan readWord = new ClickableSpan() {
private String clickedWord = currentWord;
public void onClick(View view) {
Message msg = m_HandlerReadWord.obtainMessage();
msg.obj = clickedWord;
m_HandlerReadWord.sendMessage(msg);
}
#Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(false);
}
};
strBuilder.setSpan(readWord, wordStart, wordEnd, 0);
}
m_SentenceView.setText(strBuilder);
m_SentenceView.setMovementMethod(LinkMovementMethod.getInstance());
And I also have this method which is called once TTS calls back when it is done playing the word:
public void resetHighlight() {
//What can I do there to reset any highlighted word?
}
Is there a way I can do it? Or is there a better approach than ClickableSpan?

I finally found a trick that works for me. When the text color in the TextView changes, all highlights are reset. So if I trigger a text color change in the callback of the TTS, then the highlight gets removed. The dirty part is that the triggered color change must be a different color. So I have to change the colors both when TTS calls back and in the onClick handler of the ClickableSpan. And I set these two colors to two almost identical colors.
My ClickableSpan:
final int AlmostBlack = m_Resources.getColor(R.color.i_black_almost);
ClickableSpan readWord = new ClickableSpan() {
private int almostBlack = AlmostBlack;
public void onClick(View view) {
TextView v = (TextView) view;
v.setTextColor(almostBlack);
...
And in the handler when TTS calls back:
m_SentenceView.setTextColor(m_Resources.getColor(R.color.i_black));
If you want to do something similar but without waiting for TTS or anything to call back, you can use a Color State List to trigger color changes when the view is pressed or released:
The Color State List, res/color/clickable_words.xml:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:color="#color/i_black_almost" android:state_pressed="true"/>
<item android:color="#color/i_black" />
</selector>
The TextView:
<TextView
android:id="#+id/sentence"
...
android:textColor="#color/clickable_words"
android:textColorLink="#color/clickable_words"
android:textColorHighlight="#color/i_blue" />

Related

How to save list of edittext content changes?

I want to restore previous text content on pressing undo button. With code below I can see what how did String changed, save new string to stack. For restore I just pop stack to get previous text. However that approach does not consider any changes in spannable. How to track what was changed be it either text or its styling or both?
Stack<String> save = new Stack<>();
Runnable saveRunnable = new Runnable() {
#Override public void run() {
if (tvValueDesc != null) {
String text = tvValueDesc.getText().toString();
if (save.isEmpty()) save.push(text);
if (!save.peek().equals(text)) save.push(text);
handler.postDelayed(this, 1000);
}
}
};
#OnClick(R.id.btn_undo) void restore() {
handler.removeCallbacksAndMessages(null);
if (!save.empty()) save.pop();
if (!save.empty()) {
tvValueDesc.setText(save.peek());
} else {
tvValueDesc.setText(null);
}
handler.postDelayed(saveRunnable, 1000);
}
For this you need to create Custom textPojo that includes all text propertices which you want to store ,like typeFace, text,Text Style, text size, text color , Text Alignment etc,
And When you store text while pressing button store all this information into TextPojo and maintain TextPojo stack rather maintaining single String .
To save text with spannable we should take charsequence from spannable, not the string

Why onClick() Method of ClickableSpan is called twice in android

onClick method is called twice, so when back from SecondActivity.class it will again reload it. I have a TextView named postTextView in which See More is clickable.
Where:
R.string.readMore = See More.
Here is the code which I've used.
String mTitleBody = Html.fromHtml(postBodyText).toString().substring(0, 150).trim();
mTitleBody = mTitleBody.concat("..." + mContext.getResources().getString(R.string.readMore)).replaceAll("<img.+?>|<IMG.+?>", "").replaceAll("\n", "<br/>");
int index1 = Html.fromHtml(mTitleBody).toString().trim().length() -
mContext.getResources().getString(R.string.readMore).length();
int index2 = Html.fromHtml(mTitleBody).toString().trim().length();
postTextView.setTextIsSelectable(true);
postTextView.setMovementMethod(LinkMovementMethod.getInstance());
postTextView.setText(Html.fromHtml(mTitleBody), TextView.BufferType.SPANNABLE);
Spannable mySpannable = (Spannable) postTextView.getText();
ClickableSpan myClickableSpan = new ClickableSpan() {
#Override
public void onClick(View view) {
Log.d("FirstClass", "onClick");
Intent intent = new Intent(mContext, SecondActivity.class);
(mContext).startActivity(intent);
((Activity) mContext).overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);
}
#Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(false);
ds.setColor(ContextCompat.getColor(mContext, R.color.body_text_3));
}
};
mySpannable.setSpan(myClickableSpan, index1, index2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
Can anyone help me here, Thanks in advance.
If you are using the autolink property in the TextView then you need to set the TextView to not be focusable after setting the movement method.
To do this add the following line after postTextView.setMovementMethod(LinkMovementMethod.getInstance());:
postTextView.setFocusable(false);
An explanation is in the second paragraph from the Android setMovementMethod documentation:
Sets the MovementMethod for handling arrow key movement for this
TextView. This can be null to disallow using the arrow keys to move
the cursor or scroll the view.
Be warned that if you want a TextView with a key listener or movement
method not to be focusable, or if you want a TextView without a key
listener or movement method to be focusable, you must call
View.setFocusable(boolean) again after calling this to get the
focusability back the way you want it.
Link to the documentation:
https://developer.android.com/reference/android/widget/TextView#setMovementMethod(android.text.method.MovementMethod)
I had this exact same issue, it was because I had the "autolink: true" property in the respective view, removing it helped.

Use spannable to highlight only part of already added text

I add a text like this:
tv.setText("This is text created with StringBuilder");
Then I track which word a user longclicks on, and want to highlight that word for some short period of time:
tv.setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
int offset = tv.getOffsetForPosition(coordinates.get(0), coordinates.get(1));
int[] offsets = getWordOffsets(text.toString(), offset);
int startOffset = offsets[0];
int endOffset = offsets[1];
// here I want to highlight the word starting from startOffset to endOffset
I've read that I should use SpannableString, however most examples show that I should created new SpannableString using the entire text, and then I can add styles to part of it. Is there any way to make part of the text in TextView spannable? Or should I create new SpannableString strings from TextView content and set spans every time the long click event is triggered?
you can use some countdown timer
//___ HIGHLIGHT HERE ______
new CountDownTimer(500,500){
#Override
public void onTick(long millisUntilFinished) {
}
#Override
public void onFinish() {
//___ UN HIGHLIGHT HERE ______
}
}.start();
and for the highlight you can use something like
textView.setText(Html.fromHtml("<font color='#FF0000'>text</font>"));

Change android SpannableString link text color when pressed

How to change text color of a link when pressed(Touch or pressed effect). Please see my code below
Code:
SpannableString spanStr = new SpannableString(tag);
spanStr.setSpan(new HashTagsClickableSpan(tag), 0, tag.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spanStr.setSpan(new ForegroundColorSpan(Color.RED),0,tag.length(),Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
class HashTagsClickableSpan extends ClickableSpan{
String clicked;
public HashTagsClickableSpan(String string) {
super();
clicked =string;
}
public void onClick(View tv) {
Toast.makeText(getActivity(), "Text = " + clicked,Toast.LENGTH_SHORT).show();
}
#Override
public void updateDrawState(TextPaint ds) {
ds.setUnderlineText(false);
}
}
ClickableSpan.onClick is only called after the ACTION_UP, so it's probably too late for the effect you want. In general, you can call View.invalidate() to issue a redraw (and another call to updateDrawState).
To get a nice pressed effect, you're probably better off registering a touch listener with View.setOnTouchListener , listening for the mouse events directly, modifying your custom span state if necessary, and posting an invalidate.

Select + copy text in a TextView?

Is there a way to allow the user to select / copy text in a TextView? I need the same functionality of EditText where you can long-press the control and get the popup options of select all / copy, but I need the control to look like a TextView.
Tried a few things like making an EditText use the editable="none" option or inputType="none", but those still retain the framed background of an EditText, which I don't want,
Thanks
------- Update ----------------------
This is 99% there, all I'd want is for the selection hilight to be visible (the orange stuff). Other than that it's good, could live with this though:
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:editable="false"
style="?android:attr/textViewStyle"
android:textColor="#color/white"
android:textAppearance="#android:style/TextAppearance.Medium"
android:cursorVisible="false"
android:background="#null" />
I guess it's being caused because of cursorVisible="false" but without that the cursor is present even without any selection being made.
android:textIsSelectable works (at least in ICS - I haven't yet checked in earlier versions)
<TextView
android:id="#+id/deviceIdTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:text="" />
Text View needs to be enabled, focusable, longClickable and textIsSelectable
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:id="#+id/pwTextView"
android:enabled="true"
android:textIsSelectable="true"
android:focusable="true"
android:longClickable="true" />
I think I have a better solution.
Just call
registerForContextMenu(yourTextView);
and your TextView will be registered for receiving context menu events.
Then override onCreateContextMenu in your Activity
#Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
//user has long pressed your TextView
menu.add(0, v.getId(), 0, "text that you want to show in the context menu - I use simply Copy");
//cast the received View to TextView so that you can get its text
TextView yourTextView = (TextView) v;
//place your TextView's text in clipboard
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
clipboard.setText(yourTextView.getText());
}
Hope this helps you and anyone else looking for a way to copy text from a TextView
textview1.setTextIsSelectable(true);
This will enable user to select and copy text on long clicking or as we do usually
Using Kotlin Programmatically (Manual Copy)
button.setTextIsSelectable(true)
Or, add a Kotlin property extension
var TextView.selectable
get() = isTextSelectable
set(value) = setTextIsSelectable(value)
Then call
textview.selectable = true
// or
if (textview.selectable) { ...
Using Kotlin Programmatically (Auto-Copy)
If you want to auto-copy when user long-presses you view, this is the base code required:
myView.setOnLongClickListener {
val clipboardManager = context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Copied String", myString)
clipboardManager.setPrimaryClip(clip)
true // Or false if not consumed
}
You may want to add a Toast to confirm it happened
Or, add a Kotlin extension function
myView.copyOnHold() // pass custom string to not use view contents
fun TextView.copyOnHold(customText: String? = null) {
setOnLongClickListener {
val clipboardManager = context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Copied String", customText ?: text)
clipboardManager.setPrimaryClip(clip)
true // Or false if not consumed
}
}
Using Xml (Manual Copy)
Add this to your <TextView>
android:textIsSelectable="true"
NOTE: All of these require android:enabled="true" and android:focusable="true", which are the default values for a TextView.
I'm trying to implement the same, and your question helped me to set my editext layout correctly. So Thanks! :)
Then I realized, that the highlight will actually be visible if the cursor is on.
But I just like you do not want to see a cursor before long clicking on the text, so I hide the cursor in the layout.xml file just like you, and added an eventlistener for long click and display the cursor only when a selection starts.
So add the listener in your Activity in the onCreate section:
public TextView htmltextview;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
...
htmltextview.setOnLongClickListener(new OnLongClickListener(){
public boolean onLongClick(View v) {
htmltextview.setCursorVisible(true);
return false;
}
});
}
And voilá, no cursor at the beginning, and if you long-click, the cursor appears with the selection boundaries.
I hope I could help.
Cheers,
fm
I was also trying to do something similar but still needed a custom approach with manipulation of highlighting of text in TextView. I triggered highlight and copying on LongClick action.
This is how I managed using SpannableString:
SpannableString highlightString = new SpannableString(textView.getText());
highlightString.setSpan(new BackgroundColorSpan(ContextCompat.getColor(getActivity(), R.color.gray))
, 0, textView.getText().length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
textView.setText(highlightString);
copyToClipboard(urlToShare);
and the copy function:
public void copyToClipboard(String copyText) {
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("url", copyText);
clipboard.setPrimaryClip(clip);
Toast toast = Toast.makeText(getActivity(), "Link is copied", Toast.LENGTH_SHORT);
toast.show();
}
I hope it's helpful for someone who ends up on this question :)
I have found it doesn't work the first time I double click, but it works there after ( at least in android 11). This told me it needed to get focus. So, in the onCreate event, I first made the text view selectable, then I requested the focus to shift to the text view. Now I'm not saying the text view can lose focus and the first attempted selection will work. Not guaranteed. What is guaranteed is once it has focus, it'll work every time until it loses focus again. Don't forget about androids animations. So allow at least a half second for the non overridable animation to play out when the keyboard is hiding.
// In onCreate
TextView1.setTextIsSelectable( true );
// Allow animations to play out.
timer = new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
TextView1.requestFocus();
}
});
}
};
_timer.schedule(timer, (int)(1000));
}
Thanks a lot gilbot for your explanation. I just want to add another thing.
Selected text background color follows your app theme's colorAccent
For example check the image below
Here AppTheme is my application theme.
<item name="colorAccent">#color/cold</item>
and the colorAccent value will be the selected text background color.
Just use this simple library:
GitHub: Selectable TextView

Categories

Resources