A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 丁佼 黑马帝   /  2016-1-26 10:14  /  3603 人查看  /  0 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 丁佼 于 2016-1-26 10:30 编辑

1.    ClickableSpanListViewitem点击事件冲突解决
1.1.  存在的问题
当包含ClickableSpan的TextView作为ListView的item存在时    ,由于点击事件的处理冲突,会导致列表的点击事件无法响应。
目前网络上的解决方案只能:“ClickableSpan响应+ item单击/长按”响应,本文可以则可以“ClickableSpan响应+ item单击+ item长按 ”响应,并且不存在滑动时会长按响应的问题。
1.2.  处理方法分析
1.2.1. 处理焦点等方法
TextView.setMovementMethod()方法会导致TextView的点击判断被修改,使得onTouchEvent方法始终返回为true,导致ListView无法获取touch事件,所以需要在setMovementMethod后将焦点等标志位设为false
1.2.2. 自定义MovementMethod
LinkMovementMethod的onTouchEvent方法里最终会调用到Touch.onTouchEvent,该方法将down事件返回为true(见下图),使得TextView始终拦截Touch事件,导致ListView的item点击无法响应。所以需要自定义LinkMovementMethod来修改down事件的返回值,同时在TextView的onTouchEvent里根据自定义的标志位来决定是否拦截touch事件。
1.3.  自定义TextView
  public class MyTextViewextends TextView {
    // 在LinkMovementMethod里有ClickableSpan上发生down事件将被修改为true,发生up事件修改为false
    public boolean linkHit;

    publicMyTextView(Context context) {
        super(context);
    }

    publicMyTextView(Context context, AttributeSet attrs) {
        super(context,attrs);
    }

    publicMyTextView(Context context, AttributeSet attrs, intdefStyleAttr) {
        super(context,attrs, defStyleAttr);
    }

    @Override
    public booleanonTouchEvent(MotionEvent event) {
        // super的处理方式会始终返回true,导致ListView的item点击不响应,
        // 此处的处理为如果down或up事件发生在Clickablespan上,将返回true以拦截touch事件,其他时候不拦截
        return linkHit;
    }

    /** 设置movementMethod,并修改TextView不可点击、长按、获取焦点等 */
    public voidsetLocalLinkMovementMethod(LocalLinkMovementMethod movementMethod) {
        setMovementMethod(movementMethod);

        // 除非有ClickableSpan被点击,否则不能让TextView拦截ListView的touch事件,此处设置的标志位用处可以查看View.onTouchEvent方法
        setFocusable(false);
        setClickable(false);
        setLongClickable(false);
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            setContextClickable(false);
        }
    }
}
1.4.  自定义LinkMovementMethod
  /** 判断是否有ClickaSpan被点击,并处理的Touch事件分配 */
public classLocalLinkMovementMethod extends LinkMovementMethod {
    private staticLocalLinkMovementMethod sInstance;

    public staticLocalLinkMovementMethod getInstance() {
        if (sInstance == null)
            sInstance = newLocalLinkMovementMethod();
        return sInstance;
    }

    public booleanonTouchEvent(TextView widget, Spannable buffer,
            MotionEventevent) {
        int action =event.getAction();
        // 参考父类,只判断up和downn事件
        if (action ==MotionEvent.ACTION_UP
                || action ==MotionEvent.ACTION_DOWN) {
            // 获取手指触摸位置的ClickableSpan
            ClickableSpan[]link = getOnClickSpan(widget, buffer, event);
            if (link.length != 0) {
                // 如果手指触摸位置有ClickableSpan,则down事件拦截Touch事件
                if (action ==MotionEvent.ACTION_DOWN) {
                    ((MyTextView)widget).linkHit = true;
                } else if (action ==MotionEvent.ACTION_UP) {
                    ((MyTextView)widget).linkHit = false;
                }
            }
        }
        // 交由父类处理点击事件分发。此处的返回值并不影响touch事件的分发,TextView的touch事件只受linkHit影响
        return super.onTouchEvent(widget,buffer, event);
    }

    /** 参考父类,获取触摸位置的ClickableSpan */
    privateClickableSpan[] getOnClickSpan(TextView widget,
            Spannable buffer,MotionEvent event) {
        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);

        returnbuffer.getSpans(off, off, ClickableSpan.class);
    }
}
1.5.  使用方法
使用自定义的MyTextView,Textview.setMovementMethod()改为使用TextView.setLocalLinkMovementMethod(LocalLinkMovementMethod.getInstance());
当前的解决方案保证了只在有ClickableSpan被点击时TextView才拦截touch事件,也就使得ListView的点击事件可以正常响应了。
更多详情请查看帖子:

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马