昨日第三方调查机构 Instrumental 发布了关于三星Note 7电池爆炸的调查报告,表明是因为手机本身的“激进设计”所导致一一Note 7的电池与主板之间的距离非常窄,最窄处仅0.1mm!这使得电池即使在日常使用中也会受到挤压。而压力会使电池正、负极的“分离装置”很容易损坏,正负接触会引发电池发热起火。报告还称,三星的工程师在设计过程中极力加大电池体积,以此换取更大的电池容量。追求更大的电池容量固然是好事,它能为手机带来更持久的续航,但安全始终应该放在第一位!
本篇来自 徐俊 的投稿,分析了滑动冲突的原因,以及针对嵌套ViewPager给出了解决方案,希望能帮助有需要的朋友。
徐俊 的博客地址:
http://blog.csdn.net/gdutxiaoxu
这篇博客主要讲解一下几个问题:
ScrollView 里面嵌套 ViewPager:
ViewPager 里面嵌套 ViewPager:
这篇博客不打算详细讲解View的事件分发机制,因为网上已经出现了一系列的好文章,我自己的水平也有限,目前肯定写得不咋的。
先啰嗦一下,View 的事件分发机制主要涉及到以下方法:
dispatchTouchEvent ,这个方法主要是用来分发事件
onInterceptTouchEvent,这个方法主要是用来拦截事件的(需要注意的是ViewGroup才有这个方法,View没有onInterceptTouchEvent这个方法
onTouchEvent,这个方法主要是用来处理事件的
requestDisallowInterceptTouchEvent(true),这个方法能够影响父View是否拦截事件,true表示 不拦截事件,false表示拦截事件
下面引用图解 Android 事件分发机制这一篇博客的内容:
仔细看的话,图分为3层,从上往下依次是Activity、ViewGroup、View
事件从左上角那个白色箭头开始,由 Activity 的 dispatchTouchEvent 做分发
箭头的上面字代表方法返回值,(return true、return false、return super.xxxxx(),super 的意思是调用父类实现。
dispatchTouchEvent 和 onTouchEvent 的框里有个【true—->消费】的字,表示的意思是如果方法返回true,那么代表事件就此消费,不会继续往别的地方传了,事件终止。
目前所有的图的事件是针对 ACTION_DOWN 的,对于 ACTION_MOVE 和 ACTION_UP 我们最后做分析。
之前图中的 Activity 的 dispatchTouchEvent 有误(图已修复),只有 return super.dispatchTouchEvent(ev) 才是往下走,返回 true 或者 false 事件就被消费了(终止传递)。
总结
当TouchEvent发生时,首先Activity将TouchEvent传递给最顶层的View,TouchEvent最先到达最顶层 view 的 dispatchTouchEvent ,然后由 dispatchTouchEvent 方法进行分发
如果 dispatchTouchEven t返回 true 消费事件,事件终结。
如果 dispatchTouchEvent 返回 false ,则回传给 父View 的 onTouchEvent 事件处理;
onTouchEvent 事件返回 true,事件终结,返回 false,交给 父View 的 OnTouchEvent 方法处理
如果 dispatchTouchEvent 返回 super 的话,默认会调用自己的 onInterceptTouchEvent方法
默认的情况下 interceptTouchEvent 回调用 super方法,super方法 默认返回 false,所以会交给 子View 的 onDispatchTouchEvent 方法处理
如果 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给它的 onTouchEvent 来处理,
如果 interceptTouchEvent 返回 false ,那么就传递给子 view ,由 子view 的 dispatchTouchEvent 再来开始这个事件的分发。
关于更多详细分析,请查看原博客:
图解Android事件分发机制
http://www.jianshu.com/p/e99b5e8bd67b
常见的三种情况
第一种情况,滑动方向不同
第二种情况,滑动方向相同
第三种情况,上述两种情况的嵌套
解决思路
看了上面三种情况,我们知道他们的共同特点是 父View 和 子View 都想争着响应我们的触摸事件,但遗憾的是我们的触摸事件 同一时刻 只能被某一个View或者ViewGroup拦截消费,所以就产生了滑动冲突?那既然同一时刻只能由某一个View或者ViewGroup消费拦截,那我们就只需要 决定在某个时刻由这个View或者ViewGroup拦截事件,另外的 某个时刻由 另外一个View或者ViewGroup拦截事件不就OK了吗?综上,正如 在 《Android开发艺术》 一书提出的,总共 有两种解决方案
以下解决思路来自于 《Android开发艺术》 书籍
下面的两种方法针对第一种情况(滑动方向不同),父View 是 上下滑动,子View 是 左右滑动 的情况。
外部解决法
从 父View 着手,重写 onInterceptTouchEvent 方法,在 父View 需要拦截的时候拦截,不要的时候返回false,伪代码大概如下
内部解决法
从子View着手,父View 先不要拦截任何事件,所有的 事件传递给 子View,如果 子View 需要此事件就消费掉,不需要此事件的话就交给 父View 处理。
实现思路 如下,重写 子View 的 dispatchTouchEvent 方法,在 Action_down 动作中通过方法 requestDisallowInterceptTouchEvent(true) 先请求 父View 不要拦截事件,这样保证 子View 能够接受到Action_move事件,再在Action_move动作中根据 自己的逻辑是否要拦截事件,不要的话再交给 父View 处理
外部解决法
如上面所述,从 父View ScrollView着手,重写 OnInterceptTouchEvent 方法,在上下滑动的时候拦截事件,在左右滑动的时候不拦截事件,返回 false,这样确保 子View 的dispatchTouchEvent方法会被调用,代码如下
内部解决法
如上面上述,通过 requestDisallowInterceptTouchEvent(true) 方法来影响 父View 是否拦截事件,我们通过重写 ViewPager 的 dispatchTouchEvent()方法,在左右滑动的时候请求 父View ScrollView不要拦截事件,其他的时候由 子View 拦截事件
当我们ScrollView的最上层的Layout里面多多个孩子的时候,当下面一个孩子是RecyclerView或者ListView的时候,往往会自动滑动到ListView或者RecyclerView 的第一个item,导致进入界面的时候会导致 RecyclerView 上面的 View 被滑动到界面之外,看不见,这时候的用户体验是比较差的
即结构如下面的时候
在Activity中的相关解决方法
于是我查找了相关的资料,在Activity中完美解决,主要要一下两种方法
第一种方法,重写 Activity 的 onWindowFocusChanged()方法,在里面调用 mNoHorizontalScrollView.scrollTo(0,0) 方法,滑动到顶部,因为 onWindowFocusChanged 是在所有View绘制完毕的时候才会回调的,不熟悉的话建议先回去看一下Activity的生命周期的相关介绍
第二种解决方法,调用 RecyclerView上面的View的一下方法,让其获取焦点
view.setFocusable(true);
view.setFocusableInTouchMode(true);
view.requestFocus();
这段代码在初始化的时候就让该界面的顶部的某一个控件获得焦点,滚动条自然就显示到顶部了。
在Fragment中的相关解决方法
同样是调用第二种方法,调用RecyclerView上面的View的一下方法,让其获取焦点
view.setFocusable(true);
view.setFocusableInTouchMode(true);
view.requestFocus();
这段代码在初始化的时候就让该界面的顶部的某一个控件获得焦点,滚动条自然就显示到顶部了。但是该方法存在缺点,就是当我们上面的view如果滑动到一半的时候,切换到下一个Fragment,在切换回来的时候,RecyclerView的第一个item会自动滑动到顶部。目前我还没有找到相对比较好的解决这个问题的方法。
个人疑点
借鉴于解决Activity的方法,目前我还没有找到一个方法是在Fragemnt界面完全绘制完毕以后回调的方法。
内部解决法
从 子View ViewPager着手,重写 子View 的 dispatchTouchEvent方法,在 子View 需要拦截的时候进行拦截,否则交给 父View 处理,代码如下
外部解决法
这个如果要采用外部解决法来解决的话想,相对很麻烦,我提一下自己的个人思路,我们可以先测量 子View 在哪个区域,然后我们在根据我们按下的点是否在区域以内,如果是的话,在根据 子View 时候需要拦截进行处理
对于这种效果,上面是轮播图的,下面是 RecyclerView 或者 ListView 的,一般有一下几种实现方式
使用我们上述提高的ScrollView里面嵌套ViewPager和RecyclerView,这种实现方式需要自己解决View滑动事件的冲突,同时还有我在上述提高的在Fragment中存在的问题
使用listView的addHeaderView来实现,或者是通过多种不同的item来实现
使用RecyclerView添加headerView来实现,或者复用多种不同的item来实现。关于RecyclerView如何添加headerView可以参考鸿洋大神的这一篇博客:
Android优雅的为RecyclerView添加HeaderView和FooterView
http://blog.csdn.net/lmj623565791/article/details/51854533
其布局文件如下,Activity代码见项目中的SixActivity
关于CoordinatorLayout的更多用法,可以参考我的这一篇博客
使用CoordinatorLayout打造各种炫酷的效果
http://blog.csdn.net/gdutxiaoxu/article/details/52858598
在这篇博客的最后提高的实现轮播图+list列表的几种实现形式,刚开始是不想写的,后面因为ScrollView里面嵌套ViewPager和RecyclerView在fragment中RecyclerView抢占焦点,在某些情况下用户体验不好,才写出来的,跟这篇博客要讲解的View滑动事件冲突没有多大关系,只是给读者提供多种思路而已
至于CoordinatorLayout,是google IO 2015中提出来的,功能很强大,可以说是专门为了解决嵌套导滑动而产生的,极大地方便了开发者,对于初学者,可以暂时不必掌握它,先把其他的基础学好就好
源码下载地址:
https://github.com/gdutxiaoxu/TouchDemo.git
每天学习累了,看些搞笑的段子放松一下吧。关注最具娱乐精神的公众号,每天都有好心情。
如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。
欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号: