web-dev-qa-db-ja.com

同じTextView内の他のClickableSpansに影響を与えることなく、押されたときに単一のClickableSpanのテキストの色を変更する

複数のClickableSpansを含むTextViewがあります。 ClickableSpanが押されたときに、テキストの色を変更する必要があります。

TextViewのtextColorLink属性としてカラー状態リストを設定しようとしました。これにより、ユーザーがTextViewでanywhereをクリックしたときにallスパンの色が変わるため、望ましい結果が得られません。

興味深いことに、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>
44

私は最終的に、私が望んでいたすべてを行うソリューションを見つけました。 この答え に基づいています。

これは、修正された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());

そして、これはLinkTouchMovementMethodによって設定された押された状態に基づいて描画状態を編集する修正されたClickableSpanです:(リンクから下線も削除します)

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

よりシンプルなソリューション、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

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を2つのスパンを持つTextViewに適用しました。スパンをクリックすると、スパンが青で強調表示されました。 myTextView.setHighlightColor(getResources().getColor(Android.R.color.transparent));はバグを修正しました。

備考2。

normalTextColorpressedTextColor、およびpressedBackgroundColorを渡すときに、リソースから色を取得することを忘れないでください。

ここでリソースIDの代わりに解決された色を渡す必要があります

8
Maksim Dmitriev

これらの解決策はすべて、大変な作業です。

Android:textColorLinkTextViewで選択します。次に、updateDrawState(...)をオーバーライドする必要のないclickableSpanを作成します。出来た。

ここに簡単な例:

あなたの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>

最後に、xmlのTextViewで次のようにします。

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

これで、clickableSpanで押された状態を持つことができます。

5
j2emanue

このカスタム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);
2
pskink

これは、多くのクリック要素を取得した場合の私のソリューションです(インターフェイスが必要です):インターフェイス:

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