目录
android的协调滚动的几种实现方式
,我们讲了嵌套滚动的实现方式,为什么有了嵌套滚动还需要协调滚动这种方式呢?(不细讲原理,本文只探讨实现的方式与步骤!)
那在一些细度化的操作中,如我们需要一些控件随着滚动布局做一些粒度比较小的动画、移动等操作,那么我们就需要监听滚动,然后改变当前控件的属性。
如何实现这种协调滚动的布局呢?我们使用 coordinatorlayout appbarlayout 或者 coordinatorlayout behavior 实现,另一种方案是 motionlayout。我们看看都是怎么实现的吧。
一、coordinatorlayout behavior
coordinatorlayout 顾名思义是协调布局,其原理很简单,在onmeasure()的时候保存childview,通过 predrawlistener监听childview的变化,最终通过双层for循环找到对应的behavior,分发任务即可。coordinatorlayout实现了nestedscrollingparent2,那么在childview实现了nestedscrollingchild方法时候也能解决滑动冲突问题。
而behavior就是一个应用于view的观察者模式,一个view跟随者另一个view的变化而变化,或者说一个view监听另一个view。
在behavior中,被观察view 也就是事件源被称为denpendcy,而观察view,则被称为child。
一般自定义behavior来说分两种情况:
- 监听另一个view的状态变化,例如大小、位置、显示状态等
- 监听coordinatorlayout里的滑动状态
这里我们以之前的效果为主来实现自定义的behavior,先设置nestedscrollview在imageview下面:
public class myscrollbehavior extends viewoffsetbehavior{ private int topimgheight; private int toptextheight; public myscrollbehavior(context context, attributeset attrs) { super(context, attrs); } @override public boolean layoutdependson(@nonnull coordinatorlayout parent, @nonnull nestedscrollview child, @nonnull view dependency) { return dependency instanceof imageview ; } @override protected void layoutchild(coordinatorlayout parent, nestedscrollview child, int layoutdirection) { super.layoutchild(parent, child, layoutdirection); if (topimgheight == 0) { final list dependencies = parent.getdependencies(child); for (int i = 0, z = dependencies.size(); i < z; i ) { view view = dependencies.get(i); if (view instanceof imageview) { topimgheight = view.getmeasuredheight(); } } } child.settop(topimgheight); child.setbottom(child.getbottom() topimgheight); } }
然后设置监听coordinatorlayout里的滑动状态,imageview做同样的滚动
public class myimagebehavior extends coordinatorlayout.behavior{ private int topbarheight = 0; //负图片高度 private int downendy = 0; //默认为0 public myimagebehavior(context context, attributeset attrs) { super(context, attrs); } @override public boolean onstartnestedscroll(@nonnull coordinatorlayout coordinatorlayout, @nonnull view child, @nonnull view directtargetchild, @nonnull view target, int axes, int type) { //监听垂直滚动 return (axes & viewcompat.scroll_axis_vertical) != 0; } @override public void onnestedprescroll(@nonnull coordinatorlayout coordinatorlayout, @nonnull view child, @nonnull view target, int dx, int dy, @nonnull int[] consumed, int type) { if (topbarheight == 0) { topbarheight = -child.getmeasuredheight(); } float transy = child.gettranslationy() - dy; //处理上滑 if (dy > 0) { if (transy >= topbarheight) { translationbyconsume(child, transy, consumed, dy); translationbyconsume(target, transy, consumed, dy); } else { translationbyconsume(child, topbarheight, consumed, (child.gettranslationy() - topbarheight)); translationbyconsume(target, topbarheight, consumed, (child.gettranslationy() - topbarheight)); } } if (dy < 0 && !target.canscrollvertically(-1)) { //处理下滑 if (transy >= topbarheight && transy <= downendy) { translationbyconsume(child, transy, consumed, dy); translationbyconsume(target, transy, consumed, dy); } else { translationbyconsume(child, downendy, consumed, (downendy - child.gettranslationy())); translationbyconsume(target, downendy, consumed, (downendy - child.gettranslationy())); } } } @override public boolean onnestedfling(@nonnull coordinatorlayout coordinatorlayout, @nonnull view child, @nonnull view target, float velocityx, float velocityy, boolean consumed) { return super.onnestedfling(coordinatorlayout, child, target, velocityx, velocityy, consumed); } private void translationbyconsume(view view, float translationy, int[] consumed, float consumeddy) { consumed[1] = (int) consumeddy; view.settranslationy(translationy); } }
分别为imageview和nestedscrollview设置对应的 behavior。
我们先把textview隐藏先不处理textview。效果如下:
这样我们就实现了自定义 behavior 监听滚动的实现。那么我们加上textview 的 behavior 监听imageview的滚动,做对应的滚动。
先修改 myscrollbehavior 让他在imageview和textview下面
public class myscrollbehavior extends viewoffsetbehavior{ private int topimgheight; private int toptextheight; public myscrollbehavior(context context, attributeset attrs) { super(context, attrs); } @override public boolean layoutdependson(@nonnull coordinatorlayout parent, @nonnull nestedscrollview child, @nonnull view dependency) { return dependency instanceof imageview || dependency instanceof textview ; } @override protected void layoutchild(coordinatorlayout parent, nestedscrollview child, int layoutdirection) { super.layoutchild(parent, child, layoutdirection); if (topimgheight == 0) { final list dependencies = parent.getdependencies(child); for (int i = 0, z = dependencies.size(); i < z; i ) { view view = dependencies.get(i); if (view instanceof imageview) { topimgheight = view.getmeasuredheight(); } else if (view instanceof textview) { toptextheight = view.getmeasuredheight(); view.settop(topimgheight); view.setbottom(view.getbottom() topimgheight); } } } child.settop(topimgheight toptextheight); child.setbottom(child.getbottom() topimgheight toptextheight); } }
然后设置监听imageview的滚动:
public class mytextbehavior extends coordinatorlayout.behavior{ private int imgheight; public mytextbehavior(context context, attributeset attrs) { super(context, attrs); } @override public boolean layoutdependson(@nonnull coordinatorlayout parent, @nonnull view child, @nonnull view dependency) { return dependency instanceof imageview; } @override public boolean ondependentviewchanged(@nonnull coordinatorlayout parent, @nonnull view child, @nonnull view dependency) { //跟随imageview滚动,imageview滚动多少我滚动多少 float translationy = dependency.gettranslationy(); if (imgheight == 0) { imgheight = dependency.getheight(); } float offsettranslationy = imgheight translationy; child.settranslationy(offsettranslationy); return true; } }
xml修改如下:
ok,修改完成之后我们看看最终的效果:
看到上面的示例,我们把常用的几种 behavior 都使用了一遍,系统的viewoffsetbehavior 和监听滚动的 behavior 监听view的 behavior。
为了实现这么一个简单的效果就用了这么多类,这么复杂。我分分钟就能实现!
行行,我知道你厉害,这不是为了演示同样的效果,使用不同的方式实现嘛。通过 behavior 可以实现一些嵌套滚动不能完成的效果,比如鼎鼎大名的支付宝kb88凯时d88尊龙官网手机app官网登录首页效果,美团详情效果等。behavior 更加的灵活,控制的粒度也更加的细。
但是如果只是简单实现上面的效果,我们可以用 appbarlayout 内部自带的 behavior 也能实现类似的效果,appbarlayout内部已经封装并使用了 behavior 。我们看看如何实现。
二、coordinatorlayout appbarlayout
其实内部也是基于 behavior 实现的,内部实现为 headerbehavior 和 headerscrollingviewbehavior 。
对一些场景使用进行了封装,滚动效果,吸顶效果,折叠效果等。我们看看同样的效果,使用 appbarlayout 如何实现吧:
效果:
so easy ! 真的是太方便了,类似的效果我们都能使用 appbarlayout 来实现,比如一些详情页面顶部图片,下面列表或viewpager的都可以使用这种方式,更加的便捷。
三、motionlayout
不管怎么说,appbarlayout 只能实现一些简单的效果,如果想要一些粒度比较细的效果,我们还得使用自定义 behavior 来实现,但是它的实现确实是有点复杂,2019年谷歌推出了 motionlayout 。
淘宝的出现可以说让世上没有难做的生意,那么 motionlayout 的出现可以说让 android 没有难实现的动画了。不管是动画效果,滚动效果,motionlayout 绝杀!能用 behavior 实现的 motionlayout 几乎是都能做。
使用 motionlayout 我们只需要定义起始点和结束点就行了,我们这里不需要根据百分比fram进行别的操作,所以只定义最简单的使用。
我们看看如何用 motionlayout 实现同样的效果:
定义的scene_scroll_13.xml
效果:
非常的简单,效果很流畅,性能也很好。有时候都不得不感慨一句,有了 motionlayout 要你 behavior 何用。
总结
android真的是太卷了,以前学rxjava dagger2 nestedscrolling behavior 等,这些都是很难学的,更难以应用,如果能学会,那都是高工了。现在谷歌新框架层出不穷,越来越易用了,越来越好入门了。以前学的都已经被淘汰,新入android的同学已经可以无需门槛,直接学谷歌的脚手架就能完成效果了。
言归正传,这几种方案大家都理解了吗?什么时候需要用协调滚动,什么时候需要用嵌套滚动,大家可以做到心中有数。能用 motionlayout 的还是推荐使用 motionlayout 实现,毕竟实现简单,性能优秀嘛!
当然如果仅限这种效果来说,还有很多的方式实现如rv listview,纯粹的自定义view也能实现是吧,自定义viewgroup,viewdraghelper一样能实现,就是稍微麻烦点,这里也仅从嵌套滚动和协调滚动这点来实现的。
好了,如果大家理解了协调滚动和嵌套滚动,那万变不离其宗,几乎应用开发中全部的滚动效果都是基于这两条,内部的具体实现方案几乎都是基于这6种方案来实现。
后面如果大家有兴趣,我会出一期超复杂的嵌套具体实现相关的功能,类似美团外卖点餐的页面分为上、中、下布局。下布局又分左右列表布局 ,还分上布局抽屉效果和中布局吸顶效果。