原文地址:http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0224/3991.html

如果没有深入CoordinatorLayout ,你注定无法在探索Android Design Support Library的路上走多远 - Design Library中的许多view都需要一个CoordinatorLayout。但是为什么呢?CoordinatorLayout本身并没有做太多事情:和标准的framework视图一起使用时,它就跟一个普通的FrameLayout差不多。那么它的神奇之处来自于哪里呢?答案就是CoordinatorLayout.Behavior。通过为CoordinatorLayout的直接子view设置一个Behavior,就可以拦截touch events, window insets, measurement, layout, 和 nested scrolling等动作。Design Library大量利用了Behaviors来实现你所看到的功能。

blob.png

创建一个Behavior

创建一个behavior很简单:继承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}
}

注意这个类设置的是普通View,这意味着你可以把FancyBehavior设置给任何View类。但是,如果你只允许让Behavior设置给一个特定类型的View,则需要这样写:

public class FancyFrameLayoutBehaviorextends CoordinatorLayout.Behavior<FancyFrameLayout>

这可以省去把回调方法中收到的view参数转换成正确类型的步骤-效率第一嘛。

可以使用Behavior.setTag()/Behavior.getTag() 来保存临时数据,还可以使用onSaveInstanceState()/onRestoreInstanceState()来保存跟Behavior相关的实例的状态。我建议让Behaviors尽可能的轻,但是这些方法让状态化Behaviors成为可能。
设置Behavior

当然了,Behaviors并不会对自身做任何事情-它们需要被设置在一个CoordinatorLayout的子view上之后才会被实际调用。设置Behaviors主要有三种方式:程序中动态设置,xml布局文件设置和使用注解设置。
在程序中设置Behavior

当你认为Behavior是一个被设置在CoordinatorLayout每个子view上的附加数据时,你就不会对Behavior其实是保存在每个view的LayoutParam中感到奇怪了( 如果你已经阅读了我们 关于布局的文章 )- 这也是为什么Behaviors需要声明在CoordinatorLayout的直接子View上的原因,因为只有那些子View才存有CoordinatorLayout.LayoutParams(根据自己的理解翻译的)。

FancyBehavior fancyBehavior = new FancyBehavior();
CoordinatorLayout.LayoutParams params =(CoordinatorLayout.LayoutParams) yourView.getLayoutParams();
params.setBehavior(fancyBehavior);

这里你会发现我们使用的是默认的无参构造函数。但这并不是说你就不能使用任何参数 - 如果你想,代码里面,万事皆有可能。
在xml里设置Behavior

当然,每次都在代码里面把所有事情做完会显得有点乱。就跟多数自定义的LayoutParam一样,这里也有相应的layout_ attribute 与之对应。那就是layout_behavior 属性:

<FrameLayoutandroid:layout_height=”wrap_content”android:layout_width=”match_parent”app:layout_behavior=”.FancyBehavior” />

这里与前面不同的是,被调用的构造函数总是FancyBehavior(Context context, AttributeSet attrs)。因此,你可以在xml属性中声明你想要的其他自定义属性。如果你想让开发者能够通过xml自定义Behavior的功能,这点是很重要的。

注意:类似于由父类负责解析和解释的layout_ 属性命名规则,使用behavior_ prefix来指定被专门Behavior使用的某个属性。

例子(译者结合评论做的补充):

<FrameLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"app:layout_behavior=".MaxWidthBehavior"app:behavior_maxWidth="400dp" />

自动设置一个Behavior

如果你正在创建一个需要一个自定义Behavior的自定义View(就如Design Library中的许多控件那样),那么你很可能希望view默认就设置了那个Behavior,而不需要每次都通过xml或者代码去手动指定。为此,你只需在自定义View类的最上面设置一个简单的注解:

@CoordinatorLayout.DefaultBehavior(FancyFrameLayoutBehavior.class)
public class FancyFrameLayout extends FrameLayout {
}

你会发现你的Behavior会随着默认的构造函数被调用,这非常类似于与通过程序设置Behavior。注意任何 layout_behavior属性所代表的Behavior都会重写 DefaultBehavior。
拦截 Touch Events

一旦你设置好了所有的behavior,你就该准备做点实际工作了。Behavior能做的事情之一就是拦截触摸事件。

如果没有CoordinatorLayout,我们通常会被牵涉进 ViewGroup的子类中,就像 Managing Touch Events training一文所讨论的那样。但是如果有了CoordinatorLayout,CoordinatorLayout就会把它onInterceptTouchEvent() 中的参数(主要是MotionEvent)和调用传递到Behavior的onInterceptTouchEvent(),让你的Behavior有一次拦截触摸事件的机会。如果返回true,你的Behavior则会通过onTouchEvent() 收到所有的后续触摸事件-而View完全不知道发生了什么事情。这也是SwipeDismissBehavior 在view上的工作原理。

ps:我以前专门分析过SwipeDismissBehavior,和这段话基本一致。另外CoordinatorLayout其实是遍历了一遍自己的直接子View,一个一个的调用子view中的Behavior,见:SwipeDismissBehavior用法及实现原理 。

不过还有一个更粗暴的触摸拦截:拦截所有的交互。只需在 blocksInteractionBelow() 里返回true即可(我们这个视图下的其他视图将获取不到任何Touch事件)。当然,你可能希望在交互被阻止的情况下能有一些视觉效果 - 这就是为什么blocksInteractionBelow()实际上默认依赖 getScrimOpacity() 的值 - 返回一个非零将在View之上绘制一层overlay颜色并且屏蔽所有的交互。
拦截Window Insets

假设你读了Why would I want to fitsSystemWindows? blog。那里深入讨论了fitsSystemWindows到底干什么的,但是它归纳为:window insets 需要避免在 system windows(比如status bar 和 navigation bar)的下面绘制。

Behaviors在这里也有拦截的机会 - 如果你的View是fitsSystemWindows=“true”的,那么任何依附着的Behavior都将得到onApplyWindowInsets()调用,且优先级高于View自身。

注意:如果你的Behavior并没有消费掉整个 window insets,它应该通过ViewCompat.dispatchApplyWindowInsets() 传递insets,以确保任何子view都能有机会看到这个WindowInsets。

拦截Measurement 和 layout

测量与布局(Measurement and layout)是 安卓如何绘制View的关键组成部分。因此对于能够拦截一切的Behavior来说,它应该能在第一时间拦截测量和布局才是合情合理的。 这要通过onMeasureChild() 和 onLayoutChild() 回调来完成。

比如, 我们找来任意一个普通的ViewGroup,并向它添加一个maxWidth:

/** Copyright 2015 Google Inc.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**     http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package com.example.behaviors;import android.content.Context;
import android.content.res.TypedArray;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.view.ViewGroup;import static android.view.View.MeasureSpec;/*** Behavior that imposes a maximum width on any ViewGroup.** <p />Requires an attrs.xml of something like** <pre>* &lt;declare-styleable name="MaxWidthBehavior_Params"&gt;*     &lt;attr name="behavior_maxWidth" format="dimension"/&gt;* &lt;/declare-styleable&gt;* </pre>*/
public class MaxWidthBehavior<V extends ViewGroup> extends CoordinatorLayout.Behavior<V> {private int mMaxWidth;public MaxWidthBehavior(Context context, AttributeSet attrs) {super(context, attrs);TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.MaxWidthBehavior_Params);mMaxWidth = a.getDimensionPixelSize(R.styleable.MaxWidthBehavior_Params_behavior_maxWidth, 0);a.recycle();}@Overridepublic boolean onMeasureChild(CoordinatorLayout parent, V child,int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed) {if (mMaxWidth <= 0) {// No max width means this Behavior is a no-opreturn false;}int widthMode = MeasureSpec.getMode(parentWidthMeasureSpec);int width = MeasureSpec.getSize(parentWidthMeasureSpec);if (widthMode == MeasureSpec.UNSPECIFIED || width > mMaxWidth) {// Sorry to impose here, but max width is kind of a big dealwidth = mMaxWidth;widthMode = MeasureSpec.AT_MOST;parent.onMeasureChild(child,MeasureSpec.makeMeasureSpec(width, widthMode), widthUsed,parentHeightMeasureSpec, heightUsed);// We've measured the View, so CoordinatorLayout doesn't have toreturn true;}// Looks like the default measurement will work greatreturn false;}
}

写一个通用的Behavior固然有用,但我们需要知道的是有时候如果你想让你的app简单一点的话完全可以把Behavior的相关功能写在自定义View的内部,没必要为了使用Behavior而是用它。
理解View之间的依赖

以上的所有功能都只需要一个View。但是Behaviors的强大之处在于在View之间建立依赖关系-当另一个View改变的时候,你的Behavior会得到一个callback,根据外部条件改变它的功能。

Behaviors依赖于View有两种形式:当它的View锚定于另外一个View(一种隐式的依赖)或者,当你在layoutDependsOn()中明确的返回true。

锚定发生于你使用了CoordinatorLayout的layout_anchor 属性之时。它和layout_anchorGravity 属性结合,可以让你有效的把两个View捆绑在一起。比如,你可以把一个FloatingActionButton锚定在一个AppBarLayout上,那么如果AppBarLayout滚动出屏幕,FloatingActionButton.Behavior将使用隐式的依赖去隐藏FAB。

不管什么形式,当一个依赖的View被移除的时候你的Behavior会得到回调 onDependentViewRemoved() ,当依赖的View发生变化的时候(比如:调整大小或者重置自己的position),得到回调 onDependentViewChanged()

这个把View绑定在一起的能力正是Design Library那些酷炫功能的工作原理 -以FloatingActionButton与Snackbar之间的交互为例。FAB的 Behavior依赖于被添加到CoordinatorLayout的Snackbar,然后它使用onDependentViewChanged() callback来将FAB向上移动,以避免和Snackbar重叠。

注意:如果你添加了一个依赖,不管child的顺序如何,你的View将总是在所依赖的View放置之后才会被放置。

嵌套滚动

啊哈,嵌套滚动。在这篇博客中,我只会点到为止。记住几点:

你不需要在嵌套滚动的View上面定义依赖。CoordinatorLayout的每个child都有机会接收到嵌套滚动事件。嵌套滚动不仅可以开始于CoordinatorLayout的直接child,还可以开始于任何child(比如CoordinatorLayout的child的child)。虽然我叫它嵌套滚动,但其实它包含滚动(scrolling)和划动(flinging)两种。

那么让我们使用onStartNestedScroll()来定义你所感兴趣的嵌套滚动(方向)。你将收到滚动的轴(比如横向或者纵向-让它可以轻易的忽略某个方向上的滚动)并且为了接收那个方向上的后续滚动事件必须返回true。

当你在onStartNestedScroll()中返回了true之后,嵌套滚动进入两个阶段:

onNestedPreScroll() 会在scrolling View获得滚动事件前调用,它允许你消费部分或者全部的事件信息。onNestedScroll() 会在scrolling View做完滚动后调用,通过回调可以知道scrolling view滚动了多少和它没有消耗的滚动事件。

同样,fling操作也有与之相对应的方法(虽然e pre-fling callback 必须消费完或者完全不消费fling - 没有消费部分的情况)。

当嵌套滚动(或者flinging)结束,你将得到一个onStopNestedScroll()回调。这标志着滚动的结束 - 迎接在下一个滚动之前的onStartNestedScroll() 调用。

比如,当向下滚动的时候隐藏FloatingActionButton,向上滚动的时候显示FloatingActionButton- 这只牵涉到重写onStartNestedScroll() 和 onNestedScroll(),就如在ScrollAwareFABBehavior中所看到的那样。
这只是开始

Behavior每个单独的部分都很有趣,当他们结合起来就会发生很神奇的事情。为了了解更多的高级behavior,我强烈鼓励你去查看Design Library的源码-Android SDK Search Chrome extension是我探索AOSP源码时最喜欢的资源(虽然包含在 /extras/android/m2repository中的源码总是最新的)。

在了解Behavior能做哪些事情这点上打下了坚实的基础后,让我知道你们是如何使用它们创建更优秀的app的。

要了解更多,请参与在 Google+ post 上的讨论并关注 Android Development Patterns Collection !

欢迎关注微信公众号:“Android 之旅 ”,大家一起学习、交流。

拦截一切的CoordinatorLayout Behavior相关推荐

  1. android 5 .0下拉回弹,自定义CoordinatorLayout.Behavior 实现下拉回弹

    先看效果 123.gif package com.tospur.exmind.testrecycerviewwithtopandbottomrefresh.refresh; import androi ...

  2. Android:自定义CoordinatorLayout.behavior 简单的仿UC首页

    CoordinatorLayout顾名思义协调布局,是用来协调该布局下的子控件,最简单地使用就是头部伸缩和折叠了,配合着TabLayout,只需要设置一下AppBarLayout子控件的layout_ ...

  3. sidhu眼中的CoordinatorLayout.Behavior(二)

    前言 在上一节sidhu眼中的CoordinatorLayout.Behavior(一)中,我们讲解了如何以通过Behavior来重写某个控件的触摸事件 可是我们只讲了如何将触摸事件抛出来,那怎么对这 ...

  4. sidhu眼中的CoordinatorLayout.Behavior(一)

    前言 Behavior是Android Design中推荐的布局概念,网上找了很多关于Behavior的资料,很多都是直接翻译的文档或者浅尝辄止,很多问题都没有讲明白,例如具体怎么自定义Behavio ...

  5. 反编译简书app和小红书app滑动效果sticky粘性头布局的实现CoordinatorLayout+behavior

    反编译简书app和小红书app滑动效果sticky粘性头布局的实现CoordinatorLayout+behavior 小红书效果: 简书效果: demo效果图: github地址:https://g ...

  6. android 模拟滑动app,反编译简书app和小红书app滑动效果sticky粘性头布局的实现CoordinatorLayout+behavior...

    反编译简书app和小红书app滑动效果sticky粘性头布局的实现CoordinatorLayout+behavior 小红书效果: xiaohongshuu.gif 简书效果: jianshug.g ...

  7. 源码看CoordinatorLayout.Behavior原理

    在上一篇博客CoordinatorLayout高级用法-自定义Behavior中,我们介绍了如何去自定义一个CoordinatorLayout的Behavior,通过文章也可以看出Behavior在C ...

  8. CoordinatorLayout+Behavior讲解

    文章开始前奉送上代码,方便大家对照学习 1.前言 CoordinatorLayout是在Google I/O 15上,谷歌发布了一个新的 support library中的控件,它是support l ...

  9. Error: Program type already present: android.support.design.widget.CoordinatorLayout$Behavior 预览器异常

    添加依赖和自己的版本不符合,根据自己的最低版本进行导入适合的包,比如我这里是26.28就应该导入26-28版本的android包

  10. 自定义Behavior的艺术探索-仿UC浏览器主页

    出处:http://www.jianshu.com/p/f7989a2a3ec2 前言&效果预览 最近几个周末基本在研究CoordinatorLayout控件和自定义Behavior当中,这期 ...

最新文章

  1. Cocos坐标之convertToNodeSpace、convertToWorldSpace、convertToNodeSpaceAR、convertToWorldSpaceAR区别和用法...
  2. 18:等差数列末项计算
  3. u3d 模版测试 失败_基于Python的HTTP接口自动化测试框架实现
  4. 表格为一条细线的html代码,html制作细线表格的简单实例
  5. 让搜索显示中文的方法
  6. (转)LuaPlus C++ 函数互调
  7. kvm 网络配置及克隆
  8. window下环境变量立即生效
  9. 计算机财务管理中表格的应用,论Excel表在财务管理中的应用
  10. 关于计算机软件系统的知识,会计电算化知识点:计算机软件系统
  11. 超像素块提取 matlab,GitHub - CielChen/Make3DFeature: 将图像分割成超像素,并提取每个超像素块的Make3D特征...
  12. rosetta_ddg 使用-rosetta 2020版
  13. leshan基于OMALightweight M2M(LwM2M)协议的Java实现(入门)
  14. Qt 实现Unicode字符表情包显示到界面 Emoji
  15. 前端JS常见树——Tree
  16. C++:66---特殊工具与技术之(不可移植的特性:位域、volatile、extern “C“链接提示)
  17. 牛顿迭代法求平方根原理
  18. PetaLinux使用Gstreamer传输USB摄像头到DP显示屏
  19. C#控件篇 - PictureBox控件设置滚动条
  20. 2011 Heilongjiang collegiate programming contest 【(7+1)/10】 [补完]

热门文章

  1. vim模式下报错E37: No write since last change No write since last change for buffer “ “
  2. iOS UI切图@1x、@2x、@3x的实际尺寸
  3. GD32W515实现NES模拟器
  4. 2021 Summary
  5. thinkpad x200 bios 超级密码破解方法
  6. font-family:微软雅黑在moc上显示无效的解决办法
  7. python买卖股票_Python实现买卖股票的最佳时机的一种方法
  8. 为什么我不给孩子看国产动漫?看看这5部法国动漫,你就知道了!【转】
  9. Codeforces 686 D - Kay and Snowflake
  10. js基础——图片切换实例