Android 优化交互 —— CoordinatorLayout 与 Behavior
zzcl219534
8年前
<h2>前言</h2> <p>如果你已经很时髦的用上了 AppBar , TabLayout , FloatActionButton ,以及 Snackbar 的话,我想你多多少少肯定知道 CoordinatorLayout 这个东西。 它的神秘感来自于在布局文件 (xml) 和代码调用上完全看不出和其他组件任何的耦合,却能做出一些神奇酷炫的交互效果。</p> <p><img src="https://simg.open-open.com/show/f8f30e0ad773d077e8995c6fffe68896.png"></p> <p>对,没错,今天我们就着重讲一下 CoordinatorLayout 是如何工作的,或者说,是如何让别的组件亲密无间的“合作”起来的(以及,顺便会打下一点点的广告)。</p> <h2>CoordinatorLayout 到底是干嘛的?</h2> <p>顾名思义, CoordinatorLayout 专注于把它的子View连接起来,使他们之间相互很好的配合。</p> <p>那么既然是合作, CoordinatorLayout 的职责充当了一个第三方的角色,通知各个子View之间状态的变换,的确,它也只干了这么一件事,非常纯洁&纯粹。</p> <p>那么既然有通知,一定需要媒介,总不能把子View全部改造成适合现在这种模式的模样吧?这样也太不OO了,这里的介质就是 Behavior , Behavior 是 CoordinatorLayout 用来和各个子 View 通信用的代理类。</p> <h2>来自Behavior的代理 —— 布局</h2> <p><img src="https://simg.open-open.com/show/6bc1f48e3f94325695cb9f5f07a163c5.png"></p> <p>注意箭头的走向, CoordinatorLayout 是通过 Behavior 去控制子视图,也就代表 Behavior 的数据传导基本上是单向的。当 CoordinatorLayout 需要进行 measure , layout 的时候,都会通过 behavior 询问子视图,是否需要进行相应的操作,如果不需要,就进行默认的行为,我们来看下 onLayoutChild 和 onMeasureChild 两个再熟悉不过的行为。</p> <p><img src="https://simg.open-open.com/show/338585a703c2faf0a546c7cbb3bc60fc.png"></p> <p><img src="https://simg.open-open.com/show/41f945a52f2e6f7913df6ab527427c53.png"></p> <p>可以看 @return 的说明,如果 Behavior 处理了相关的操作,那么就会覆盖 CoordinatorLayout 默认的行为(其实它的默认行为和FrameLayout简直一模一样)</p> <p>这一节简单的说了 CoordinatorLayout 如何通过 Behavior 来控制子View的布局相关的行为,接下来我们看看重点的 交互 部分。</p> <h2>来自Behavior的代理 —— 触摸事件之普通流程</h2> <p>CoordinatorLayout 的功能当然不仅仅是通过 Behavior 来控制子视图的布局,控制触摸的流程才是大头。</p> <p>首先我们知道,控制触摸事件,一般有2个:</p> <ol> <li> <p>onInterceptTouchEvent.</p> </li> <li> <p>onTouchEvent</p> </li> </ol> <p>这里不解释他们之间的区别,我们看到在 Behavior 中也有这么两个方法。</p> <p><img src="https://simg.open-open.com/show/d47a5bfb3f9fe69968adf2f6e353fbd0.png"></p> <p><img src="https://simg.open-open.com/show/98a70481b35d1193c586406941ecc5f5.png"></p> <p>如果你的View所拥有的 Behavior 处理了相关的事件,那么接下去发到 CoordinatorLayout 上的触摸事件就会像正常流程一样发到这个 Behavior 中。</p> <p>我们终于可以实现在不子类化 View 的情况下,重写它的触摸事件啦。</p> <h2>来自Behavior的代理 —— 触摸事件之 NestedScrolling</h2> <p>这才是重点中的重点啊!!首先,我们来睁大眼睛看!</p> <p><img src="https://simg.open-open.com/show/b64f19e14e9c1033fb07f67297c8f95a.png"></p> <p>好,可以看见 CoordinatorLayout 是实现了 NestedScrollingParent 接口的,也就是说,要用到这个特性的话,默认不实现 NestedScrollingChild 接口 (pre Lollipop) 且不调用 dispatchNestedScroll 相关接口的 View 靠一边去! 【 ListView 哭晕在厕所】</p> <p>CoordinatorLayout 正是从 NestedScrollingParent 相关的接口中,获取到嵌套滚动相关的参数,再通过 Behavior 传到各个子 View 中,包含 Behavior 的 View 这时候才成为真正处理嵌套滚动的对象,消费掉一些滚动参数后,再把消费掉的数值传回到发生触摸事件的 View 中,达到交互的目的。</p> <p><img src="https://simg.open-open.com/show/c2b42ecd01a799a9b42d1cfb251c1718.png"></p> <p>consumed 这个数组可以在 View0 中获取到,表示的意思是它的 NestedScrollParent 消费了多少的滑动量,意味着它能使用的滑动量要减去数组里的值。</p> <p>这样产生滑动的 View 就通过 CoordinatorLayout 和 其他的 View 的 Behavior 产生了交互,我们可以在 Behavior 中给 View 生成一些位置的偏移量,达到视图上移动的效果。</p> <h2>来自Behavior的代理 —— layoutDependency</h2> <p>布局依赖,这也是个很重要的东西,比如 FAB 的位置需要在 Snackbar 上边,需要依赖它来操作。</p> <p>主要有2个接口(用的比较少的暂时忽略):</p> <ol> <li> <p>layoutDependsOn</p> </li> <li> <p>onLayoutDependencyChanged</p> </li> </ol> <p>第二个接口通常在 onPreDraw 或者 onNestedScroll 系列的回调中 最后进行调用, <strong> 注意:它并不是在 onLayout 过程中回调的 </strong></p> <p>第一个很显然是告诉 CoordinatorLayout ,一个View是否依赖于另一个View。</p> <p>第二个是 CoordinatorLayout 发现存在依赖的时候,把 被依赖方 回调给 依赖方 , 因为这时候, layout 已经完成,我们可以获取 被依赖方 的所有布局信息,根据布局信息,使用 offset 来决定依赖方的一些位置; 同时在这个时候,你也可以调用 requestLayout 进行重新布局。</p> <h2>最后的最后</h2> <p>好了, Guang Gao Time:</p> <p>SegmentFault for Android 新版在奋力开发中,带着对 Material Design 的执着,强势归来!</p> <p><img src="https://simg.open-open.com/show/e0313f1f4834e46e1777976a43a72426.gif"></p> <p>欢迎关注我 <a href="/misc/goto?guid=4959641016432625944" rel="nofollow,noindex">Github</a> 以及@Gemini</p> <p>来自: <a href="/misc/goto?guid=4959671800547294684" rel="nofollow">https://segmentfault.com/a/1190000005024216</a></p>