Изменить цвет текста одного 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>
9 ответов
Я наконец нашел решение, которое делает все, что я хотел. Это основано на этом ответе.
Это мой модифицированный метод 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);
}
}
Это должно быть применено к TextView следующим образом:
yourTextView.setMovementMethod(new LinkTouchMovementMethod());
И это модифицированный ClickableSpan, который редактирует состояние рисования на основе нажатого состояния, установленного LinkTouchMovementMethod: (он также удаляет подчеркивание из ссылок)
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);
}
}
Гораздо более простое решение, ИМО:
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);
}
};
Все эти решения слишком много работы.
Просто установите 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);
}
и вот важные части.. объявить селектор, как это называется 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.
Ответ 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
,
Должен передать разрешенный цвет вместо идентификатора ресурса здесь
Попробуйте этот пользовательский 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);
Это мое решение, если у вас есть много элементов 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);
}
}
Ответ Стивена М выше работает, но подумал, что поделюсь этим:https://github.com/saket/Better-Link-Movement-Method
Это просто работает. Приятно не иметь кучу сложного кода.
Король с уважением относится к ответу Стивена , если кому-то нужно, вот его реализация в Котлине:
abstract class TouchableSpan(
private val mNormalTextColor: Int,
private val mPressedTextColor: Int
) : ClickableSpan() {
private var isPressed = false
fun setPressed(isSelected: Boolean) {
isPressed = isSelected
}
override fun updateDrawState(ds: TextPaint) {
super.updateDrawState(ds)
ds.color = if (isPressed) mPressedTextColor else mNormalTextColor
ds.isUnderlineText = false
}
}
class LinkTouchMovementMethod : LinkMovementMethod() {
private var pressedSpan: TouchableSpan? = null
override fun onTouchEvent(
textView: TextView,
spannable: Spannable,
event: MotionEvent
): Boolean {
if (event.action == MotionEvent.ACTION_DOWN) {
pressedSpan = getPressedSpan(textView, spannable, event)
pressedSpan?.setPressed(true)
Selection.setSelection(
spannable, spannable.getSpanStart(pressedSpan),
spannable.getSpanEnd(pressedSpan)
)
} else if (event.action == MotionEvent.ACTION_MOVE) {
val touchedSpan = getPressedSpan(textView, spannable, event)
if (touchedSpan !== pressedSpan) {
pressedSpan?.setPressed(false)
pressedSpan = null
Selection.removeSelection(spannable)
}
} else {
pressedSpan?.setPressed(false)
super.onTouchEvent(textView, spannable, event)
pressedSpan = null
Selection.removeSelection(spannable)
}
return true
}
private fun getPressedSpan(
textView: TextView,
spannable: Spannable,
event: MotionEvent
): TouchableSpan? {
val x = event.x.toInt() - textView.totalPaddingLeft + textView.scrollX
val y = event.y.toInt() - textView.totalPaddingTop + textView.scrollY
val layout = textView.layout
val position = layout.getOffsetForHorizontal(layout.getLineForVertical(y), x.toFloat())
val link = spannable.getSpans(position, position, TouchableSpan::class.java)
if (link.isNotEmpty() && positionWithinTag(position, spannable, link[0])) {
return link[0]
}
return null
}
private fun positionWithinTag(position: Int, spannable: Spannable, tag: Any) =
position >= spannable.getSpanStart(tag)
&& position <= spannable.getSpanEnd(tag)
companion object {
val instance by lazy {
LinkTouchMovementMethod()
}
}
}
Поместите код 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>
Попробуйте и скажите, хотите ли вы этого или что-то еще. обнови меня чувак.