web-dev-qa-db-fra.com

Modifier la couleur du texte d'un seul ClickableSpan lorsqu'il est pressé sans affecter les autres ClickableSpans dans la même TextView

J'ai un TextView avec plusieurs ClickableSpans dedans. Quand un ClickableSpan est pressé, je veux qu'il change la couleur de son texte.

J'ai essayé de définir une liste d'état des couleurs comme attribut textColorLink de TextView. Cela ne donne pas le résultat souhaité car cela provoque tous les étendues à changer de couleur lorsque l'utilisateur clique n'importe où sur TextView.

Fait intéressant, l'utilisation de textColorHighlight pour modifier la couleur d'arrière-plan fonctionne comme prévu: un clic sur une étendue modifie uniquement la couleur d'arrière-plan de cette étendue et un clic n'importe où ailleurs dans TextView ne fait rien.

J'ai également essayé de définir ForegroundColorSpans avec les mêmes limites que les ClickableSpans où je passe la même liste d'état de couleur que ci-dessus comme ressource de couleur. Cela ne fonctionne pas non plus. Les plages conservent toujours la couleur de l'état par défaut dans la liste des états de couleur et n'entrent jamais dans l'état enfoncé.

Est-ce que quelqu'un sait comment faire ça?

Voici la liste des états de couleur que j'ai utilisée:

<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>
44
Steven Meliopoulos

J'ai finalement trouvé une solution qui fait tout ce que je voulais. Il est basé sur cette réponse .

Il s'agit de ma LinkMovementMethod modifiée qui marque une étendue comme pressée au début d'un événement tactile (MotionEvent.ACTION_DOWN) et la désélectionne lorsque le toucher se termine ou lorsque l'emplacement tactile se déplace hors de l'étendue.

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

Cela doit être appliqué au TextView comme suit:

    yourTextView.setMovementMethod(new LinkTouchMovementMethod());

Et ceci est le ClickableSpan modifié qui édite l'état de dessin en fonction de l'état enfoncé défini par le LinkTouchMovementMethod: (il supprime également le soulignement des liens)

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);
    }
}
62
Steven Meliopoulos

Solution beaucoup plus 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);
    }
};
21
zundi

réponse de legr3c m'a beaucoup aidé. Et j'aimerais ajouter quelques remarques.

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

J'ai appliqué LinkTouchMovementMethod à un TextView avec deux portées. Les travées étaient surlignées en bleu lorsque vous les cliquiez. myTextView.setHighlightColor(getResources().getColor(Android.R.color.transparent)); a corrigé le bogue.

Remarque n ° 2.

N'oubliez pas d'obtenir des couleurs des ressources lors du passage de normalTextColor, pressedTextColor et pressedBackgroundColor.

Devrait passer la couleur résolue au lieu de l'ID de ressource ici

8
Maksim Dmitriev

Toutes ces solutions sont trop de travail.

Réglez simplement Android:textColorLink dans votre TextView vers un sélecteur. Créez ensuite un ClickableSpan sans avoir besoin de remplacer updateDrawState (...). Terminé.

voici un exemple rapide:

Dans votre strings.xml ont une chaîne déclarée comme ceci:

<string name="mystring">This is my message%1$s these words are highlighted%2$s and awesome. </string>

puis dans votre activité:

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

et voici les parties importantes .. déclarez un sélecteur comme celui-ci l'appelant 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>

Et en dernier dans votre TextView en xml faites ceci:

 <TextView
     Android:id="@+id/mytextview"
     Android:background="@Android:color/transparent"
     Android:text="@string/mystring"
     Android:textColorLink="@drawable/myselector" />

Vous pouvez maintenant avoir un état enfoncé sur votre clickableSpan.

5
j2emanue

essayez ce ClickableSpan personnalisé:

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

et ce 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) {
    }
}

testez-le dans 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);
2
pskink

C'est ma solution si vous avez de nombreux éléments de clic (nous avons besoin d'une interface): L'interface:

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

La classe qui gère l'événement:

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 classe principale:

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);
  }
}
2
Carlos Gómez