Change the text color of a single ClickableSpan when pressed without affecting other ClickableSpans in the same TextView

后端 未结 8 1323
小鲜肉
小鲜肉 2020-11-29 21:19

I have a TextView with multiple ClickableSpans in it. When a ClickableSpan is pressed, I want it to change the color of its text.

I have tried setting a color state

8条回答
  •  情话喂你
    2020-11-29 21:58

    I finally found a solution that does everything I wanted. It is based on this answer.

    This is my modified LinkMovementMethod that marks a span as pressed on the start of a touch event (MotionEvent.ACTION_DOWN) and unmarks it when the touch ends or when the touch location moves out of the span.

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

提交回复
热议问题