无标题文章

一、写在前面

其实博主在之前已经对 Design 包的各个控件都做了博文说明,无奈个人觉得理解不够深入,所以有了这篇更加深入的介绍,希望各位看官拍砖~

二、从是什么开始

1、首先我们得知道CoordinatorLayout是什么玩意儿,到底有什么用,我们不妨看看官方文档的描述:

CoordinatorLayout是一个 “加强版”FrameLayout, 它主要有两个用途:

1) 用作应用的顶层布局管理器,也就是作为用户界面中所有 UI 控件的容器;

2) 用作相互之间具有特定交互行为的 UI 控件的容器,通过为CoordinatorLayout的子 View 指定 Behavior, 就可以实现它们之间的交互行为。 Behavior 可以用来实现一系列的交互行为和布局变化,比如说侧滑菜单、可滑动删除的 UI 元素,以及跟随着其他 UI 控件移动的按钮等。

其实总结出来就是CoordinatorLayout是一个布局管理器,相当于一个增强版的FrameLayout,但是它神奇在于可以实现它的子 View 之间的交互行为。

2、交互行为?

先看个简单的效果图

无标题文章_第1张图片

可能大家看到这,就自然能想到观察者模式,或者我前面写的Rx模式:知识必备】RxJava+Retrofit最佳结合体验,打造懒人封装框架~

我们的Button就是一个被观察者,TextView 作为一个观察者,当Button移动的时候通知TextView,TextView就跟着移动。看看其布局:

很简单,一个TextView, 一个Button, 外层用CoordinatorLayout, 然后给我们的TextView加一个自定义的Behavior文件,内容如下:

packagecom.nanchen.coordinatorlayoutdemo;importandroid.content.Context;importandroid.support.design.widget.CoordinatorLayout;importandroid.util.AttributeSet;importandroid.view.View;importandroid.widget.Button;importandroid.widget.TextView;/** * * 自定义 CoordinatorLayout 的 Behavior, 泛型为观察者 View ( 要跟着别人动的那个 ) * * 必须重写两个方法,layoutDependOn和onDependentViewChanged * *@authornanchen *@fileNameCoordinatorLayoutDemo *@packageNamecom.nanchen.coordinatorlayoutdemo *@date2016/12/13  10:13 */publicclassFollowBehaviorextendsCoordinatorLayout.Behavior{/**

* 构造方法

*/publicFollowBehavior(Context context, AttributeSet attrs){super(context, attrs);    }/**

* 判断child的布局是否依赖 dependency

*

* 根据逻辑来判断返回值,返回 false 表示不依赖,返回 true 表示依赖

*

* 在一个交互行为中,Dependent View 的变化决定了另一个相关 View 的行为。

* 在这个例子中, Button 就是 Dependent View,因为 TextView 跟随着它。

* 实际上 Dependent View 就相当于我们前面介绍的被观察者

*

*/@OverridepublicbooleanlayoutDependsOn(CoordinatorLayout parent, TextView child, View dependency){returndependencyinstanceofButton;    }@OverridepublicbooleanonDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency){        child.setX(dependency.getX());        child.setY(dependency.getY() +100);returntrue;    }}

重点看看其中重写的两个方法layoutDependsOn()和onDependentViewChanged()。在介绍这两个方法的作用前,我们先来介绍一下 Dependent View。在一个交互行为中,Dependent View 的变化决定了另一个相关 View 的行为。在这个例子中, Button 就是 Dependent View, 因为 TextView 跟随着它。实际上 Dependent View 就相当于我们前面介绍的被观察者。

知道了这个概念,让我们看看重写的两个方法的作用:

layoutDependsOn():这个方法在对界面进行布局时至少会调用一次,用来确定本次交互行为中的 Dependent View,在上面的代码中,当Dependency是Button 类的实例时返回 true,就可以让系统知道布局文件中的 Button 就是本次交互行为中的 Dependent View。

onDependentViewChanged():当 Dependent View 发生变化时,这个方法会被调用,参数中的child相当于本次交互行为中的观察者,观察者可以在这个方法中对被观察者的变化做出响应,从而完成一次交互行为。

所以我们现在可以开始写Activity中的代码:

findViewById(R.id.btn).setOnTouchListener(newOnTouchListener() {            @Override            public boolean onTouch(Viewview, MotionEvent motionEvent) {if(motionEvent.getAction() == MotionEvent.ACTION_MOVE){view.setX(motionEvent.getRawX()-view.getWidth()/2);view.setY(motionEvent.getRawY()-view.getHeight()/2);}returntrue;}        });

这样一来,我们就完成了为 TextView 和Button 设置跟随移动这个交互行为。很简单有木有,其实为CoordinatorLayout的子 View 设置交互行为只需三步:

自定义一个继承自Behavior类的交互行为类;

把观察者的layout_behavior属性设置为自定义行为类的类名;

重写Behavior类的相关方法来实现我们想要的交互行为。

值得注意的是,有些时候,并不需要我们自己来定义一个Behavior类,因为系统为我们预定义了不少Behavior类。在接下来的篇章中,我们会做出进一步的介绍。

3、更进一步

现在我们已经知道了怎么通过给CoordinatorLayout的子 View 设置Behavior来实现交互行为。现在,让我们更进一步地挖掘下CoordinatorLayout, 深入了解一下隐藏在表象背后的神秘细节。

实际上,CoordinatorLayout本身并没有做过多工作,实现交互行为的主要幕后推手是CoordinatorLayout的内部类——Behavior。通过为CoordinatorLayout的直接子 View绑定一个Behavior,这个Behavior就会拦截发生在这个 View 上的 Touch 事件、嵌套滚动等。不仅如此,Behavior还能拦截对与它绑定的 View 的测量及布局。关于嵌套滚动,我们会在后续文章中进行详细介绍。下面我们来深入了解一下Behavior是如何做到这一切的。

4、深入理解 Behavior

拦截 Touch 事件

当我们为一个CoordinatorLayout的直接子 View 设置了 Behavior 时,这个 Behavior 就能拦截发生在这个 View 上的 Touch 事件,那么它是如何做到的呢?实际上,CoordinatorLayout重写了onInterceptTouchEvent()方法,并在其中给 Behavior 开了个后门,让它能够先于 View 本身处理 Touch 事件。具体来说,CoordinatorLayout的onInterceptTouchEvent()方法中会遍历所有直接子 View ,对于绑定了 Behavior 的直接子 View 调用 Behavior 的 onInterceptTouchEvent() 方法,若这个方法返回 true, 那么后续本该由相应子 View 处理的 Touch 事件都会交由 Behavior 处理,而 View 本身表示懵逼,完全不知道发生了什么。

拦截测量及布局

了解了 Behavior 是怎养拦截 Touch 事件的,想必大家已经猜出来了它拦截测量及布局事件的方式 —— CoordinatorLayout 重写了测量及布局相关的方法并为 Behavior 开了个后门。没错,真相就是如此。

CoordinatorLayout 在onMeasure()方法中,会遍历所有直接子 View ,若该子 View 绑定了一个 Behavior ,就会调用相应 Behavior 的onMeasureChild()方法,若此方法返回 true,那么 CoordinatorLayout 对该子 View 的测量就不会进行。这样一来, Behavior 就成功接管了对 View 的测量。

同样,CoordinatorLayout 在onLayout()方法中也做了与onMeasure()方法中相似的事,让 Behavior 能够接管对相关子 View 的布局。

View 的依赖关系的确定

现在,我们在探究一下交互行为中的两个 View 之间的依赖关系是怎么确定的。我们称 child 为交互行为中根据另一个 View 的变化做出响应的那个个体,而 Dependent View 为child所依赖的 View。实际上,确立 child 和 Dependent View 的依赖关系有两种方式:

1) 显式依赖:为 child 绑定一个 Behavior,并在 Behavior 类的layoutDependsOn()方法中做手脚。即当传入的dependency为 Dependent View 时返回 true,这样就建立了 child 和 Dependent View 之间的依赖关系。

2) 隐式依赖:通过我们最开始提到的锚(anchor)来确立。具体做法可以这样:在 XML 布局文件中,把 child 的layout_anchor属性设为 Dependent View 的id,然后 child 的layout_anchorGravity属性用来描述为它想对 Dependent View 的变化做出什么样的响应。关于这个我们会在后续篇章给出具体示例。

无论是隐式依赖还是显式依赖,在 Dependent View 发生变化时,相应 Behavior 类的onDependentViewChanged()方法都会被调用,在这个方法中,我们可以让 child 做出改变以响应 Dependent View 的变化。

三、玩转AppBarLayout

实际上我们在应用中有 CoordinatorLayout 的地方通常都会有 AppBarLayout 的联用,作为同样的出自 Design 包的库,我们看看官方文档怎么说:

AppBarLayout 是一个垂直的 LinearLayout,实现了 Material Design 中 App bar 的 Scrolling Gestures 特性。AppBarLayout 的子 View 应该声明想要具有的“滚动行为”,这可以通过 layout_scrollFlags 属性或是 setScrollFlags() 方法来指定。

AppBarLayout 只有作为 CoordinatorLayout 的直接子 View 时才能正常工作,为了让 AppBarLayout 能够知道何时滚动其子 View,我们还应该在 CoordinatorLayout 布局中提供一个可滚动 View,我们称之为 Scrolling View。

Scrolling View 和 AppBarLayout 之间的关联,通过将 Scrolling View 的 Behavior 设为 AppBarLayout.ScrollingViewBehavior 来建立。

1、一般怎么用?

AppBar是 Design 的一个概念,其实我们也可以把它看做一种 5.0 出的ToolBar,先感受一下AppBarLayout+CoordinatorLayout的魅力。

无标题文章_第2张图片

实际效果就是这样,当向上滑动 View 的时候,ToolBar 会小时,向下滑动的时候,ToolBar 又会出现,但别忘了,这是 AppBarLayout 的功能,ToolBar 可办不到。由于要滑动,那么我们的 AppBarLayout 一定是和可以滑动的 View 一起使用的,比如RecyclerView,ScollView等。

我们看看上面的到底怎么实现的:

我们可以看到,上面出现了一个app:layouy_scrollFrags的自定义属性设置,这个属性可以定义我们不同的滚动行为。

2、layout_scrollFlags

根据官方文档,layout_scrollFlags的取值可以为以下几种。

scroll

设成这个值的效果就好比本 View 和 Scrolling view 是“一体”的。具体示例我们在上面已经给出。有一点特别需要我们的注意,为了其他的滚动行为生效,必须同时指定 Scroll 和相应的标记,比如我们想要exitUntilCollapsed所表现的滚动行为,必须将 layout_scrollFlags 指定为scroll|exitUntilCollapsed。

exitUntilCollapsed

当本 View 离开屏幕时,会被“折叠”直到达到其最小高度。我们可以这样理解这个效果:当我们开始向上滚动 Scrolling view 时,本 View 会先接管滚动事件,这样本 View 会先进行滚动,直到滚动到了最小高度(折叠了),Scrolling view 才开始实际滚动。而当本 View 已完全折叠后,再向下滚动 Scrolling view,直到 Scrolling view 顶部的内容完全显示后,本 View 才会开始向下滚动以显现出来。

enterAlways

当 Scrolling view 向下滚动时,本 View 会一起跟着向下滚动。实际上就好比我们同时对 Scrolling view 和本 View 进行向下滚动。

enterAlwaysCollapsed

从名字上就可以看出,这是在enterAlways的基础上,加上了“折叠”的效果。当我们开始向下滚动 Scrolling View 时,本 View 会一起跟着滚动直到达到其“折叠高度”(即最小高度)。然后当 Scrolling View 滚动至顶部内容完全显示后,再向下滚动 Scrolling View,本 View 会继续滚动到完全显示出来。

snap

在一次滚动结束时,本 View 很可能只处于“部分显示”的状态,加上这个标记能够达到“要么完全隐藏,要么完全显示”的效果。

四、CollapsingToolBarLayout

这个东西,我相信很多博客和技术文章都会把CollapsingToolBarLayout和CoordinatorLayout放一起讲,这个东西的确很牛。我们同样先看看官方文档介绍:

CollapsingToolbarLayout 通常用来在布局中包裹一个 Toolbar,以实现具有“折叠效果“”的顶部栏。它需要是 AppBarLayout 的直接子 View,这样才能发挥出效果。

CollapsingToolbarLayout包含以下特性:

Collasping title(可折叠标题):当布局完全可见时,这个标题比较大;当折叠起来时,标题也会变小。标题的外观可以通过 expandedTextAppearance 和 collapsedTextAppearance 属性来调整。

Content scrim(内容纱布):根据 CollapsingToolbarLayout 是否滚动到一个临界点,内容纱布会显示或隐藏。可以通过 setContentScrim(Drawable) 来设置内容纱布。

Status bar scrim(状态栏纱布):也是根据是否滚动到临界点,来决定是否显示。可以通过 setStatusBarScrim(Drawable) 方法来设置。这个特性只有在 Android 5.0 及其以上版本,我们设置 fitSystemWindows 为 ture 时才能生效。

Parallax scrolling children(视差滚动子 View):子 View 可以选择以“视差”的方式来进行滚动。(视觉效果上就是子 View 滚动的比其他 View 稍微慢些)

Pinned position children:子 View 可以选择固定在某一位置上。

上面的描述有些抽象,实际上对于Content scrim、Status bar scrim我们可以暂时予以忽略,只要留个大概印象待以后需要时再查阅相关资料即可。下面我们通过一个常见的例子介绍下CollapsingToolbarLayout的基本使用姿势。

我们来看看一个常用的效果:

无标题文章_第3张图片

看看布局:


我们在 XML 文件中为 CollapsingToolBarLayout 的 layout_scrollFlags 指定为scroll|exitUntilCollapsed|snap,这样便实现了向上滚动的折叠效果。

CollapsingToolbarLayout本质上同样是一个FrameLayout,我们在布局文件中指定了一个ImageView和一个Toolbar。ImageView的layout_collapseMode属性设为了parallax,也就是我们前面介绍的视差滚动;而 Toolbar 的layout_collaspeMode设为了pin,也就是 Toolbar 会始终固定在顶部。

五、写在最后

本次的 Design 包下的CoordinatorLayout和AppBarLayout就讲述到这里,后续还将持续更新,欢迎拍砖~

查看源码请移步 Github:https://github.com/nanchen2251/CoordinatorAppBarDemo

作者:南尘2251

链接:http://www.jianshu.com/p/640f4ef05fb2

來源:

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

你可能感兴趣的:(无标题文章)