Измените цвет текста одного ClickableSpan при нажатии, не затрагивая другие ClickableSpans в том же TextView
У меня есть TextView с несколькими ClickableSpans. Когда нажимается ClickableSpan, я хочу, чтобы он изменил цвет его текста.
Я попытался установить список состояний цвета в качестве атрибута textColorLink в TextView. Это не дает желаемого результата, потому что это приводит к тому, что все изменяются цвета, когда пользователь нажимает в любом месте на TextView.
Интересно, что использование textColorHighlight для изменения цвета фона работает так, как ожидалось: нажатие на диапазон изменяет только цвет фона этого диапазона и щелчок в любом месте в TextView ничего не делает.
Я также попытался установить ForegroundColorSpans с теми же границами, что и ClickableSpans, где я передаю тот же список состояний цвета, что и выше, как ресурс цвета. Это тоже не работает. Пролисты всегда сохраняют цвет состояния по умолчанию в списке состояний цвета и никогда не вступают в нажатое состояние.
Кто-нибудь знает, как это сделать?
Это список состояний цвета, который я использовал:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="@color/pressed_color"/>
<item android:color="@color/normal_color"/>
</selector>
Ответы
Ответ 1
Наконец-то я нашел решение, которое делает все, что я хотел. Он основан на этом ответе.
Это мой модифицированный метод LinkMovementMethod, который отмечает диапазон, который был нажат в начале события касания (MotionEvent.ACTION_DOWN), и снимает отметки с него, когда касание заканчивается или когда местоположение касания выходит из диапазона.
public class LinkTouchMovementMethod extends LinkMovementMethod {
private TouchableSpan mPressedSpan;
@Override
public boolean onTouchEvent(TextView textView, Spannable spannable, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mPressedSpan = getPressedSpan(textView, spannable, event);
if (mPressedSpan != null) {
mPressedSpan.setPressed(true);
Selection.setSelection(spannable, spannable.getSpanStart(mPressedSpan),
spannable.getSpanEnd(mPressedSpan));
}
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
TouchableSpan touchedSpan = getPressedSpan(textView, spannable, event);
if (mPressedSpan != null && touchedSpan != mPressedSpan) {
mPressedSpan.setPressed(false);
mPressedSpan = null;
Selection.removeSelection(spannable);
}
} else {
if (mPressedSpan != null) {
mPressedSpan.setPressed(false);
super.onTouchEvent(textView, spannable, event);
}
mPressedSpan = null;
Selection.removeSelection(spannable);
}
return true;
}
private TouchableSpan getPressedSpan(
TextView textView,
Spannable spannable,
MotionEvent event) {
int x = (int) event.getX() - textView.getTotalPaddingLeft() + textView.getScrollX();
int y = (int) event.getY() - textView.getTotalPaddingTop() + textView.getScrollY();
Layout layout = textView.getLayout();
int position = layout.getOffsetForHorizontal(layout.getLineForVertical(y), x);
TouchableSpan[] link = spannable.getSpans(position, position, TouchableSpan.class);
TouchableSpan touchedSpan = null;
if (link.length > 0 && positionWithinTag(position, spannable, link[0])) {
touchedSpan = link[0];
}
return touchedSpan;
}
private boolean positionWithinTag(int position, Spannable spannable, Object tag) {
return position >= spannable.getSpanStart(tag) && position <= spannable.getSpanEnd(tag);
}
}
This needs to be applied to the TextView like so:
yourTextView.setMovementMethod(new LinkTouchMovementMethod());
And this is the modified ClickableSpan that edits the draw state based on the pressed state set by the LinkTouchMovementMethod:
(it also removes the underline from the links)
public abstract class TouchableSpan extends ClickableSpan {
private boolean mIsPressed;
private int mPressedBackgroundColor;
private int mNormalTextColor;
private int mPressedTextColor;
public TouchableSpan(int normalTextColor, int pressedTextColor, int pressedBackgroundColor) {
mNormalTextColor = normalTextColor;
mPressedTextColor = pressedTextColor;
mPressedBackgroundColor = pressedBackgroundColor;
}
public void setPressed(boolean isSelected) {
mIsPressed = isSelected;
}
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(mIsPressed ? mPressedTextColor : mNormalTextColor);
ds.bgColor = mIsPressed ? mPressedBackgroundColor : 0xffeeeeee;
ds.setUnderlineText(false);
}
}
Ответ 2
Более простое решение, IMO:
final int colorForThisClickableSpan = Color.RED; //Set your own conditional logic here.
final ClickableSpan link = new ClickableSpan() {
@Override
public void onClick(final View view) {
//Do something here!
}
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setColor(colorForThisClickableSpan);
}
};
Ответ 3
ответ legr3c мне очень помог. И я хотел бы добавить несколько замечаний.
Замечание № 1.
TextView myTextView = (TextView) findViewById(R.id.my_textview);
myTextView.setMovementMethod(new LinkTouchMovementMethod());
myTextView.setHighlightColor(getResources().getColor(android.R.color.transparent));
SpannableString mySpannable = new SpannableString(text);
mySpannable.setSpan(new TouchableSpan(), 0, 7, 0);
mySpannable.setSpan(new TouchableSpan(), 15, 18, 0);
myTextView.setText(mySpannable, BufferType.SPANNABLE);
Я применил LinkTouchMovementMethod
к TextView
с двумя интервалами. При нажатии на них прожекторы были выделены синим цветом.
myTextView.setHighlightColor(getResources().getColor(android.R.color.transparent));
исправлена ошибка.
Замечание № 2.
Не забывайте получать цвета из ресурсов при передаче normalTextColor
, pressedTextColor
и pressedBackgroundColor
.
Необходимо передать разрешенный цвет вместо идентификатора ресурса здесь
Ответ 4
попробуйте этот пользовательский ClickableSpan:
class MyClickableSpan extends ClickableSpan {
private String action;
private int fg;
private int bg;
private boolean selected;
public MyClickableSpan(String action, int fg, int bg) {
this.action = action;
this.fg = fg;
this.bg = bg;
}
@Override
public void onClick(View widget) {
Log.d(TAG, "onClick " + action);
}
@Override
public void updateDrawState(TextPaint ds) {
ds.linkColor = selected? fg : 0xffeeeeee;
super.updateDrawState(ds);
}
}
и этот SpanWatcher:
class Watcher implements SpanWatcher {
private TextView tv;
private MyClickableSpan selectedSpan = null;
public Watcher(TextView tv) {
this.tv = tv;
}
private void changeColor(Spannable text, Object what, int start, int end) {
// Log.d(TAG, "changeFgColor " + what);
if (what == Selection.SELECTION_END) {
MyClickableSpan[] spans = text.getSpans(start, end, MyClickableSpan.class);
if (spans != null) {
tv.setHighlightColor(spans[0].bg);
if (selectedSpan != null) {
selectedSpan.selected = false;
}
selectedSpan = spans[0];
selectedSpan.selected = true;
}
}
}
@Override
public void onSpanAdded(Spannable text, Object what, int start, int end) {
changeColor(text, what, start, end);
}
@Override
public void onSpanChanged(Spannable text, Object what, int ostart, int oend, int nstart, int nend) {
changeColor(text, what, nstart, nend);
}
@Override
public void onSpanRemoved(Spannable text, Object what, int start, int end) {
}
}
проверить его в onCreate:
TextView tv = new TextView(this);
tv.setTextSize(40);
tv.setMovementMethod(LinkMovementMethod.getInstance());
SpannableStringBuilder b = new SpannableStringBuilder();
b.setSpan(new Watcher(tv), 0, 0, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
b.append("this is ");
int start = b.length();
MyClickableSpan link = new MyClickableSpan("link0 action", 0xffff0000, 0x88ff0000);
b.append("link 0");
b.setSpan(link, start, b.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
b.append("\nthis is ");
start = b.length();
b.append("link 1");
link = new MyClickableSpan("link1 action", 0xff00ff00, 0x8800ff00);
b.setSpan(link, start, b.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
b.append("\nthis is ");
start = b.length();
b.append("link 2");
link = new MyClickableSpan("link2 action", 0xff0000ff, 0x880000ff);
b.setSpan(link, start, b.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(b);
setContentView(tv);
Ответ 5
Это мое решение, если у вас много элементов click (нам нужен интерфейс):
Интерфейс:
public interface IClickSpannableListener{
void onClickSpannText(String text,int starts,int ends);
}
Класс, который управляет событием:
public class SpecialClickableSpan extends ClickableSpan{
private IClickSpannableListener listener;
private String text;
private int starts, ends;
public SpecialClickableSpan(String text,IClickSpannableListener who,int starts, int ends){
super();
this.text = text;
this.starts=starts;
this.ends=ends;
listener = who;
}
@Override
public void onClick(View widget) {
listener.onClickSpannText(text,starts,ends);
}
}
В основном классе:
class Main extends Activity implements IClickSpannableListener{
//Global
SpannableString _spannableString;
Object _backGroundColorSpan=new BackgroundColorSpan(Color.BLUE);
private void setTextViewSpannable(){
_spannableString= new SpannableString("You can click «here» or click «in this position»");
_spannableString.setSpan(new SpecialClickableSpan("here",this,15,18),15,19, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
_spannableString.setSpan(new SpecialClickableSpan("in this position",this,70,86),70,86, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
TextView tv = (TextView)findViewBy(R.id.textView1);
tv.setMovementMethod(LinkMovementMethod.getInstance());
tv.setText(spannableString);
}
@Override
public void onClickSpannText(String text, int inicio, int fin) {
System.out.println("click on "+ text);
_spannableString.removeSpan(_backGroundColorSpan);
_spannableString.setSpan(_backGroundColorSpan, inicio, fin, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
((TextView)findViewById(R.id.textView1)).setText(_spannableString);
}
}
Ответ 6
Все эти решения - слишком большая работа.
Просто установите android:textColorLink
в свой TextView
на какой-то селектор. Затем создайте clickableSpan без необходимости переопределять updateDrawState (...). Все сделано.
здесь быстрый пример:
В вашей strings.xml
есть объявленная строка, подобная этой:
<string name="mystring">This is my message%1$s these words are highlighted%2$s and awesome. </string>
то в вашей деятельности:
private void createMySpan(){
final String token = "#";
String myString = getString(R.string.mystring,token,token);
int start = myString.toString().indexOf(token);
//we do -1 since we are about to remove the tokens afterwards so it shifts
int finish = myString.toString().indexOf(token, start+1)-1;
myString = myString.replaceAll(token, "");
//create your spannable
final SpannableString spannable = new SpannableString(myString);
final ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(final View view) {
doSomethingOnClick();
}
};
spannable.setSpan(clickableSpan, start, finish, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mTextView.setMovementMethod(LinkMovementMethod.getInstance());
mTextView.setText(spannable);
}
и heres важные части. Сделайте такой селектор, который вызывает его myselector.xml
:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="@color/gold"/>
<item android:color="@color/pink"/>
</selector>
И последнее в TextView
в xml:
<TextView
android:id="@+id/mytextview"
android:background="@android:color/transparent"
android:text="@string/mystring"
android:textColorLink="@drawable/myselector" />
Теперь вы можете нажать нажатие на свой clickableSpan.
Ответ 7
Поместите код Java, как показано ниже:
package com.synamegames.orbs;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
public class CustomTouchListener implements View.OnTouchListener {
public boolean onTouch(View view, MotionEvent motionEvent) {
switch(motionEvent.getAction()){
case MotionEvent.ACTION_DOWN:
((TextView) view).setTextColor(0x4F4F4F);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
((TextView) view).setTextColor(0xCDCDCD);
break;
}
return false;
}
}
В приведенном выше коде укажите нужный цвет.
Измените стиль .xml как хотите.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="MenuFont">
<item name="android:textSize">20sp</item>
<item name="android:textColor">#CDCDCD</item>
<item name="android:textStyle">normal</item>
<item name="android:clickable">true</item>
<item name="android:layout_weight">1</item>
<item name="android:gravity">left|center</item>
<item name="android:paddingLeft">35dp</item>
<item name="android:layout_width">175dp</item>
<item name="android:layout_height">fill_parent</item>
</style>
Попробуйте и скажите, что вы хотите или что-то еще. обновите меня, чувак.