CoordinatorLayout-AppBarLayout-CollapsingToolbarLayout复杂滑动逻辑源码解读

892 阅读9分钟

文章中基本不会贴具体代码,只会提供相关方法,以及自己理解,有需要更具体了解或者帮助消化的同学,自己查看源码即可

1、前言

滑动对于android来说,是一个必不可少;它不复杂,大家都知道在onTouchEvent中,让它滑动就完事了,说它复杂,其嵌套处理复杂;在本系列文章,最终是为了熟悉各种滑动;对于滑动,分为下面几篇文章来完成解读:

在本章内,本章从四个类分析(着重分析前三个类)

  1. CoordinatorLayout
  2. Behavior
  3. AppBarLayout
  4. CollapsingToolbarLayout

这是一种新的体系;通过增加Behavior布局属性,动态改变视图对事件的拦截处理,以及相互之间对同一滑动事件的处理;这种Behavior的处理是嵌套在事件分发机制和嵌套滑动机制中;

其次我们要有一个认知,这个新的滑动体系,是CoordinatorLayout进行架构的,通过Behavior可以实现各种不同的效果;而AppBarLayout为头部整体滑动做了架构,而CollapsingToolbarLayout依赖于AppBarLayout生存而实现折叠头部的一种方式

2、CoordinatorLayout类

其实嵌套滑动,机制都在这个类中,其他类都在这个机制中进行自己独特的定制;所以你可以把它当成总纲;其中Behavior也是其内部类,对滑动进行定制细节规定。

而此实现协调布局总纲和具体实现的分离,就是充分利用LayoutParam这个view属性,增加Behavior类进行定制;作者觉得很妙。

此类中,与滑动相关有一下几个过程

  • 布局、绘制过程:其中对依赖关系的处理,使滑动十分的顺滑
  • 事件分发过程:增加了Behavior拦截处理机制,也就是Behavior可以承包视图的事件处理,增加了已有视图对事件处理的极大灵活性
  • 嵌套滑动接口:子类各个Behavior属性,完全独立消费相关事件;
  • 绘制前监听、视图移除监听:对视图进行可见位置调整,这个其实应该放在测量、布局、绘制过程中

这里有一个概念:anchorView,我直译锚点视图,也就是其它视图相对于它来放置,也可以锚点视图变化,依赖其的视图会跟随变化

  1. prepareChildren方法,在测量过程中被调用,处理锚点依赖关系,通过DirectedAcyclicGraph类生成一个可以直接处理布局的顺序;下面会详细介绍DirectedAcyclicGraph
  2. ensurePreDrawListener,并不是只有这一个方法,在view的生命周期(onAttachedToWindow、onDetachedFromWindow)中也进行了处理;也就是如果存在锚点依赖,则进行绘制前监听,否则不会;这个是为了依赖视图的移动或者top或者left变动,而调整了依赖其的视图变动,使视图时刻保持正确锚点依赖;
  3. onMeasure,就可以简单理解为一个不限制高度的帧视图;还有一种依赖方式,是依赖垂直与x轴的线的方式,由于与滑动没有什么关系,简单理解,就是线(我称为锚点线)的位置+anchorGravity属性,来决定视图在线的什么位置;Behavior的onMeasureChild方法优先于孩子视图的默认测量方法
  4. onLayout,处理中Behavior中onLayoutChild,由于容器自身对孩子容器的测量;容器对孩子测量分为3中情况:a)无锚点视图、锚点线,帧布局放置,b)存在锚点视图,则根据锚点视图位置+gravity+anchorGravity来布局,c)存在锚点线,其优先级低于锚点视图,根据锚点线位置+gravity+anchorGravity来布局
  5. onDraw,标准的处理,并在最后,可能在状态栏绘制图片,图片来源于属性statusBarBackground设置
  6. 事件分发方法:增加哪个Behavior进行拦截,哪个Behavior进行消费的逻辑
  7. 嵌套滑动方法:每个Behavior都是个独立"父容器",独立处理,但是消费进行最大统计
  8. parseBehavior:Behavior解析方法,通过属性配置的方式layout_behavior,字符串,根据字符串的不同,可分为a)以.开头,相对当前应用的包名的类名字,b)包含.则是类全名,c)仅仅简单类名,包与CoordinatorLayout相同;然后根据类的全名称进行反射得到
  9. getResolvedLayoutParams,如果xml没有配置,则当前类如果继承CoordinatorLayout.Behavior,则其设置为属性Behavior值,否则以CoordinatorLayout.DefaultBehavior注解的类为Behavior

DirectedAcyclicGraph类

存在下面变量:

  • mGraph:是一个SimpleArrayMap,key表示锚点节点,value为ArrayList表示依赖锚点的节点
  • mSortResult:排序好的节点;不存在循环依赖,且节点所依赖的锚点不在其前面

排序思想

mGraph中key值对应的数组为空或者其数据已经加入到排序中,则把当前key节点放入排序数组中;另外使用标记拒绝循环依赖,也就是避免此算法会不停止

2、Behavior类

CoordinatorLayout内部,静态抽象类;从CoordinatorLayout类的分析,Behavior主要有下面角色

  • 优先于视图本身的测量布局权利
  • 优于ViewGroup中的事件分发流程
  • 给与子视图处理其他子视图传过来的嵌套滑动事件的权利

那么具体的那些方法有这些用途,Behavior方法命名已经很明确了

Behavior定义的每一个方法在CoordinatorLayout机制中已经赋予固定的意义,但是系统也为我们提供了一些基础的实现类;

ViewOffsetBehavior

通过ViewOffsetHelper记录了视图的top,left初始位置,以及滑动偏移,并根据滑动偏移修改left,top值,也即是视图的绘制区域;并未对滑动进行实际处理

HeaderBehavior

继承ViewOffsetBehavior,并增加了一下功能

  • 只对y轴移动处理
  • 增加此view滑动事件处理能力,并根据滑动进行移动和惯性处理
  • 手指处理接力形式

其仅仅增加view处理自身滑动事件的能力,而未处理嵌套滑动时,而从CoordinatorLayout中是可以拿到嵌套滑动处理权的;此类的意图就是为界面顶部布局的一个基础Behavior,也可以看出作者认为顶部视图自身事件也就是移动,而嵌套事件却可以不仅仅是移动,因此也就没有处理,其实CollapsingToorbarLayout就是这样来的一种情况

3、AppBarLayout类

继承线性布局;整体来说,其必须是锚点,这是因为此类在向上滑动可能优先消耗滑动事件,如果不是锚点就会导致布局的不连贯性;第二点就是子view布局属性layout_scrollFlags的不同,造成了不同的效果,但是必须有scroll标志,否则其它flag不起作用;

layout_scrollFlags

  • scroll:嵌套滑动必备值,否无嵌套滑动处理;下面几个flg说明中的滑动均是嵌套滑动过程的滑动
  • exitUnitilCollapsed:向上滑动时,可滑动距离为视图高度-视图最小高度;向下滑动时,可滑动距离为视图高度-视图最小高度
  • enterAlways:向下滑动优先滑动整个视图的高度,与exitUnitCollapsed一起时,按照exitUnitCollapsed计算,与enterAlwaysCollapsed一起时按照enterAlwaysCollapsed计算
  • enterAlwaysCollapsed:与enterAlways配合生效,此时优先滑动视图的最小高度
  • snap:滑动停止时视图会自动完全展示或者完全不展示;依赖于展示部分比例,如果展示比例超过了3/4则自动完全展示,否则小时
  • snapMargins:配合snpa一起用,展示时视图上方其topMargins高度一起展示,隐藏时视图下方bottomMargin值高度一起隐藏

因为AppBarLayout必须成为锚点,所以,其提供了其默认成为锚点的Behavior,其内部类ScrollingViewBehavior

因为AppBarLayout仅仅对整理进行移动,但提供了子视图可展示区间也提供了子视图变化自控,因此在其布局位置移动的时候,提供了监听机制,a)增加监听方法addOnOffsetChangedListener,b)移除监听方法removeOnOffsetChangedListener;利用此类机制的都应该在view生命周期开始到结束时进行监听。

4、CollapsingToolbarLayout类

从上面分析可知,CollapsingToobarLayout类,只是通过监听AppBarLayout的变化而进行自身布局绘制调整而带来的折叠效果; 从代码分析,其折叠效果其实是文字绘制大小和位置的变化,这个效果必须满足一下条件

  1. 子view必须有"Toolbar"视图;这个视图有下面三种情况:a)layout_toolbarId配置某个子ViewGroup的id,b)直接子view为androidx.appcompat.widget.Toolbar子类,c)直接子view为android.widget.Toobar的子类
  2. layout_toobarId设置时,提供title属性或者本身为Toobar的子类;layout_toobarId未设置时,Toobar子类直接子view存在时,必须设置title或者CollapsingToobarLayout设置title属性
  3. layout_titleEnabled属性设置为true,其默认为true无需配置

这个类能做到的折叠只是文字折叠,但其提供给我们思路做更多神奇的事情。

5 小结

本章缺少了一些图,比如锚点布局到底怎么回事,视图依赖排序算法是怎么回事,还有很多过程怎么来的;但是作者是把相关代码熟读后,并进行分析把各个细节点想明白后,进行的总结归纳;这样肯定会有些知识点会遗漏,如果读者有什么不懂得地方,可以随时留言联系我,也可以通过微信(weixin-897283086)联系我进行解答,请备注好渠道+问题

如果在此文章中您有所收获,请给作者一个鼓励,点个赞,谢谢支持

技术变化都很快,但基础技术、理论知识永远都是那些;作者希望在余后的生活中,对常用技术点进行基础知识分享;如果你觉得文章写的不错,请给与关注和点赞;如果文章存在错误,也请多多指教!

上一篇:NestedScrollView嵌套滑动源码解读