Android——ViewGroup的事件分发

上篇文章分析了AndroidUI事件传递,点击冲突,接下来用一个例子继续学习ViewGroup的事件分发。


ViewGroup?它和普通的View有什么区别?
ViewGroup就是一组View的集合,它包含很多的子View和子VewGroup,是android中所有布局的父类或间接父类,像LinearLayout、RelativeLayout等都是继承自ViewGroup的。但ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能。ViewGroup继承结构示意图如下所示:
这里写图片描述

这里写图片描述

我们平时项目里经常用到的各种布局,全都属于ViewGroup的子类。


接下来,移步郭霖大神的文章看一下源码分析:

Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

用一个图总结一下:
这里写图片描述

好啦,这样就解决了这两个问题:

  1. ViewGroup中Item点击事件和子控件的冲突;
  2. onTouch与onClick之间的冲突;

常见的ListView中的Item点击事件和子控件的冲突或者item点击没有反应的解决办法:

此时这些子控件会将焦点获取到,所以常常当点击item时变化的是子控件,item本身的点击没有响应。
这时候就可以使用descendantFocusability来解决啦,API描述如下:
android:descendantFocusability
该属性是当一个为view获取焦点时,定义viewGroup和其子控件两者之间的关系。

属性的值有三种:

    beforeDescendants:viewgroup会优先其子类控件而获取到焦点


    afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点


    blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点

通常我们用到的是第三种,即在Item布局的根布局加上android:descendantFocusability=”blocksDescendants”的属性。


好了,接下来就结合上篇文章应该就能理解UI点击事件传递与点击冲突了。好了,接下来 view的滑动冲突

Android中的滑动冲突
只要在界面中内外两层同时滑动的时候,就会产生滑动。意即有一个占主导地位的View抢着去执行滑动操作,从而带来非常差的用户体验。
常见的滑动冲突场景分为如下三种:

  1. 外部滑动方向与内部滑动方向不一致,主要是将ViewPager和Fragment配合使用所形成的页面滑动效果。

    在这个效果中,可以通过左右滑动来切换页面,而每个页面内部往往又是一个Listview。这种情况下本来是很容易发生滑动冲突的,但ViewPager内部处理了这种滑动冲突,所以如果使用ViewPager,则无需担心这个问题。
    但如果使用的是Scroller,则必须手动处理滑动冲突了。否则后果就是内外两层只能有一层能够滑动。
    
    具体来说是:根据滑动的方向判断到底由什么来拦截事件。 
    
  2. 外部滑动和内部滑动方向一致,比如ScrollView嵌套ListView,或者是ScrollView嵌套自己。表现在要么只能有一层能够滑动,要么两者滑动起来显得十分卡顿。

    处理规则:从业务上寻找突破点,比如业务上有规定:当处于某种状态时需要外部View处理用户的操作,而处理另一种状态时则让内部View处理用户的操作。 
    
  3. 上面两种情况的嵌套。

    处理规则:同场景二
    

滑动冲突的解决方式:
针对场景一的滑动冲突,有两种处理滑动的解决方式:
1.外部拦截法:
所谓外部拦截法是指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,这样就可以解决滑动冲突的问题,这个方法需要重写父容器的onInterceptTouchEvent方法。伪代码如下所示:

 @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercepted=false;
        int x=(int)event.getX();
        int y=(int)event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                intercepted=false;
                break;
            case MotionEvent.ACTION_MOVE:
                if(父容器需要当前点击事件){
                   intercepted=true;
                }else {
                    intercepted=false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercepted=false;
                break;
            default:
                break;
        }
        mLastXIntercept=x;
        mLastYIntercept=y;
        return intercepted;
    }

2.内部拦截法:
内部拦截法是指父容器不拦截任何事件,所有的事件传递给子元素,如果子元素需要此事件就直接消耗掉,如果不需要则交由父容器处理。需要配合requestDisallowInterceptTouchEvent方法才能正常工作。伪代码如下:

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        int x=(int)event.getX();
        int y=(int)event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX=x-mLastX;
                int deltaY=y-mLastY;
                if(父容器需要当前点击事件){
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }
        mLastX=x;
        mLastY=y;
        return super.dispatchTouchEvent(event);
    }

另外,为了使父容器不接收ACTION_DOWN事件,我们需要对父类进行一下修改:

 @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        int action=event.getAction();
        if (action==MotionEvent.ACTION_DOWN){
            return false;
        }else{
            return true;
        }
    }

以上两种方式,是针对场景一而得出的通用的解决方法。对于场景二和场景三而言,只需改变相关的滑动规则的逻辑即可。
注意:因为内部拦截法的操作较为复杂,因此推荐采用外部拦截法来处理常见的滑动冲突。

主要参考
Android开发艺术探索读书(三)-View的事件体系

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值