Android TextView ClickSpan与onClick事件冲突问题

0 本章内容

  • TextView自定义Span实现
  • 多Span融合
  • 点击事件冲突处理方法

1 TextView自定义Span实现

1.1 产品需求

产品要求实现一个类似于下图的功能

Android TextView ClickSpan与onClick事件冲突问题_第1张图片

其中红框部分需要实现点击,且左右有Padding。

并且整体的文本需要有点击事件 和 长按事件,蓝字处要实现另一个点击事件

1.2 Android中的Span

这个不再赘述了,Android内本身支持很多类型的Span,例如ClickSpan、ForgroundColorSpan等等。具体实现方式大家可以自行查阅。

2 多Span融合方式

此处使用ClickSpan实现蓝字点击、使用自定义的ReplacementSpan实现UI层样式和Padding。

ClickSpan需要搭配LinkMovementMethod实现点击的具体逻辑。本身系统LinkMovementMethod并不支持去除下划线,并且在点击后会有background变化等问题。此处也许要自定义一个。

自定义Method的具体代码如下

public class MyLinkMovementMethod extends LinkMovementMethod {
    private static LinkMovementMethod sInstance;
    private long downTime;

    public static MovementMethod getInstance() {
        if (sInstance == null)
            sInstance = new ReaderQuoterMovementMethod();
        return sInstance;
    }
    @Override
    public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {

        int action = event.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            downTime = System.currentTimeMillis();
        }
        if (action == MotionEvent.ACTION_UP) {
            long interval = System.currentTimeMillis() - downTime;
            int x = (int) event.getX();
            int y = (int) event.getY();

            x -= widget.getTotalPaddingLeft();
            y -= widget.getTotalPaddingTop();

            x += widget.getScrollX();
            y += widget.getScrollY();

            Layout layout = widget.getLayout();
            int line = layout.getLineForVertical(y);
            int off = layout.getOffsetForHorizontal(line, x);

            ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);

            if (link.length != 0) {
                if (interval < ViewConfiguration.getLongPressTimeout()) {
                    link[0].onClick(widget);
                    buffer.setSpan(new BackgroundColorSpan(Color.TRANSPARENT),
                            buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0]),
                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                }
            }
        }
        return true;
    }
}

自定义ReplacementSpan的具体代码如下:

public class QuoterSpan extends ReplacementSpan {

    private static final int DEFAULT_RIGHT_PADDING = 6;
    private Context mContext;
    private Rect mRect;


    public QuoterSpan(Context context) {
        mContext = context;
        mRect = new Rect();
    }

    @Override
    public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fontMetricsInt) {
    //获取原本文本的宽度,并在此基础上添加padding宽度    
    paint.getTextBounds(text.subSequence(0, text.length()).toString(), start, end, mRect);
        return mRect.width() + Math.round(((TextPaint) paint).density * DEFAULT_LEFT_PADDING * 2);
    }

    @Override
    public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
        if (mContext == null) {
            return;
        }
        paint.setColor(ThemeSettingsHelper.getInstance().getThemeColor(mContext, R.color.milk_Blue).getDefaultColor());
        paint.setAntiAlias(true);
        //文本开始绘制区域为最初X值加左边padding
        int textBegin = Math.round(x + ((TextPaint) paint).density * DEFAULT_LEFT_PADDING);
        canvas.drawText(text.subSequence(start, end).toString(), textCenter, y, paint);
    }
}

3、点击事件的冲突问题:

1、单机:如果textView本身有点击事件,此时又添加了ClickSpan,必须搭配MovenmentMethod才能将点击事件拦截在ClickSpan。这一点在源码中可以找到:

   public boolean onTouchEvent(MotionEvent event) {
        final int action = event.getActionMasked();
        if (mEditor != null) {
            mEditor.onTouchEvent(event);

            if (mEditor.mSelectionModifierCursorController != null
                    && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
                return true;
            }
        }
        // 先交由父类决定是否拦截
        final boolean superResult = super.onTouchEvent(event);

       
        // .......


        // 判断是否由MovementMethod处理 
        if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
                && mText instanceof Spannable && mLayout != null) {
            boolean handled = false;

            if (mMovement != null) {
                handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
            }

            // .......
    }

2、长按:如果当前textView设置了longClick事件。而我们的MovementMethod又多在MotionEvent = Up 的时候处理点击逻辑。(放down里可能会误触)。

这就会导致一个问题,当textView的长按被触发后抬起手指,MovementMethod的Up事件也会被触发。

解决:如本文中第一段代码中,在MovementMethod的Down事件时记录一个事件,如果Up和down的间隔时间大于系统内置的长按间隔(500ms默认,各厂商可能会改这个时间,例如小米手机的tap间隔小于系统默认的100ms)。则不做处理。

你可能感兴趣的:(Android,TextView,ClickSpan,onClick)