Android事件分发传递机制的领悟和理解

 
(此文章是以发表日期的两年前所写,但至今来看仍不过时,所以再在此发表)

这两天在做这个美女图片软件时,为了实现一个需求,遇到了由于事件分发传递机制引起的种种异常、难题和BUG,对事件分发传递有了进一步的理解,悟出一种重写事件分发的最佳实践(个人认为的最佳方法)。。

需求

如图,主界面是由三个ListView和一个标题栏组成的,三个ListView都可以自由上下滑动,现有一个需求:
当手指处于中间ListView的上半部分滑动时,旁边两个ListView也要向相同方向跟随中间的ListView滑动。

看似简单的需求,却可以引发种种BUG,这里就记录下这些问题产生的原因和解决的方法。。

首先,我们要明白Android事件传递的流程
dispatchTouchEvent(MotionEvent ev)  →  onInterceptTouchEvent(MotionEvent ev)    →   onTouchEvent(MotionEvent ev)
由父控件向子控件一层一层向下传递,经过 (事件分发  →  事件拦截   →  事件处理)流程 ,这里不对此作详细介绍

解决方案

这里简称左侧ListView为lv1,中间ListVIew为lv2,右边ListView为lv3
最容易想到的解决方案就是重写 dispatchTouchEvent()方法,手动为三个ListVIew分发事件
在Activirt中重写
dispatchTouchEvent()方法, 代码如下:
 
     public  boolean  dispatchTouchEvent( MotionEvent  ev) {
         //  x值 lv1 lv1 lv1
         if ( ev. getRawX()  <  lv1. getWidth()) {
              lv1. dispatchTouchEvent( ev);
         //  x lv1  * 2 lv3 lv3
        }  else  if ( ev. getRawX()  >  lv1. getWidth()  *  2) {
              lv3. dispatchTouchEvent( ev);
        }  else {
              // lv2lv2
              lv2. dispatchTouchEvent( ev);
              // ylvlv2
              // lv1lv3
              if ( ev. getRawY()  <  lv1. getHeight() /  2) {
                   lv1. dispatchTouchEvent( ev);
                   lv3. dispatchTouchEvent( ev);
             }
        }
         // true
         return  true;
    }
 
 测试确实也没有问题,解决了需求。但是就此引发了琢磨几天的一连串BUG....
 

lv2和lv3无法接收点击事件
预期效果:点击listview中的美女条目,跳转该美女的图片集
lv1正常,达到预期;lv2和lv3可以正常滑动,但是无法响应点击。

问题产生原因分析
重写 dispatchTouchEvent()方法,给子ListView传递事件时,事件对象ev被原封不动的传递了下去。
比如用户点击lv2的一个条目,此时假设每个lv的宽度为100,点击点ev的x坐标为150,经过上面判断,150既不小于100,也不大于200,则进入else代码块,执行
lv2.dispatchTouchEvent(ev);,ev被原封不动的传递给lv2
会造成什么样的后果呢?
lv2在判断点击点位于哪个条目的时候,ev的x坐标为150,而lv2的宽度一共只有100,这个x坐标超出了lv2的宽度,也就是说点到了lv2的外面,那么这个点击当然就无法正确触发条目点击事件了。lv3也是同理。
问题解决方案
 当事件需要分发给lv2时,需要将点击点的x坐标减去lv的宽度,重新设置给ev,再传递下去。
 
 当事件需要分发给lv3时,需要将点击点的 x坐标减去lv的宽度 * 2,重新设置给ev,再传递下去。

 

上方标题栏可以接收触摸事件
在上方标题栏中触摸上下拖动和点击,也会造成ListView滑动和触发lv1的点击事件

问题产生原因分析
要处理三个ListView的事件分发,应该重写它们的父布局LinearLayout的 dispatchTouchEvent()方法,而不是重写 Activity的事件分发方法,这样会影响到Activirt上的所有控件。
问题解决方案
创建自定义View继承 LinearLayout,重写 dispatchTouchEvent()方法


BUG解决
 
     public  boolean dispatchTouchEvent(MotionEvent ev) {
         // x
         int x  = ( int) ev.getRawX();
         // y
         int y  = ( int) ev.getRawY();
         // lvlv便
         int width  = lv1.getWidth();
         // xlv1lv1lv1
         if (x  < width) {
            lv1.dispatchTouchEvent(ev);
         // xlv1 * 2lv3lv3
        }  else  if (x  > width  * 2) {
            
             // lv3xlv
            ev.setLocation(x  - (width  * 2), y);
            
            lv3.dispatchTouchEvent(ev);
        }  else {
            
             // lv2xlv
            ev.setLocation(x  - width, y);
            
             // lv2lv2
            lv2.dispatchTouchEvent(ev);
             // ylvlv2
             // lv1lv3
             if (y  < lv1.getHeight() / 2) {
                lv1.dispatchTouchEvent(ev);
                lv3.dispatchTouchEvent(ev);
            }
        }
         // true
         return true;
    }
 

追求优雅的代码

上述代码中,去掉了系统的super.dispatchTouchEvent(ev)方法,造成很多东西需要自己处理,判断了三种情况,管理了三个ListView的事件分发
完全可以优化,其实实现需求只需要判断一种情况:当滑动点位于lv2的上半部分的时候,将事件同时也分发给另外两个ListVIew

优化后, LinearLayout的 dispatchTouchEvent()方法
 
     public  boolean  dispatchTouchEvent( MotionEvent  ev) {
         // ListView1
         View  view  =  getChildAt( 0);
         if ( MotionEvent. ACTION_DOWN  ==  ev. getAction()) {
              downY  = ( intev. getY();
        }
         int  x  = ( intev. getX();
         int  width  =  view. getWidth();
         // xlistviewylistview
         if ( x  >  width  &&  x  <  width  *  2  &&  downY  <  view. getHeight() /  2) {
              //ListView
              view. dispatchTouchEvent( ev);
              getChildAt( 2). dispatchTouchEvent( ev);
        }
         return  super. dispatchTouchEvent( ev);
    }

不但没有各种BUG,代码长度也减少了这么多。。

经过这次事件,总结出如下几点经验:

1、没事千万不要删掉return super.dispatchTouchEvent(ev),系统为我们做了N多事情,包括事件该如何传递,传递下去该如何控制xy坐标,等等等等...
2、事件的传递,尽量不要重写Activity的,它的处理逻辑和ViewGroup的略有不同,应该重写目标控件的父控件。
3、如必须手动向下级子View传递事件,则需要计算并重新设置好x、y坐标,再传递
4、重写dispatchTouchEvent、onTouchEvent、方法时,尽量不要直接return true;或者 return false,除非系统对事件的处理与我们的预期有冲突。
 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值