Android View的滑动冲突解决方法

注意:

  1. 阅读本文需要了解《Android事件分发机制》
  2. 在此知识点,本人也有部分困惑尚未完全解决,也会在文中标出出来。

常见的滑动冲突场景及对应的处理规则

  1. 外部滑动方向和内部滑动方向不一致
    面对这种情况的滑动冲突,解决规则是:根据滑动是水平滑动还是竖直滑动来判断由谁来拦截事件。判断滑动方向的方法是:比较水平方向和竖直方向滑动距离的大小,或者滑动路径和水平方向的夹角,或者根据水平方向和竖直方向的速度差。
  2. 外部滑动方向和内部滑动方向一致
    这种情况的滑动冲突,无法根据滑动的角度判断,一般都是根据业务需要来进行判断。
  3. 上面两种情况的结合
    这种情况比较复杂,一般也需要从业务上找到突破点。

场景一

场景一:假如打算做个像ViewPager一样的效果,父容器如horizonal方向的LinearLayout一样,容纳了三个ListView。

外部拦截法

就是指点击事件都先经过父容器的拦截处理,如果不需要此事件就不拦截,这样就可以解决滑动冲突的问题。外部拦截法需要重写父容器的onInterceptTouchEvent()方法,在内部完成相应的拦截即可。

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;                                 //注解1
                  if (!mScroller.isFinished()){                       //注解2
                        mScroller.abortAnimation();
                        intercepted = true;
                  }
                  break;
            case MotionEvent.ACTION_MOVE:                 //注解3
                  int deltaX = x - mLastXIntercept;
                  int deltaY = y - mLastYintercept;
                  if (Math.abs(deltaX) > Math.abs(deltaY)){     //在这里的if中加入是否拦截的判断
                        intercepted = true;
                  } else {
                        intercepted = false;
                  }
                  break;
            case MotionEvent.ACTION_UP:
                  intercepted = fasle;                       //注解4
                  break;
            default:
                  break;         
      }
      mLastXIntercept = x;
      mLastYintercept = y;
      
      return intercepted; 
}

注解1:
为什么要在ACTION_DOWN时,intercepted = false?因为如果在ACTION_DOWN时,intercepted == true,那么根据Android事件分发机制,后面的MOVE事件和UP都会无条件的交给父容器去处理。这样的话,事件永远无法传递给子View。
此处的困惑:如果仅仅在父容器中设置ACTION_DOWN时,intercepted = false还是不够的。intercepted = false,然后DOWN事件传递给了子View,按照Android事件分发机制,如果子View没有成功处理DOWN事件(即返回了false),最终还是会调用父容器的处理方法。如果这样的话,后面的MOVE事件和UP依然会无条件的交给父容器去处理。
注解2:
这个if内的语句,针对的是下面的情况:如果用户此时在进行父容器的滑动方向(这里是水平滑动),但是在水平滑动之前如果用户再迅速进行竖直滑动,就会导致界面在水平方向无法滑动到终点从而处于一种中间状态。为了避免这种情况,当水平滑动时,下一个序列的点击事件仍然交给父容器处理(哪怕竖直方向滑动距离大于水平方向滑动距离,此时仍然判定是水平滑动)。
注解3:
这一块代码是解决滑动冲突的关键。在MOVE事件中,判断这一滑动事件是水平滑动还是竖直滑动,如果是水平滑动,作为父容器就拦截事件,如果是水平滑动,就不拦截事件,交给子view去处理。
此处的困惑:如果在一系列的MOVE事件中,前部分是水平移动,后部分是竖直移动的,那怎么办?因为没有拦截DOWN事件,所有很有可能事件拦截过程中的mFirstTouchTarget != null,所以后部分的MOVE事件仍然要调用onInterceptTouchEvent(),此时,intercept = false;,那么接着交给子View处理?
如何解答这个困惑呢?有一个结论是:

一旦父容器开始拦截任何一个事件,那么后续的事件都会交给它来处理。

这个结论先记住吧,暂时还没有搞明白为什么会这样。
注解4:
如果父容器在UP事件中返回了true,就会导致子View无法接受到UP事件,这个时候子元素中的onClick事件就无法处罚法。同样的,

因为一旦父容器开始拦截任何一个事件,那么后续的事件都会交给它来处理,所以UP作为最后一个事件也必定可以传递给父容器,即便父容器的onInterceptTouchEvent方法在UP时返回了false

但是依然没有弄明白为什么有这个结论。

内部拦截法

内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理。这种方法需要配合requestDisallowInterceptTouchEvent()方法才能正常工作。
第一步,修改父容器的onInterceptTouchEvent(),让其在DOWN事件返回false,其他情况下返回true。

//父容器内
public boolean onInterceptTouchEvent(MotionEvent event){
      int x = (int)event.getX();
      int y = (int)event.getY();
      int action = event.getAction();
      if (action == MotionEvent.ACTION_DOWN){                  //注解5
            mLastX = x;
            mLastY = y;
            if (!mScroller.isFinished()){
                  mScroller.abortAnimation()
                  return true;
            }
            return true;
      } else {
            return true;
      }
 }

注解5:
父容器拦截了除了DOWN事件以外的其他事件,这样当子元素调用parent.requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件。
第二步,修改子元素的dispatchTouchEvent()方法

//在子View中
public boolean dispatchTouchEvent(MotionEvent event){
      int x = (int) event.getX();
      int y = (int) event.getY();
      switch(event.getAction()){
      case MotionEvent.ACTION_DOWN:
            XXX.requestDisallowInterceptTouchEvent(true);
            break;
      case MotionEvent.ACTION_MOVE:
            int deltaX = x - mLastX;
            int deltaY = y - mLastY;
            if (Math.abs(deltaX) > Math.abs(deltaY)){                              //在这里的if中加入是否拦截的判断
                  XXX.requestDisallowInterceptTouchEvent(false);
            }
            break;
      case MotionEvent.ACTION_UP:
            break;
      default:
            break;
      }
      mLastX = x;
      mLastY = y;
      return super.dispatchTouchEvent(event);               //注解6
}

注解6:因为子view是自定义view,重写的dispatchTouchEvent()方法,在解决了滑动冲突后,调用父类的dispatchTouchEvent()方法来进行原来的事件分发。

场景二和场景三

在总体的实现方法和场景一是一样的,仅仅是在MOVE事件中判断的条件不一样,场景一仅仅是通过滑动方向来进行判断,而场景二和场景三需要判断业务逻辑。这里就不详细介绍了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容