深入理解Android开发中的CoordinatorLayout Behavior
在使用Android设计支持库(Android Design Support Library)时,很难避开CoordinatorLayout:设计库中有很多视图都需要CoordinatorLayout的支持。为什么呢?实际上CoordinatorLayout本身所做的事情并不多,要是在标准框架视图中使用它,结果也就跟普通的FrameLayout差不多。那么奇迹来自何处呢?完全是由于CoordinatorLayout.Behaviors的存在。只要将Behavior绑定到CoordinatorLayout的直接子元素上,就能对触摸事件(touch events)、window insets、measurement、layout以及嵌套滚动(nested scrolling)等动作进行拦截。Design Library的大多功能都是借助Behavior的大量运用来实现的。
创建Behavior
创建behavior非常简单:使用extend Behavior就可以了。
public class FancyBehavior<V extends View>
extends CoordinatorLayout.Behavior<V> {
/**
* Default constructor for instantiating a FancyBehavior in code.
*/
public FancyBehavior() {
}
/**
* Default constructor for inflating a FancyBehavior from layout.
*
* @param context The {@link Context}.
* @param attrs The {@link AttributeSet}.
*/
public FancyBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
// Extract any custom attributes out
// preferably prefixed with behavior_ to denote they
// belong to a behavior
}
}
注意: 这里绑定了泛型类型,也就是说,可以将FancyBehavior绑定到任意视图类上。不过,如果只想将Behavior绑定到特定种类的视图上,就可以用这段代码:
public class FancyFrameLayoutBehavior
extends CoordinatorLayout.Behavior<FancyFrameLayout>
这样一来,当从视图收到方法调用时,就无需再费神将大量参数转到正确的子类中了,简单又便捷。
使用Behavior.setTag()/Behavior.getTag() 可以保存临时数据, 使用onSaveInstanceState()/onRestoreInstanceState()还可以保存Behavior相关的实例状态。虽然笔者建议要保证Behavior尽可能轻量级,不过这些方法可以让Behavior更具状态性。
关联Behavior
当然,Behavior无法独立完成工作,必须与实际调用的CoordinatorLayout子视图相绑定。具体有三种方式:通过代码绑定、在XML中绑定或者通过注释实现自动绑定。
通过代码绑定Behavior
如果将Behavior当作绑定到CoordinatorLayout中每个视图的附加数据,那么发现Behavior实际上是存储在各个视图的LayoutParams中也就不足为奇了(之前有关于布局的博文)。也是因此,Behavior需要绑定到CoordinatorLayout的直接子项中,因为只有那些子项会包含LayoutParams的特定Behavior子类。
FancyBehavior fancyBehavior = new FancyBehavior();
CoordinatorLayout.LayoutParams params =
(CoordinatorLayout.LayoutParams) yourView.getLayoutParams();
params.setBehavior(fancyBehavior);
在这种情况下,我们会使用默认的无参数构造函数,不过这并不代表有参数的构造函数就不能用了,反正想用代码编写什么都没有限制。
在XML中绑定Behavior
当然,每次都用代码绑定的话总是有些麻烦,正如大多自定义的LayoutParams一样,完成这件工作也有相应的layout_ 属性,这里是layout_behavior属性:
<FrameLayout
android:layout_height=”wrap_content”
android:layout_width=”match_parent”
app:layout_behavior=”.FancyBehavior” />
与代码绑定不同,这里调用的总是FancyBehavior(Context context, AttributeSet attrs) 构造函数。此外还能声明任何自定义属性,并将其从XML AttributeSet中提取出来,如果想要赋予开发者通过XML自定义Behavior的功能,这一点非常重要。
注意: 与父类负责解析与诠释的Layout_属性的命名规则相类似,在Behavior中我们使用behavior_作为属性前缀。
自动绑定Behavior
如果构建了需要自定义Behavior的自定义视图(就像Design Library中很多组件中那样),也许你会想要默认绑定某个behavior,而无需每次手动在代码中或XML中指定。为了达到这个目的,只需在自定义视图顶层添加简单的注释:
@CoordinatorLayout.DefaultBehavior(FancyFrameLayoutBehavior.class)
public class FancyFrameLayout extends FrameLayout {
}
这样,默认的构造函数就会调用Behavior,与使用代码绑定非常类似。注意:目前任何layout_behavior代表的属性都会重写DefaultBehavior。
拦截触摸事件
一旦将所有behavior设置完毕,就可以准备实际开工了。Behavior能做的事情之一包括拦截触摸事件。
不用CoordinatorLayout时,一般会使用各个ViewGroup的子类,Managing Touch Events training一文有提到过这个问题。不过有了CoordinatorLayout,通过Behavior的onInterceptTouchEvent(),将调用传递给它的onInterceptTouchEvent(),让Behavior获得拦截触摸事件的机会。通过返回为true,那么Behavior会通过onTouchEvent()接收后续的所有触摸事件,而且无需视图了解后续情况。SwipeDismissBehavior就是通过这样的方式在视图中执行任务的。
不过更严重的触摸拦截就是拦截任何交互,只要在blocksInteractionBelow()中返回true就会出现这样的情况。当然,在互动被拦截时也许你会希望有些视觉信号提示(以免使用者以为应用完全不能用了)——这就是为什么blocksInteractionBelow()的默认功能实际上依赖于getScrimOpacity()值——返回非零值会为视图提供一层颜色遮罩(用getScrimColor()来确定颜色,默认为黑),并立即禁用所有的触摸互动。非常方便。
拦截window insets
假设本文读者已经看过Why would I want to fitsSystemWindows一文, 在该文中我们就fitsSystemWindows的实际作用做了深入探讨,不过可归结为:window insets需要避免在系统窗口(比如状态栏和导航栏)之下出现。这里Behavior也能发挥作用:如果视图为fitsSystemWindows=“true”,则onApplyWindowInsets()会调用绑定Behavior,且优先级高于视图自身。
注意: 大多情况下,如果Behavior没有消耗掉整个window insets,则应当通过ViewCompat.dispatchApplyWindowInsets() 来传递这个insets,以确保视图的任何子项有机会看到这个WindowInsets。
拦截Measurement和Layout
Measurement和layout是Android绘制视图的关键组件,因此Behavior只有在onMeasureChild()和onLayoutChild()回调前拦截父视图的measurement和layout,才能达到预计的效果。
例如:我们采用泛型ViewGroup并为其添加一个maxWidth:
编写适用所有项目的通用Behavior非常有用,不过切记:尽量考虑在应用内使用behavior的办法,这样会让应用更为简单。(并非所有Behavior都应当是泛型的!)
理解视图间的依赖
上述所有功能都仅需要单个视图便可实现。不过Behavior的强大之处源自构建视图间的依赖,也就是说:当另一个视图改变时,你的Behavior会获得回调,根据外部情况来变更自身功能。
Behavior在两种情况下会成为视图的依赖:一种是将Behavior相应的视图锚定在另一个视图上时(隐性依赖),还有一种是在layoutDependsOn()中明确返回true时。
在视图中使用CoordinatorLayout的layout_anchor属性,就能起到锚定的作用。与layout_anchorGravity属性一同使用,就能将两个视图一并有效地固定在某个位置上。例如:可以将FloatingActionButton锚定到AppBarLayout上,而在AppBarLayout滚动出屏幕时,FloatingActionButton.Behavior就会通过隐性依赖将自身隐藏起来。
无论哪种情况,当依赖视图被移除时,Behavior会获得onDependentViewRemoved()的回调;而只要依赖视图出现变更,Behavior就会获得onDependentViewChanged()的回调(即调整大小或自身位置)。
将视图固定在一起的能力正是Design
Library实现诸多炫酷功能的办法——比如FloatingActionButton与Snackbar之间的互动。FAB的Behavior依赖于添加到CoordinatorLayout上的Snackbar实例,再通过onDependentViewChanged()回调将FAB向上移动,避免遮住Snackbar。
注意: 在添加依赖时,视图总是会在依赖视图布局后进行布局,无视子项次序。
嵌套滚动
说到嵌套滚动,有详细介绍它的相关文章,在本文中笔者只做粗浅概述。需要牢记这几件事:
- 无需在嵌套滚动视图中声明依赖,因为CoordinatorLayout的每个子项都有可能接收到嵌套滚动事件。
- 嵌套滚动不仅可以在CoordinatorLayout的直接子项中发起,也能在任何子视图(比如CoordinatorLayout的子项的子项的子项中)发起。
- 虽然我们称之为嵌套滚动,不过实际上包括滚动(按照滚动做1:1的位移)与滑动(flinging)两种动作。
因此,通过onStartNestedScroll()来发起感兴趣的嵌套滚动事件吧。收到滚动轴(例如横向或纵向——使它容易忽略在特定方向上的滚动)后,必须在该方向上返回true,以获得随后的滚动事件。
在向onStartNestedScroll()返回true之后,嵌套滚动分两步运行:
- onNestedPreScroll()在滚动视图获得滚动事件前运行,允许相应Behavior消耗一部分或所有的滚动事件(最后消耗的int[]是一个“外部”参数,在其中指明消耗掉的滚动)。
- 滚动视图在滚动后会调用onNestedScroll(),可以知道滚动了多少view,未消耗掉的(overscroll)数量又有多少。
还有类似滑动操作(尽管pre-fling调用必须要么消耗掉所有的滑动,要么不消耗滑动——没有部分消耗的选项)。
在嵌套滚动(或滑动)停止后,就能获得onStopNestedScroll()的调用。这表示滚动结束:在下一个滚动开始前,等待重新调用onStartNestedScroll()。
举个例子:如果想要在向下滚动时隐藏FloatingActionButton,并在向上滚动时显示它,只用重写onStartNestedScroll() 和onNestedScroll(),就像在这个ScrollAwareFABBehavior中看到的那样。
这只是开始
Behavior的每个部分都很有趣,不过在把它们放在一起时,就出现了奇迹。笔者强烈建议,请读者查看Design
Library的原代码库,找出更高级的behavior。Android SDK Search
Chrome的扩展组件是我最喜欢搜索开源代码的来源之一(尽管包含在/extras/android/m2repository中的源码总是最新的)。
在深刻理解Behavior的功能之后,希望你们能使用它们构建更好的应用。
深入理解Android开发中的CoordinatorLayout Behavior的更多相关文章
- Android开发中常见的设计模式
对于开发人员来说,设计模式有时候就是一道坎,但是设计模式又非常有用,过了这道坎,它可以让你水平提高一个档次.而在android开发中,必要的了解一些设计模式又是非常有必要的.对于想系统的学习设计模式的 ...
- 转:Android开发中的MVP架构(最后链接资源不错)
Android开发中的MVP架构 最近越来越多的人开始谈论架构.我周围的同事和工程师也是如此.尽管我还不是特别深入理解MVP和DDD,但是我们的新项目还是决定通过MVP来构建. 这篇文章是我通过研究和 ...
- 转: Android开发中的MVP架构详解(附加链接比较不错)
转: http://www.codeceo.com/article/android-mvp-artch.html 最近越来越多的人开始谈论架构.我周围的同事和工程师也是如此.尽管我还不是特别深入理解M ...
- Android 开发中的屏幕适配技术详解
本文主要介绍Android开发中比较头疼繁琐的一个问题-屏幕适配问题.主要从适配原因.基本核心概念.适配方法等方面介详细 介绍从而是的深入或者进一步对Android屏幕适配技术的掌握和理解. 真题园网 ...
- android开发中的5种存储数据方式
数据存储在开发中是使用最频繁的,根据不同的情况选择不同的存储数据方式对于提高开发效率很有帮助.下面笔者在主要介绍Android平台中实现数据存储的5种方式. 1.使用SharedPreferences ...
- 设计模式笔记之二:Android开发中的MVP架构(转)
写在前面,本博客来源于公众号文章:http://mp.weixin.qq.com/s?__biz=MzA3MDMyMjkzNg==&mid=402435540&idx=1&sn ...
- Android开发中常见的设计模式 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- Android开发中使用七牛云存储进行图片上传下载
Android开发中的图片存储本来就是比较耗时耗地的事情,而使用第三方的七牛云,便可以很好的解决这些后顾之忧,最近我也是在学习七牛的SDK,将使用过程在这记录下来,方便以后使用. 先说一下七牛云的存储 ...
- Android开发中无处不在的设计模式——动态代理模式
继续更新设计模式系列.写这个模式的主要原因是近期看到了动态代理的代码. 先来回想一下前5个模式: - Android开发中无处不在的设计模式--单例模式 - Android开发中无处不在的设计模式-- ...
随机推荐
- [Android]异常7-Error:Configuration with name 'default' not found.
背景:使用SVN更新代码,运行出现 异常原因: 可能一>缺少Modules 解决办法有: 解决一>Android Studio切换为Project,settings.gradle中引用和现 ...
- JS——锚点的运用
锚点的两种形式: 1.<a href="#a">点击到锚点</a> 2.window.location.hash = "#a"; 最后都 ...
- C# 学习——静态(第四天)
一.命名空间 类似于文件夹,而类就是文件夹中的文件: 作用:明确的指向我们所需要的类的 所在的位置: 统一命名空间下,类名不能重复. 二.类 概念:具有相同属性和功能的对象的抽象的集合. 三.静态与实 ...
- (转)分布式文件存储FastDFS(六)FastDFS多节点配置
http://blog.csdn.net/xingjiarong/article/details/50759918 前面几篇关于FastDFS的博客中介绍了如何在一台机器上搭建一个简易的FastDFS ...
- C# 获得枚举值中所有数据到Array(数组)中
Array LogType = Enum.GetValues(LogTypes.登录.GetType()); public enum LogTypes { 登录, 添加, 修改, 删除, 导出, 异常 ...
- js for 循环 添加tr td 算法
StringBuffer sb=new StringBuffer(); int n = 5; sb.append("<tr>"); List<MenuBean&g ...
- UID中RUID、EUID和SUID的区别
看UNIX相关的书时经常能遇到这几个概念,但一直没有好好去理清这几个概念,以致对这几个概念一直一知半解.今天好好区分了一下这几个概念并总结如下.说白了这几个UID引出都是为了系统的权限管理. 下面分别 ...
- redis下载安装配置教程
参考 https://www.cnblogs.com/taostaryu/p/9481749.html 上面做完后, 打开客户端 $ redis-cli 以上命令将打开以下终端: redis 127. ...
- HDU - 1087 Super Jumping!Jumping!Jumping!(dp求最长上升子序列的和)
传送门:HDU_1087 题意:现在要玩一个跳棋类游戏,有棋盘和棋子.从棋子st开始,跳到棋子en结束.跳动棋子的规则是下一个落脚的棋子的号码必须要大于当前棋子的号码.st的号是所有棋子中最小的,en ...
- 转:Windows Phone 7 设计简介
英文原文:smashingmagazine 导读:Windows Phone 7 引进了一个全新的内容管理和用户界面,命名为Metro的设计语言和理论.微软这次所看准的市场和用户群也与之前的老一代 W ...