一、写在前面

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

二、从是什么开始

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

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

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

      2、用作相互之间具有特定交互行为的UI控件的容器

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

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

  2、交互行为?

    先看个简单的效果图

    

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

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

 <?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.nanchen.coordinatorlayoutdemo.CoordinatorActivity"> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="观察者"
app:layout_behavior=".FollowBehavior"/> <Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="被观察者"
android:layout_gravity="center"
android:id="@+id/btn"/> </android.support.design.widget.CoordinatorLayout>

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

 package com.nanchen.coordinatorlayoutdemo;

 import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
import android.widget.TextView; /**
*
* 自定义CoordinatorLayout的Behavior,泛型为观察者View(要跟着别人动的那个)
*
* 必须重写两个方法,layoutDependOn和onDependentViewChanged
*
* @author nanchen
* @fileName CoordinatorLayoutDemo
* @packageName com.nanchen.coordinatorlayoutdemo
* @date 2016/12/13 10:13
*/ public class FollowBehavior extends CoordinatorLayout.Behavior<TextView>{ /**
* 构造方法
*/
public FollowBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
} /**
* 判断child的布局是否依赖dependency
*
* 根据逻辑来判断返回值,返回false表示不依赖,返回true表示依赖
*
* 在一个交互行为中,dependent view的变化决定了另一个相关View的行为。
* 在这个例子中,Button就是dependent view,因为TextView跟随着它。
* 实际上dependent view就相当于我们前面介绍的被观察者
*
*/
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
return dependency instanceof Button;
} @Override
public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency) {
child.setX(dependency.getX());
child.setY(dependency.getY() + 100);
return true;
}
}

    重点看看其中重写的两个方法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(new OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (motionEvent.getAction() == MotionEvent.ACTION_MOVE){
view.setX(motionEvent.getRawX()-view.getWidth()/2);
view.setY(motionEvent.getRawY()-view.getHeight()/2);
}
return true;
}
}); 

  这样一来,我们就完成了为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

    a) 拦截Touch事件

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

    b)拦截测量及布局

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

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

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

    c) view的依赖关系的确定

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

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

  • 隐式依赖:通过我们最开始提到的锚(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的魅力。

  

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

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

 <?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_coor_app_bar"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.nanchen.coordinatorlayoutdemo.CoorAppBarActivity"> <android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"> <android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways"> </android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout> <android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/recycler"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/> </android.support.design.widget.CoordinatorLayout>

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

  2、layout_scrollFlags

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

    a)  scroll

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

    b)  exitUntilCollapsed

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

    c) enterAlways

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

    d)  enterAlwaysCollapsed

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

    e)  snap

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

四、CollapsingToolBarLayout

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

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

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

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

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

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

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

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

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

  

  看看布局:

  

 <?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_coor_tool_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.nanchen.coordinatorlayoutdemo.CoorToolBarActivity"> <android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:theme="@style/AppTheme.AppBarOverlay"> <android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="200dp"
app:contentScrim="@color/colorPrimary"
app:expandedTitleMarginStart="100dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:statusBarScrim="@android:color/transparent"
app:titleEnabled="false"> <ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
android:src="@mipmap/logo"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.6"/> <android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:title=""/> </android.support.design.widget.CollapsingToolbarLayout> </android.support.design.widget.AppBarLayout> <android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/recycler"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/> <TextView
android:id="@+id/toolbar_title"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_marginLeft="16dp"
android:layout_marginTop="-100dp"
android:alpha="0"
android:elevation="10dp"
android:gravity="center_vertical"
android:text="爱吖校推-你关注的,我们才推"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold"
app:layout_behavior=".SimpleViewBehavior"
app:svb_dependOn="@id/appbar"
app:svb_dependType="y"
app:svb_targetAlpha="1"
app:svb_targetY="0dp"/> <android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:src="@mipmap/ic_start"
app:layout_anchor="@id/appbar"
app:layout_anchorGravity="bottom|right"/> </android.support.design.widget.CoordinatorLayout>

  我们在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

【知识必备】一文让你搞懂design设计的CoordinatorLayout和AppbarLayout联动,让Design设计更简单~的更多相关文章

  1. 一文带你搞懂 RPC 到底是个啥

    RPC(Remote Procedure Call),是一个大家既熟悉又陌生的词,只要涉及到通信,必然需要某种网络协议.我们很可能用过HTTP,那么RPC又和HTTP有什么区别呢?RPC还有什么特点, ...

  2. 夯实Java基础系列3:一文搞懂String常见面试题,从基础到实战,更有原理分析和源码解析!

    目录 目录 string基础 Java String 类 创建字符串 StringDemo.java 文件代码: String基本用法 创建String对象的常用方法 String中常用的方法,用法如 ...

  3. 【springcloud】一文带你搞懂API网关

    作者:aCoder2013 https://github.com/aCoder2013/blog/issues/35 前言 假设你正在开发一个电商网站,那么这里会涉及到很多后端的微服务,比如会员.商品 ...

  4. 一文带你搞懂 Kafka 的系统架构(深度好文,值得收藏)

    Kafka 简介 Kafka 是一种高吞吐.分布式.基于发布和订阅模型的消息系统,最初是由 LinkedIn 公司采用 Scala 和 java 开发的开源流处理软件平台,目前是 Apache 的开源 ...

  5. 一文帮你搞懂 Android 文件描述符

    介绍文件描述符的概念以及工作原理,并通过源码了解 Android 中常见的 FD 泄漏. 一.什么是文件描述符? 文件描述符是在 Linux 文件系统的被使用,由于Android基 于Linux 系统 ...

  6. 一文带你搞懂 JWT 常见概念 & 优缺点

    在 JWT 基本概念详解这篇文章中,我介绍了: 什么是 JWT? JWT 由哪些部分组成? 如何基于 JWT 进行身份验证? JWT 如何防止 Token 被篡改? 如何加强 JWT 的安全性? 这篇 ...

  7. 从定义到AST及其遍历方式,一文带你搞懂Antlr4

    摘要:本文将首先介绍Antlr4 grammer的定义方式,如何通过Antlr4 grammer生成对应的AST,以及Antlr4 的两种AST遍历方式:Visitor方式和Listener方式. 1 ...

  8. 一文带你搞懂 SSR

    欲语还休,欲语还休,却道天凉好个秋 ---- <丑奴儿·书博山道中壁>辛弃疾 什么是 SSR ShadowsocksR?阴阳师?FGO? Server-side rendering (SS ...

  9. Web端即时通讯基础知识补课:一文搞懂跨域的所有问题!

    本文原作者: Wizey,作者博客:http://wenshixin.gitee.io,即时通讯网收录时有改动,感谢原作者的无私分享. 1.引言 典型的Web端即时通讯技术应用场景,主要有以下两种形式 ...

随机推荐

  1. 记一次tomcat线程创建异常调优:unable to create new native thread

    测试在进行一次性能测试的时候发现并发300个请求时出现了下面的异常: HTTP Status 500 - Handler processing failed; nested exception is ...

  2. AngularJs之九(ending......)

    今天继续angularJs,但也是最后一篇关于它的了,基础部分差不多也就这些,后续有机会再写它的提升部分. 今天要写的也是一个基础的选择列表: 一:使用ng-options,数组进行循环. <d ...

  3. Hyper-V2:向VM增加虚拟硬盘

    使用Hyper-V创建VM,在VM成功安装OS之后,发现VM只有一个逻辑盘C,用于存储VM的操作系统.在产品环境中,需要向VM增加虚拟硬盘,便于将数据单独存储在不同的逻辑盘符中.在Hyper-V中,分 ...

  4. 【.net 深呼吸】启动一个进程并实时获取状态信息

    地球人和火星人都知道,Process类既可以获取正在运行的进程,也可以启动一个新的进程.在79.77%应用场合,我们只需要让目标进程顺利启动就完事了,至于它执行了啥,有没有出错,啥时候退出就不管了. ...

  5. Ubuntu 16.10 开启PHP错误提示

    两个步骤: 修改php.ini配置文件中的error_reporting 和 display_errors两地方内容: sudo vim /etc/php/7.0/apache2/php.ini er ...

  6. nodejs中获取时间戳、时间差

    Nodejs中获取时间戳的方法有很多种,例如: new Date().getTime() Date.now() process.uptime() process.hrtime() 平时想获取一个时间戳 ...

  7. Activity之概览屏幕(Overview Screen)

    概览屏幕 概览屏幕(也称为最新动态屏幕.最近任务列表或最近使用的应用)是一个系统级别 UI,其中列出了最近访问过的 Activity 和任务. 用户可以浏览该列表并选择要恢复的任务,也可以通过滑动清除 ...

  8. TFS 生成发布代理

    下载Agent 后,执行配置命令     参考 安装TFS(2015)工作组模式代理服务器(Agent)

  9. mysql join 和left join 对于索引的问题

    今天遇到一个left join优化的问题,搞了一下午,中间查了不少资料,对MySQL的查询计划还有查询优化有了更进一步的了解,做一个简单的记录: select c.* from hotel_info_ ...

  10. 2DToolkit官方文档中文版打地鼠教程(二):设置摄像机

    这是2DToolkit官方文档中 Whack a Mole 打地鼠教程的译文,为了减少文中过多重复操作的翻译,以及一些无必要的句子,这里我假设你有Unity的基础知识(例如了解如何新建Sprite等) ...