一文彻底搞懂 Design 设计的 CoordinatorLayout 和 AppbarLayout 联动,让 Design 设计更简单~
来源:互联网 发布:小众运动鞋知乎 编辑:程序博客网 时间:2024/06/05 19:16
转载自:http://www.jianshu.com/p/640f4ef05fb2
一、写在前面
其实博主在之前已经对 Design 包的各个控件都做了博文说明,无奈个人觉得理解不够深入,所以有了这篇更加深入的介绍,希望各位看官拍砖~
二、从是什么开始
1、首先我们得知道 CoordinatorLayout
是什么玩意儿,到底有什么用,我们不妨看看官方文档的描述:
CoordinatorLayout
是一个 “加强版”FrameLayout
, 它主要有两个用途:
1) 用作应用的顶层布局管理器,也就是作为用户界面中所有 UI 控件的容器;
2) 用作相互之间具有特定交互行为的 UI 控件的容器,通过为CoordinatorLayout
的子 View 指定 Behavior, 就可以实现它们之间的交互行为。 Behavior 可以用来实现一系列的交互行为和布局变化,比如说侧滑菜单、可滑动删除的 UI 元素,以及跟随着其他 UI 控件移动的按钮等。
其实总结出来就是 CoordinatorLayout
是一个布局管理器,相当于一个增强版的 FrameLayout
,但是它神奇在于可以实现它的子 View 之间的交互行为。
2、交互行为?
先看个简单的效果图
可能大家看到这,就自然能想到观察者模式,或者我前面写的Rx模式:这可能是最好的RxJava 2.x 教程(完结版)
我们的 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
- 拦截 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
的魅力。
实际效果就是这样,当向上滑动 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
的取值可以为以下几种。
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
的基本使用姿势。
我们来看看一个常用的效果:
看看布局:
<?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 联动,让 Design 设计更简单~
- 一文彻底搞懂 Design 设计的 CoordinatorLayout 和 AppbarLayout 联动,让 Design 设计更简单~
- Android Design Support Library(二):CoordinatorLayout、AppBarLayout简单用法
- 一篇文章让你彻底搞懂Material Design
- Android Material Design(5) CoordinatorLayout,AppBarLayout,Toolbar,CollapsingToolbarLayout的使用
- Material Design:扁而不平--让你彻底搞懂什么是Material Design
- Android Design Support Library之CoordinatorLayout,AppBarLayout
- Material Design学习:CoordinatorLayout+AppBarLayout+CollapsingToolbarLayout
- Android:Material Design之CoordinatorLayout+AppBarLayout使用
- Android Material Design 之 CoordinatorLayout + AppBarLayout
- Design 四: CoordinatorLayout + AppBarLayout+ Toolbar+NestedScrollView
- Design 四: CoordinatorLayout + AppBarLayout+ Toolbar+NestedScrollView
- 彻底搞懂CoordinatorLayout
- android.support.design.widget包下的CoordinatorLayout、AppBarLayout、TextInputLayout、FloatingActionButton、
- Android Material Design(4) CoordinatorLayout,AppBarLayout,Toolbar以及TabLayout的使用
- CoordinatorLayout+AppBarLayout实现联动
- 设计模式Design Patterns (一)
- Material Design之CoordinatorLayout+AppBarLayout实现上滑隐藏ToolBar
- 万维链的技术开发团队
- pycharm快捷键
- struts2部分配置说明
- Log4j日志记录(Java)
- poj3667Hotel
- 一文彻底搞懂 Design 设计的 CoordinatorLayout 和 AppbarLayout 联动,让 Design 设计更简单~
- 序列化serialize和反序列化unserialize
- Nginx学习总结(8)——Nginx服务器详解
- docker--制作自己的镜像(三)
- Goldbach`s Conjecture 素数筛选
- android 绘制控件圆角边框
- log4J日志的使用
- OpenGL ES应用开发实践指南(android 卷)笔记 第三章2
- 关于定时器-闭包分析