请支持原文作者 : 鸿洋
看到这个文章我才想起来,今天推荐一个项目,是我的一位好朋友之前在创业公司的一个项目,后来项目失败了,经允许后将源码分享出来了,该项目也对SwipeRefreshLayout进行了扩展,支持了上拉,如果有实际需求,不妨也试下这个(项目质量非常高)。
https://github.com/pinguo-sunjianfei/Android-Application-ZJB
ps:该项目服务端已经停了,所以只能看代码了,运行不起来了~~
本文作者
本文由Get_Zoom投稿。
Get_Zoom的博客地址:
http://blog.csdn.net/Get_Zoom
1
NestedScrolling机制
之前笔者在设计的时候,想在ViewPager的页面上实现仿微信的左滑删除,但是怎么都实现不了,因为其中跟ViewPager的滑动冲突了,当时才疏学浅(现在也是),进了很多坑,比如滑动的拦截、滑动事件在Down之后会跳过判断等,在没有系统学习过这方面知识的情况下以大败告终。
所以,谷歌人性化地推出了这个机制,滑动之前和父控件商量一下,一切多么融洽,和之前盲人摸象的方式相比人性化多了。
(1)滑动流程
子view获取到点击事件后,询问父亲是否需要配合滑动,然后每一次滑动之前都会询问父亲,并记录下父亲消耗的滑动距离,在上面完成后才进行自己自身的滑动。
(2)接口
NestedScrollingChild
NestedScrollingParent
(3)帮助类
NestedScrollingChildHelper NestedScrollingParentHelper
故名思义,上面的帮助类帮助我们处理了上面父子接口的方法。
它们帮助我们实现了逻辑上的方法,在一些情况下我们只希望处理子接口或者父接口,为了对接可以在另一个接口使用帮助类(比如下面的实例,改造SwipeRefreshLayout,我们更希望作为父亲处理子view事件而滑动自身,对于上层组件(父)不是很关心,就可以使用NestedScrollingParentHelper来方便编程。
借用网上的一张图,可以看到两个接口之间的对应关系:
图片来源:https://segmentfault.com/a/119000000287365
2
实例演示:改造SwipeRefreshLayout
(1)目的
SwipeRefreshLayout就是一个实现了NestedScrolling机制的控件,可以方便的实现下拉刷新。
现在我们想加上上拉刷新功能,可以反着做,给下方加一个可拉动的控件(小圆圈),然后处理它的滑动事件。为了能兼顾上层,我们再外面还加了常规的CoordinatorLayout和AppBarLayout作为测试。
成果:
(2)准备工作
改造之前当然要先把人家之前的成果准备好。
首先当然是把我们的SwipeRefreshLayout移过来,可以换一个名字避免以后的冲突。
然后布局中要用到两个控件,一个是CircleImageView,也就是显示的小圆圈,附带阴影功能;一个是MaterialProgressDrawable,用于在CircleImageView显示进程(颜色滑动)。
在移动的时候SwipeRefreshLayout会报错,报错时把东西移动过来就行了。
然后阅读源码,我们暂时只处理NestedScrolling机制,所以一般的移动流程比如onTouchEvent可以先放着,以后为了和其他不带NestedScrolling控件兼容的时候再改进。
(3)开始改造工作
**1.参数测量 **
一个主要的问题就是,我们下面的圆圈(加载圈,以下简称圆圈)要放在哪?
原生的直接放在中间然后上去一个圆圈身位的地方,所以下面圆圈水平位置一样,竖直的话就放在屏幕下方。
综合测试出这样的距离比较好:
所以相应的位置放置我们就这样:
2.作为父控件配合滑动
a.是否配合滑动
这里我们只增加了一个上拉刷新标志位
这里也是,只是增加了mTotalUnconsumedBottom ,这是我们上拉刷新的未消费路程。
b.滑动之前
我们可以看到源码中的onNestedPreScroll有这么一段处理:
在滑动前先判断,我们未消费滑动路程是否还有,有则判断方向,如果是滑动的反方向,也就是我们再下拉刷新一半的时候又往回拉,这时做出处理,选择消费当前滑动路程。
所以我们可以写出下方的拖动预处理:
这里有个坑要提醒下,一开始笔者自作聪明,觉得consumed参数应该传绝对值,导致后来往回滑的时候子控件跑得飞快(可以想想为什么),所以这里消费了负的路程就传回负的路程,可以看看源码中NestedScrollingChildHelper的实现。
c.正式滑动
这个反而比较容易,只需要加上判断当前子控件还能不能往上滑。
滑动处理:
大概解释一下,我们的滑动时分段的,在mTotalDragDistance滑动之前是线性的,在这之后会做一个加速的处理,最多延伸一个mSpinnerOffsetEnd
在实际的view位置改变中,我们使用的是
setTargetOffsetTopAndBottomForBottom (targetY-mCurrentTargetOffsetBottom, true);
这个方法,里面是采用
ViewCompat.offsetTopAndBottom
来改变view的位置的。
d.结束滑动
如果手指离开的时候,拖动距离不为零,那么我们要做判断,做出相应的处理
可以看到主要有两种处理,以mTotalDragDistance为界限,超过这个滑动距离我们就显示刷新,没有的话就动画回到原点。
**3.作为子控件配合滑动 **
我们知道,AppBarLayout和CoordinatorLayout会配合滑动,子view往上滑的时候会隐藏,如果不做处理,在下端圆圈滑动到一半的时候往回滑会把AppBar又拖出来,消费滑动事件,所以我们选择拦截,在下部圆圈滑动的时候优先处理滑动:
到这里,我们的控件就基本完成了,当然谷歌出版的控件,动画效果是不能少的,它的美观也体现在这里,由于基本是能模仿的,所以这里不再多讲。这个控件的改造主要麻烦在它的动画衔接以及滑动处理机制(加速等),剩下的都很好理解,建议大家动手试一试。
项目地址:
https://github.com/SGZoom/DailyWidget/tree/master/widgetpro
文章跟代码可能包含很多不足之处,欢迎大家帮忙指出错误与不足,谢谢~