前言
在做章节管理的过程中,发现一个问题。单纯的提供无限滚动的ListView,其实有点将问题复杂化了;
如果仅仅用无限滚动的ListView,那么index会重复出现,这样内容和index就无法绑定;如果单纯的通过判断当前操作是上一页还是下一页,感觉会搞的挺麻烦的;之前就是这样,引入从当前章跳转到上一章或者下一章的操作,所做的事其实有点乱;
所以我计划重新设计一下,整个阅读器是由外面一个大的ListView,里面嵌套三个小ListView,这三个小ListView分别代表上一章,下一章,当前章,这三个部分;上一章的ListView,将初始index设置到末尾;然后这三个小ListView跟外面的大ListView都用同样的动画处理;脑补一下,好像这种方式没什么问题?
由此带来一个问题,如何让大ListView中的小ListView们可以滑动,滑动的时候会有嵌套滑动效果;
方案
其实在挺久之前,我就思考过这个问题:
[Flutter]从零开始实现一个嵌套滑动的PageView(一)
当时的方案是参考NestedScrollView,不过存在一个问题:
ListView、或者其他可滑动View,在动画结束前,或者说begin 一个 新activity前,快速滑动、或者滑动到一半按住再接着滑动的话,滑动的是最外面的View,而非所需要的内部View;
其实严格来说,是任何操作内部View都不会反馈;甚至手势竞争场它都没参与~
比如说这样,可以看到中间tab01所属的tab000、tab001这四个子tab直接跳过了,操作的是tab00、tab01这一层的tab:
PS : 在extend_tab 的 issue 区有这种快速滑动情况下的解决方案,说白了就是让动画变快,让快速滑动的操作速度不如动画播放的速度,这样就能解决这个问题,调整滑动动画到一定程度,勉强还是能接受的:issue
这次的方案设计上,大体参考之前的方案,不过尝试将核心逻辑抽到mixin中,以类似android的 NestedScrollChild、NestedScrollParent这种形式处理封装提供出来;另外尝试解决下上面提到的这个问题:
设计
首先是之前的方案修改:
在之前的方案中,是参考NestedScrollView的实现方式,将NestedScrollView和其内部可滑动View的controller结合组装成一个coordiantor统一管理;
这次整体逻辑部分不动,我想将逻辑放到mixin中,然后子View通过寻找parent中的这个mixin的方式来实现互通;毕竟对于这种可滑动的View来说,只需要获取到controller这种东西,就能进行操纵;
其次由于上述方案是基于子View本身调用判断的基础上来实现的,所以首先要解决的问题就是上面提到的,子View不会反馈,甚至都没加入到手势竞争场的这个问题;
分析
首先这个问题的源头就是子View的手势没有添加到手势竞争场中,那么首先就要分析的就是手势竞争的流程:
不过这个问题,其实文章太多太多了,其流程也不是一句两句就能解释清楚的,所以这里就不再赘述,直接关注为什么没添加到手势竞争器中的问题:
由于手势竞争器中添加手势的方法是 addPointer 方法,那么追踪一下这个方法,就可以看到RawGestureDetector的身影;
那么问题来了,为什么子Scrollable所持有的RawGestureDetector并没有将手势信息放到竞争器中呢?
答案很简单,给拦截了;
在之前的ListView分析中,可以看到Scrollable中有个 IgnorePointer ,ScrollController也会调用setIgnorePointer方法来给是否拦截设置值,而很多activity都会将这个值设置为true给拦截掉子View的手势事件;那么将这个值固定为false,就可以让子View获取到手势事件的优势,最优先处理;
比如说extend_tab 这个项目,上面的demo示例,只要简单的不拦截,就能解决掉快速滑动的问题:
PS : 当然这里仅作快速验证,好孩子不要学我,老老实实改activity的shouldIgnorePointer去;
现在看下效果,可以看到上面indicator的滑块动画即使没结束,也不影响子tab去处理手势:
那么现在是毫无问题么?肯定不是,比如说滑到一半,松开立马按住,再次滑动的情况,由于外面的tab在竞争器中败给了子tab,触发了cancel复位,所以即使没有松开手势,外面的tab还是滑动了(可以看到鼠标一直在按着没松开过,仅仅最后稍微移动了一点点):
所以关于是否拦截这块,就要结合逻辑综合判断了;不能单纯的直接设置个false完事;