Cambia el color del texto de un solo ClickableSpan cuando se presiona sin afectar a otros ClickableSpans en el mismo TextView

Tengo un TextView con múltiples ClickableSpans en él. Cuando se presiona un ClickableSpan, quiero que cambie el color de su texto.

He intentado establecer una lista de estado de color como el atributo textColorLink de TextView. Esto no produce el resultado deseado porque esto hace que todos los tramos cambien de color cuando el usuario hace clic en cualquier parte de TextView.

Curiosamente, el uso de textColorHighlight para cambiar el color de fondo funciona como se esperaba: Al hacer clic en un tramo, solo cambia el color de fondo de ese tramo y al hacer clic en cualquier otro lugar del TextView no se hace nada.

También he intentado configurar ForegroundColorSpans con los mismos límites que ClickableSpans, donde paso la misma lista de estado de color que el recurso de color. Esto tampoco funciona. Los tramos siempre mantienen el color del estado predeterminado en la lista de estados de color y nunca ingresan el estado presionado.

¿Alguien sabe como hacer esto?

Esta es la lista de estado de color que utilicé:

    

Finalmente encontré una solución que hace todo lo que quería. Está basado en esta respuesta .

Este es mi LinkMovementMethod modificado que marca un lapso como se presiona al inicio de un evento táctil (MotionEvent.ACTION_DOWN) y lo desmarca cuando el toque finaliza o cuando la ubicación táctil se mueve fuera del lapso.

 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); } } 

Esto debe aplicarse a TextView de esta manera:

  yourTextView.setMovementMethod(new LinkTouchMovementMethod()); 

Y este es el ClickableSpan modificado que edita el estado del sorteo basado en el estado presionado establecido por el LinkTouchMovementMethod: (también elimina el subrayado de los enlaces)

 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); } } 

Solución mucho más simple, 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); } }; 

La respuesta de legr3c me ayudó mucho. Y me gustaría agregar algunas observaciones.

Observación n. ° 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 a un TextView con dos tramos. Los tramos se resaltaron con azul cuando se hizo clic en ellos. myTextView.setHighlightColor(getResources().getColor(android.R.color.transparent)); corrigió el error.

Observación n. ° 2

No olvides obtener los colores de los recursos al pasar normalTextColor , pressedTextColor y pressedBackgroundColor .

Debería pasar el color resuelto en lugar de la identificación del recurso aquí

Todas estas soluciones son demasiado trabajo.

Simplemente configure android:textColorLink en su TextView a algún selector. A continuación, cree un clickableSpan sin necesidad de anular updateDrawState (…). Todo listo.

aquí un ejemplo rápido:

En tus strings.xml tienes una cadena declarada como esta:

 This is my message%1$s these words are highlighted%2$s and awesome.  

luego en tu actividad:

 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); } 

y aquí están las partes importantes ..declarar un selector como este llamándolo myselector.xml :

      

Y por último en su TextView en xml haga esto:

   

Ahora puede tener un estado presionado en su clickableSpan.

prueba este ClickableSpan personalizado:

 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); } } 

y este 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) { } } 

pruébalo en 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); 

Esta es mi solución si tienes muchos elementos de clic (necesitamos una interfaz): La interfaz:

 public interface IClickSpannableListener{ void onClickSpannText(String text,int starts,int ends); } 

La clase que administra el evento:

 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); } } 

En la clase principal:

 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); } } 

Coloque el código java de la siguiente manera:

 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; } } 

En el código anterior, especifique el color de wat que desee.

Cambia el estilo .xml como quieras.

    

Pruébalo y di que es esto lo que quieres o algo más. actualizarme amigo.