0 本章内容

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

1 TextView自定义Span实现

1.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)。则不做处理。

更多相关文章

  1. Android(安卓)Studio 中使用github功能
  2. 进阶必备-Android事件分发机制
  3. Android(安卓)开发之实时更新 App Widget
  4. 让android的webview中的按钮,触发事件,也能像原生按钮一样使用
  5. Android中的双击事件
  6. Android(安卓)开发事件响应之基于监听的事件响应
  7. Android(安卓)上从外部应用注入按键事件流程分析
  8. 从源码剖析PopupWindow 兼容Android(安卓)6.0以上版本点击外部不
  9. Android(安卓)view触摸反馈原理和源码分析

随机推荐

  1. Android(安卓)Contacts(一)—— 读取联系人
  2. Android中跑马灯 maxLines与singleLine
  3. Android(安卓)内置图片
  4. 如何在unity中生成android工程
  5. Android学习笔记之Android安装问题
  6. Android是什么(What is Android)
  7. Android中利用GridView实现水平和垂直均
  8. Android(安卓)Studio小技巧
  9. android如何配置基本环境
  10. Android的Camera架构介绍