谷歌在推出Android5.0的同时推出了全新的设计Material Design,谷歌为了给我们提供更加规范的MD设计风格的控件,在2015年IO大会上推出了Design支持包,Design常用的新控件有下面几种。
- 1.官方侧滑菜单DrawerLayout
- 2.导航栏NavigationView
- 3.AppbarLayout
- 4.CollapsingToolbarLayout
- 5.TextInputLayout
- 6.SnackBar
- 6.FloatingActionButton
- 7.CoordinatorLayout
一.概念
DrawerLayout其实是一个布局控件,跟LinearLayout等控件是一种东西,但是drawerLayout带有滑动的功能。只要按照drawerLayout的规定布局方式写完布局,就能有侧滑的效果
二.使用
DrawerLayout分为侧边菜单和主内容区两部分,侧边菜单可以根据手势展开与隐藏(drawerLayout自身特性),主内容区的内容可以随着菜单的点击而变化(这需要使用者自己实现)
<android.support.v4.widget.DrawerLayout
android:id="@+id/dl_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/rl_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_red_light">
</android.support.v7.widget.RecyclerView>
<android.support.design.widget.NavigationView
android:id="@+id/ng_view"
android:layout_width="200dp"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/main_header_layout"
app:menu="@menu/main_nav_menu">
</android.support.design.widget.NavigationView>
</android.support.v4.widget.DrawerLayout>
其中:DrawerLayout最好为界面的根布局,官网是这样说的,否则可能会出现触摸事件被屏蔽的问题;
主内容区的布局代码要放在侧滑菜单布局的前面, 因为 XML 顺序意味着按 z 序(层叠顺序)排序,并且抽屉式导航栏必须位于内容顶部;
侧滑菜单部分的布局(NavigationView)必须设置layout_gravity属性,他表示侧滑菜单是在左边还是右边,而且如果不设置在打开关闭抽屉的时候会报错,
设置了layout_gravity="start/left"的视图才会被认为是侧滑菜单
三.DrawerLayout常用方法
/**
* Adds the specified listener to the list of listeners that will be notified of drawer events.
*将指定的监听添加到DrawerLayout监听列表中
* @param listener Listener to notify when drawer events occur.
* @see #removeDrawerListener(DrawerListener)
*/
addDrawerListener(@NonNull DrawerListener listener)
//移除对应的监听
removeDrawerListener(DrawerListener)
//关闭指定的抽屉视图
closeDrawer()
//打开指定的抽屉视图
openDrawer()
检查给定的抽屉视图当前是否处于打开状态。 *被认为是“开放”的抽屉必须已经处于完全可见的状态。检查部分可见性使用情况isDrawerVisible()
isDrawerOpen()
drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
/**
* 当抽屉被滑动的时候调用月此方法
*
* @param drawerView
* @param slideOffset 表示滑动的幅度(0-1)
*/
@Override
public void onDrawerSlide(View drawerView, float slideOffset) {
}
/**
* 当抽屉完全打开时调用
* @param drawerView
*/
@Override
public void onDrawerOpened(View drawerView) {
}
/**
* 当抽屉完全关闭时调用
* @param drawerView
*/
@Override
public void onDrawerClosed(View drawerView) {
}
/**
* 当抽屉运动状态改变的时候被调用
* 状态值分别为 STATE_IDLE 0 抽屉处于闲置状态。没有动画正在进行中。处于打开或者关闭状态
* STATE_DRAGGING 1 表示用户当前正在拖动抽屉。
* STATE_SETTLING 2 表示抽屉滑动到了关闭或者打开的位置
* 当用户滑动抽屉时 抽屉此时处于STATE_DRAGGING 滑动到刚好关闭或者打开后是STATE_SETTLING状态 随后状态会变为STATE_IDLE 闲置状态
* @param newState
*/
@Override
public void onDrawerStateChanged(int newState) {
Log.d(TAG, "onDrawerStateChanged: "+newState);
}
});
四.ActionBarDrawerToggle与ToolBar
ActionBarDrawerToggle实现了DrawerListener,所以他能做DrawerListener可以做的任何事情,同时他还能将drawerLayout的展开和隐藏与Toolbar的app 图标关联起来,
点击图标的时候还能展开或者隐藏菜单
将抽屉滑动与Toolbar图标关联
1. mToolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
2.
初始化ActionBarDrawerToggle
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawerLayout, mToolbar, R.string.open_string, R.string.close_string)
3.将ActionBarDrawerToggle添加到DrawerLayout监听列表中
drawerLayout.addDrawerListener(toggle);
4.将 ActionBarDrawerToggle与Toolbar图标关联
toggle.syncState();
这样在抽屉打开关闭时 Toolbar图标将会变动,且点击图标时可以切换抽屉的打开关闭状态
同时一下主题可以设置图标的颜色
<style name="AppTheme">
<item name="drawerArrowStyle">@style/DrawerArrowStyle</item>
</style>
<style name="DrawerArrowStyle" parent="Widget.AppCompat.DrawerArrowToggle">
<item name="spinBars">true</item>
<item name="color">@android:color/holo_red_light</item>
</style>
一.概念
NavigationView顾名思义是指导航菜单栏,一般配合DrawerLayout使用作为侧滑菜单栏
二.使用
NavigationView需要接收几个必要的参数、一个用于显示头部的布局app:headerLayout="@layout/main_header_layout"(可选) 以及用于建立导向选项的菜单app:menu="@menu/main_nav_menu",这些都设置完之后,你就只添加监听选中事件的listener就行了。
其中app:menu配置的是一个菜单文件
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:id="@+id/group1"
android:checkableBehavior="single">
<item
android:id="@+id/app_bar"
android:icon="@android:drawable/ic_delete"
android:title="AppbarActivity"></item>
</group>
<group android:id="@+id/group2"
android:checkableBehavior="single">
<item
android:icon="@android:drawable/ic_menu_save"
android:title="Start"></item>
</group>
<item android:id="@+id/sub1" android:title="sub item">
<menu>
<item
android:icon="@android:drawable/ic_menu_save"
android:title="Start"></item>
</menu>
</item>
</menu>
其中:
1.group表示分组 checkableBehavior用来设置item选中模式 有3个值 表示选中状态 single单选 all多选 none 默认
item可以表示一个子项 也可在其中加入menu添加子菜单来实现带有头部的分组效果
每个group 和menu子菜单都会在顶部产生移到横线
2.设置item选中事件
mNavigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
item.setChecked(true);
drawerLayout.closeDrawer(Gravity.LEFT);
return true;
}
});
注意:这样只可以设置group中的item选中状态
item子菜单设置选中状态
1.首先设置子菜单item android:checkable="true"
2.public boolean onNavigationItemSelected(@NonNull MenuItem item) {
item.setChecked(true);
if (mPreMenuItem != null) mPreMenuItem.setChecked(false);
item.setChecked(true);
drawerLayout.closeDrawers();
mPreMenuItem = item;
return true;
}
三.常用属性
app:itemIconTint="" 修改图标颜色
app:itemBackground="" item背景颜色
app:itemTextColor="" item 文字颜色
AppBarLayout继承自LinearLayout,布局方向为垂直方向。所以你可以把它当成垂直布局的LinearLayout来使用。
AppBarLayout是在LinearLayou上加了一些材料设计的概念,它可以让你定制当某个可滚动View的滚动手势发生变化时,其内部的子View实现何种动作
解释:
1.AppBarLayout内部子View可以和一个可滚动的View的滑动事件产生关联,从而使AppBarLayout内部的子View执行相关联的滑动动作,
内部子View可以是Toolbar、任何View或者布局
注意:AppbarLayout 严重依赖于CoordinatorLayout,必须用于CoordinatorLayout 的直接子View,如果你将AppbarLayout 放在其他的ViewGroup 里面,那么它的这些功能是无效的
2.AppBarLayout如何与可滚动的View关联:
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<!--将你的内容放在这里-->
</android.support.v4.widget.NestedScrollView>
属性:app:layout_behavior="@string/appbar_scrolling_view_behavior"是CoordinatorLayout的布局属性,它能够将此View与AppBarLayout关联,指定Behavior的,appbar_scrolling_view_behavior对应的类的名称是:android.support.design.widget.AppBarLayout$ScrollingViewBehavior
3.AppBarLayout可以与那些View关联
3.1,根据概念首先这个View必须是可以滚动的如ScrollView RecyclerView等
3.2,还有一个就是此View必须实现NestedScrollingChild接口
所以ScrollView ListView GridView是不能直接和AppBarLayout联合使用的,需要自己实现NestedScrollingChild接口后才行
已经实现此接口的如:RecyclerView,NestedScrollView等
4.AppBarLayout内部子View如何执行动作
1.内部的子View通过在布局中加app:layout_scrollFlags设置执行的动作
layout_scrollFlagsyou 有如下几个值:
- 1、 scroll ,子View 添加layout_scrollFlags属性 的值scroll 时,这个View将会随着可滚动View(如:NestedScrollView,NestedScrollView 来代替可滚动的View )一起滚动,就好像子View 是属于ScrollView的一部分一样。
- 2、 enterAlways ,子View 添加layout_scrollFlags属性 的值有enterAlways 时, NestedScrollView 滑动时,子View将剥夺滑动事件先执行动作后 ,NestedScrollView 再滑动。 注意:要与scroll 搭配使用,否者是不能滑动的。
- 3、 enterAlwaysCollapsed ,必须配合scroll|enterAlways 一起使用, enterAlwaysCollapsed 是对enterAlways 的补充, NestedScrollView 向上滑动和Scroll,enterAlways效果一样 ,向下滑动时 滑动View先下滑到最小高度 minHeight(最小高度)指定的,然后让NestedScrollView滑动到顶点后,滑动View再继续下滑到最大值。
- 4、exitUntilCollapsed,必须配合scroll 当NestedScrollView向上滑动时,滑动View先响应滑动事件,滑动至最小高度,(也就是通过minHeight 设置的最小高度)后,就固定不动了,再把滑动事件交给 NestedScrollView 继续滑动。
- 5、snap,意思是:会给滑动view的滑动事件添加一个自动滚动属性,
在滚动结束后,view滑动到部分可见时,如果隐藏区域比显示区域大则它将滚动离开屏幕,显示区域比影藏区域大,它将自动滚动到完全显示。必须配合scroll使用
5.app:layout_scrollInterpolator属性指定滚动动画效果的插值器.
6.AppBarLayout常用方法
- addOnOffsetChangedListener 当AppbarLayout 的偏移发生改变的时候回调,也就是子View滑动。
- getTotalScrollRange 返回AppbarLayout 所有子View的滑动范围
- removeOnOffsetChangedListener 移除监听器
- setExpanded (boolean expanded, boolean animate)设置AppbarLayout 是展开状态还是折叠状态,animate 参数控制切换到新的状态时是否需要动画
- setExpanded (boolean expanded) 设置AppbarLayout 是展开状态还是折叠状态,默认有动画
特殊场景
禁止单独触摸AppBarLayout而滑动展开AppBarLayout
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
AppBarLayout.Behavior behavior = (AppBarLayout.Behavior) params.getBehavior();
behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
@Override
public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
return false;
}
});
通过总是返回 false ,您滚动 view 不会再由 ABL 控制。
注︰之前调用此你应该检查, ViewCompat.isLaidOut(appBarLayout) ,否则为 params.getBehavior() 将返回 null。
[images](https://github.com/bux-git/MeterialDesignStudy/raw/master/imges/collspasingtoolbarlayout01.gif1)
一.概念
CollapsingToolbarLayout是用来对Toolbar进行再次包装的ViewGroup,主要是用于实现折叠的App Bar效果。
它需要作为AppBarLayout的直接子View,并且需要作为AppBarLayout的关联滑动View
CollapsingToolbarLayout包含以下功能:
-
1.Collapsing title(折叠标题) 当布局全部可见的时候,title 是最大的,当布局开始滑出屏幕,title 将变得越来越小,你可以通过setTitle(CharSequence) 来设置要显示的标题
注意:Toolbar 和CollapsingToolbarLayout 同时设置了title时,不会显示Toolbartitle而是显示CollapsingToolbarLayout 的title,如果要显示Toolbar 的title,你可一在代码中添加如下代码:collapsingToolbarLayout.setTitle("");
-
2.Content scrim(内容纱布) 当CollapsingToolbarLayout滑动到一个确定的阀值时将显示或者隐藏内容纱布,可以通过setContentScrim(Drawable)来设置纱布的图片。
提醒:纱布可以是图片也可以是颜色色值,如果要显示颜色,在xml 布局文件中用contentScrim属性添加,代码如下: app:contentScrim="@color/colorPrimary"
- 3.Status bar scrim(状态栏纱布) 当CollapsingToolbarLayout滑动到一个确定的阀值时,状态栏显示或隐藏纱布,你可以通过setStatusBarScrim(Drawable)来设置纱布图片。
注意:同内容纱布一样,状态栏纱布可以是图片也可以是一个颜色值,如果要显示颜色值,在xml 中用statusBarScrim 属性指定。
- 4.Parallax scrolling children(有视差地滚动子View) 让CollapsingToolbarLayout 的子View 可以有视差的滚动,需要在xml中用 添加如下代码:
app:layout_collapseMode="parallax"
- 5.Pinned position children(固定子View的位置)子View可以固定在全局空间内,这对于实现了折叠并且允许通过滚动布局来固定Toolbar 这种情况非常有用。在xml 中将collapseMode设为pin,代码如下:
app:layout_collapseMode="pin"
-
其他属性:
collapsedTitleGravity:折叠时Toolbar标题位置
expandedTitleGravity:展开时Toolbar标题位置
titleEnabled:滑动时,设置是否应该显示自己的标题。标题将根据滚动偏移而收缩和增长。默认为true
CollapsingToolbarLayout使用时
1.作为AppBarLayout的子View并设置滑动关联动作如: app:layout_scrollFlags="scroll|exitUntilCollapsed"
2.根据需求设置自身标题等一些相关属性
3.添加Toolbar和其他子View
1.概念
TextInputLayout 将EditText包裹起来能够辅助EditText实现一些如hint 以浮动标签的形式显示出来,同时可以通过setErrorEnabled(boolean)和setError(CharSequence)来显示错误信息等
每一个TextInputLayout中只能有一个EditText
XML属性&常用方法
-
counterEnabled 对应方法 setCounterEnabled(boolean)
用于设置字符计数器的显示与隐藏,会在布局右下角显示输入字符的进度:1/10这样
-
counterMaxLength 对应方法 setCounterMaxLength(int)
设置字符计数器的最大长度。(仅用于设置计数器最大值,并不影响文本实际能输入的最大长度)
-
errorEnabled 对应方法 setErrorEnabled(boolean)
用于设置错误提示是否显示
-
hint 对应方法 setHint(CharSequence)
设置输入框的提示语
-
hintAnimationEnabled 对应方法 setHintAnimationEnabled(boolean)
开启或关闭hint浮动成标签的动画效果
-
hint 对应方法 setHint(CharSequence)
设置输入框的提示语
-
hintEnabled 对应方法 setHintEnabled(boolean)
开启或关闭hint浮动的功能,设为false的话就和之前的EditText一样,在输入文字后,提示语就消失了
-
hintTextAppearance 对应方法 setHintTextAppearance(int)
设置hint的style,字体颜色,字体大小等,可引用系统自带的也可以自定义。若要使用请统一使用,以保证APP体验的统一性
当文本输入类型为密码时,系统提供了一个开关来控制密码是否可见(默认为眼睛👁)。此为design包24.0.2新提供的功能。
-
passwordToggleEnabled 对应方法 setPasswordVisibilityToggleEnabled(boolean)
控制密码可见开关是否启用。设为false则该功能不启用,密码输入框右侧也没有控制密码可见与否的开关
-
passwordToggleDrawable 对应方法 setPasswordVisibilityToggleDrawable(Drawable)
设置密码可见开关的图标。通常我们会在不同的情况下设定不同的图标,可通过自定义一个selector,根据“state_checked”属性来控制图标的切换
-
passwordToggleTint 对应方法 setPasswordVisibilityToggleTintList(ColorStateList)
控制密码可见开关图标的颜色。在开启或关闭的状态下我们可以设定不同的颜色,可通过自定义一个color的selector,根据“state_checked”和“state_selected”属性来控制颜色的切换
-
hintEnabled 对应方法 setHintEnabled(boolean)
开启或关闭hint浮动的功能,设为false的话就和之前的EditText一样,在输入文字后,提示语就消失了
一.概念
Snackbar 是一种针对操作的轻量级反馈机制,常以一个小的弹出框的形式,出现在手机屏幕下方或者桌面左下方。它们出现在屏幕所有层的最上方,包括浮动操作按钮。 它们会在超时或者用户在屏幕其他地方触摸之后自动消失。Snackbar 可以在屏幕上滑动关闭。当它们出现时,不会阻碍用户在屏幕上的输入,并且也不支持输入。屏幕上同时最多只能现实一个 Snackbar。
综上所述: 1.SnackBar可以自动消失,也可以手动取消
2.SnackBar可以通过setAction()来与用户进行交互
3.通过CallBack我们可以获取SnackBar的状态
二.使用方法
Snackbar用法:Snackbarmake(@NonNull View view, @NonNull CharSequence text,@Duration int duration).show();
View:SnackBar显示,它需要有一个View来承载SnackBar会遍历整个View Tree来找到一个合适的View承载SnackBar的View,
如果你想要实现上面的动画交互效果的话最好是在布局中包括CoordinatorLayout,假如你的布局中不包括CoordinatorLayout是不会有动画效果的
text:
SnackBar显示文字
duration
有三种状态:
Snackbar.LENGTH_SHORT// 短时间显示,然后自动取消
Snackbar.LENGTH_LONG// 长时间显示,然后自动取消
Snackbar.LENGTH_INDEFINITE// 不消失显示,除非手动取消
make方法源码:
public static Snackbar make(@NonNull View view, @NonNull CharSequence text,
@Duration int duration) {
Snackbar snackbar = new Snackbar(findSuitableParent(view));
snackbar.setText(text);
snackbar.setDuration(duration);
return snackbar;
}
其实这里面的重点就是Snackbarsnackbar =newSnackbar(findSuitableParent(view));
我们可以看到我们传入的view经过了
findSuitableParent()方法的包装。
这个方法主要的作用是:
1.当传入的View不为空时,如果我们在布局中发现了CoordinatorLayout布局,那么返回的View就是CoordinatorLayout;
2.如果没有CoordinatorLayout布局,我们就先找到一个id为android.R.id.content的FrameLayout(这个布局是最底层的根布局),
将View设置为该FrameLayout;
3.其他情况下就使用View的Parent布局一直到这个View不为空。
//获取实例
Snackbar snackbar= Snackbar.make(mBtnShow,"测试",Snackbar.LENGTH_LONG);
//设置右侧点击事件
snackbar.setAction("编辑", new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(SnackBarActivity.this,"点击",Toast.LENGTH_SHORT).show();
}
});
//设置点击文字颜色
snackbar.setActionTextColor(getResources().getColor(R.color.black));
//设置SnackBar 背景颜色
snackbar.getView().setBackgroundColor(getResources().getColor(R.color.colorAccent));
//添加显示与消失监听
snackbar.addCallback(new BaseTransientBottomBar.BaseCallback<Snackbar>() {
@Override
public void onDismissed(Snackbar transientBottomBar, int event) {
super.onDismissed(transientBottomBar, event);
}
@Override
public void onShown(Snackbar transientBottomBar) {
super.onShown(transientBottomBar);
}
});
//显示SnackBar
snackbar.show();
Snackbars 与 Toasts
SnackBar使用详解
浅谈FloatingActionButton(悬浮按钮)
浮动操作按钮详解
一.概念
译为:协调者布局
1.首先可以理解Coordinatorlayout是一个FrameLayout升级版本
2.重要功能:CoordinatorLayout可以用来协调其子view之间动作的交互
如:协调滑动控件和AppBarLayout之间的交互等等,
CoordinatorLayout 实现子View之间的交互是靠Behavior来实现的
二.使用
CoordinatorLayout子View之间是如何协调的:
1.根据前面与:
3.AppbarLayout
4.CollapsingToolbarLayout
6.SnackBar
6.FloatingActionButton
等使用的情况,子View之间相互协调是通过CoordinatorLayout的布局属性app:layout_behavior来设置的
layout_behavior 属性定义了这个View如何和其他View互相交互的行为, 其值填写的是一个class的名字(全称带包名) 这个值指定的类必须是 CoordinatorLayout.Behavior 的子类, 我们也可以自定义一个该类继承于它, 以此来写自己想要的交互效果.
三.Behavior
1.概述
CoordinatorLayout的诸多功能全部依赖与CoordinatorLayout.Behavior来实现
通过为CoordinatorLayout的直接子view设置一个Behavior,CoordinatorLayout会遍历一遍自己的直接子View,
一个一个的调用子view中的Behavio就可以拦截touch events, window insets, measurement, layout, 和 nested scrolling等动作。
Design Library大量利用了Behaviors来实现你所看到的功能
2.Behavior的创建
2.1、创建behavior,需要继承 CoordinatorLayout.Behavior 或其子View
public class FollowUpDownBehavior extends CoordinatorLayout.Behavior {
public FollowUpDownBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
这样就可以将这个Behavior设置给任何View,如果只想设置给某一些特定类型的View则可以传入泛型如:
FollowUpDownBehavior extends CoordinatorLayout.Behavior<Button>
3.设置Behavior
设置Behavior一共有3种方式:
3.1 在代码中设置Behavior
CoordinatorLayout.LayoutParam中可以存储Behavior布局属性,所以在代码中:
FollowUpDownBehavior behavior = new FollowUpDownBehavior();
CoordinatorLayout.LayoutParams layoutParams= (CoordinatorLayout.LayoutParams) view.getLayoutParams();
view.setLayoutParams(layoutParams);
3.2 在XML中设置
在xml布局中直接设置属性值
<View android:layout_width="50dp"
android:layout_height="20dp"
android:background="@color/black"
app:layout_behavior="com.dqr.www.meterialdesignstudy.coordinatorlayout.behavior.FollowUpDownBehavior"
app:target="@id/moveView"></View>
在XML设置属性,初始化Behavior时,是使用的FollowUpDownBehavior(Context context, AttributeSet attrs)
两个参数的构造函数,有传入AttributeSet所以可以自定义一些属性然后在xml中设置,Behavior中接收并使用,
如app:target属性设置一个目标ID
3.3 在View类上添加默认的Behavior
在自定义View时我们希望这个View自带一个Behavior,而不需要去另外设置,我们可以在自定义View类上设置
注释:
@CoordinatorLayout.DefaultBehavior(BtnTestBehavior.class)
public class TempView extends View {
}
Behavior的功能
1.拦截Touch Events
CoordinatorLayout会在他的onInterceptTouchEvent()中将事件MotionEvent传递到子View的
Behavior.onInterceptTouchEvent()中,让Behavior也可以拦截触摸事件,
如果Behavior.onInterceptTouchEvent()返回true,则Behavior.onTouchEvent()将会收到
后续触摸事件,而View将不会收到后续的触摸事件
CoordinatorLayout的事件分发过程
首先ViewGroup/View 的事件分派, 事件分派是有两个过程的: 深入理解CoordinatorLayout原文
捕获过程:从根元素到子元素依次调用onInterceptTouchEvent,检测是否有View要拦截触摸事件 如果有View拦截了立即进入冒泡过程,否则一直传递到最末尾的元素再进入到冒泡过程.
冒泡过程:从底层往上冒泡,一次调用onTouchEvent,如果有View消耗了事件,则不再继续向上传递,否则一直传递 到根元素.
CoordinatorLayout事件分发
CoordinatorLayout在他的onInterceptTouchEvent中去遍历所有的子View,并调用子View的Behavior.onInterceptTouchEvent方法 如果在Behavior.onInterceptTouchEvent方法中返回了true拦截了该事件,则该Behavior就可以在onTouchEvent
中处理触摸事件,而这个Behavior对应的View将不会收到触摸时间
这样的设置可以使得处理例如手势的逻辑可以完全从具体的某个View解耦出来,可以给不同的View设置相同的
Behavior来获得处理相同手势的功能,代码复用率极高.
2.子View之间的依赖
Behaviors的强大之处在于在View之间建立依赖关系-当另一个View改变的时候,你的Behavior会得到一个callback,根据外部条件改变它的功能
在 CoordinatorLayout.LayoutParams 中定义了一个View是否依赖( dependsOn ) 另一个View:
boolean dependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency == mAnchorDirectChild
|| (mBehavior != null && mBehavior.layoutDependsOn(parent, child, dependency));
}
Behaviors依赖于View有两种形式:
1.在Behavior.layoutDependsOn()中返回true
2.使用CoordinatorLayout的layout_anchor 属性之时。它和layout_anchorGravity 属性结合,可以让你有效的把两个View捆绑在一起。比如,你可以把一个FloatingActionButton锚定在一个AppBarLayout上,那么如果AppBarLayout滚动出屏幕,FloatingActionButton.Behavior将使用隐式的依赖去隐藏FAB
View之间关联之后,当依赖View被移除的时候,将会回调Behavior.onDependentViewRemoved() 当依赖的View发生变化的时候(比如:调整大小或者重置自己的position),得到回调 onDependentViewChanged()
我们可以在这2个方法中处理关于自身View的一些事情这个把View绑定在一起的能力正是Design Library那些酷炫功能的工作原理
-以FloatingActionButton与Snackbar之间的交互为例。FAB的 Behavior依赖于被添加到CoordinatorLayout的Snackbar,. 然后它使用onDependentViewChanged() callback来将FAB向上移动,以避免和Snackbar重叠。
CoordinatorLayout的三个子View A B C之间的依赖可以有如下几种:
- 可以 多个View同时依赖同一个View: B C 同时依赖A
- 被依赖的View可以继续依赖其他View A 依赖B B 依赖C
- 也可以单独依赖 A 依赖 B
- 但是不能循环依赖 :A 依赖B B 依赖C C依赖A
3.嵌套滚动
NestedScrolling 是Android提供的一套父View和子View交互滑动机制.
完成这样的交互 需要父View实现NestedScrollingParent接口,子View实现NestedScrollingChild接口
同时系统也提供了2辅助类来帮助处理子View和父View交互的大部分逻辑:
NestedScrollingParent--->NestedScrollingParentHelper
NestedScrollingChild--->NestedScrollingChildHelper
NestedScrollingChild & NestedScrollingChildHelper
public interface NestedScrollingChild {
/**
* 设置嵌套滑动是否可用
*
* @param enabled
*/
public void setNestedScrollingEnabled(boolean enabled);
/**
* 嵌套滑动是否可用
*
* @return
*/
public boolean isNestedScrollingEnabled();
/**
* 开启整个嵌套滑动流程,通知父View一起处理TouchEvent事件
*
* @param axes 表示方向 有一下两种值
* ViewCompat.SCROLL_AXIS_HORIZONTAL 水平方向滑动
* ViewCompat.SCROLL_AXIS_VERTICAL 纵向滑动
* @return 父View是否支持嵌套滑动
*/
public boolean startNestedScroll(int axes);
/**
* 结束嵌套滑动流程
*/
public void stopNestedScroll();
/**
* 是否有父View 支持 嵌套滑动, 会一层层的往上寻找父View
* @return
*/
public boolean hasNestedScrollingParent();
/**
* 向父view汇报滚动情况,包括子view消费的部分和子view没有消费的部分。
* 如果父view接受了它的滚动参数,进行了部分消费,则这个函数返回true,否则为false。
* 这个函数一般在子view处理scroll后调用。
* @param dxConsumed x轴上 被父View消费的距离
* @param dyConsumed y轴上 被父View消费的距离
* @param dxUnconsumed x轴上 未被消费的距离
* @param dyUnconsumed y轴上 未被消费的距离
* @param offsetInWindow view 的移动距离
* @return
*/
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);
/**
* 在子View的onInterceptTouchEvent或者onTouch中(一般在MontionEvent.ACTION_MOVE事件里),
* 调用该方法将子View此次滑动的距离通知给父View做处理
* @param dx x 轴上滑动的距离, 相对于上一次事件, 不是相对于 down事件的 那个距离
* @param dy y 轴上滑动的距离
* @param consumed 数组 输出参数,返回父View消耗掉的滑动长度
* 如果传入不为null,cosumed[0]表示父View在X方向上消费掉的Scroll距离
* cosumes[1]表示父View在Y方向上消费掉的scroll距离
* 如果这两个值不为0,则子view需要对滚动的量进行一些修正
* @param offsetInWindow 支持嵌套滑动到额父View 消费 滑动事件后 导致 本 View 的移动距离
* @return 如果父View 接收了滚动参数,并进行了消费,则返回true 否则为false
这个函数一般在子view处理scroll前调用
*/
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);
/**
*
* @param velocityX x 轴上的滑动速度
* @param velocityY y 轴上的滑动速度
* @param consumed 是否被消费
* @return
*/
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
/**
*
* @param velocityX x 轴上的滑动速度
* @param velocityY y 轴上的滑动速度
* @return
*/
public boolean dispatchNestedPreFling(float velocityX, float velocityY);
}
使用:
public class Child extends ViewGroup implements NestedScrollingChild {
private NestedScrollingChildHelper mNestedScrollingChildHelper;
public Child(Context context, AttributeSet attrs) {
super(context, attrs);
mNestedScrollingChildHelper = new NestedScrollingChildHelper(this);
}
@Override
public void setNestedScrollingEnabled(boolean enabled) {
mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled);
}
@Override
public boolean isNestedScrollingEnabled() {
return mNestedScrollingChildHelper.isNestedScrollingEnabled();
}
@Override
public boolean startNestedScroll(int axes) {
return mNestedScrollingChildHelper.startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {
mNestedScrollingChildHelper.stopNestedScroll();
}
@Override
public boolean hasNestedScrollingParent() {
return mNestedScrollingChildHelper.hasNestedScrollingParent();
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return mNestedScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
}
/**
*重写这个方法或者onInterceptTouchEvent
*在这里面开启嵌套滑动相关方法并处理相关逻辑
*具体可以参考现有的RecyclerView NestedScrollView 等
*
*/
@Override
public boolean onTouchEvent(MotionEvent e) {
1.startNestedScroll(nestedScrollAxis);
2.dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset);
3.stopNestedScroll();
return super.onTouchEvent(e);
}
}
NestedScrollingParent & NestedScrollingParentHelper
public interface NestedScrollingParent {
/**
* 当子view的调用NestedScrollingChild的方法startNestedScroll时,会调用该方法
* 该方法决定了当前控件是否能接收到其内部View(直接子View或者子View的子View)滑动时的参数
* @param child ViewParent 的直接子View 该View 或者是他的子View实现了NestedScrollingChild
* @param target 实现了NestedScrollingChild的View (在这里如果不涉及多层嵌套的话,child和target)
* @param nestedScrollAxes 嵌套滚动的滚动方向 Flags consisting of {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
* {@link ViewCompat#SCROLL_AXIS_VERTICAL} or both
* @return 是否接受此次嵌套滑动
*/
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
/**
*
*如果onStartNestedScroll方法返回true,之后就会调用该方法.它是让嵌套滚动在开始滚动之前,让布局容器(viewGroup)或者它的父类执行一些配置的初始化
* @param child Direct child of this ViewParent containing target
* @param target View that initiated the nested scroll
* @param nestedScrollAxes Flags consisting of {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
* {@link ViewCompat#SCROLL_AXIS_VERTICAL} or both
* @see #onStartNestedScroll(View, View, int)
* @see #onStopNestedScroll(View)
*/
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
/**
* 当子view调用stopNestedScroll时会调用该方法,停止滚动
* React to a nested scroll operation ending.
*
* <p>Perform cleanup after a nested scrolling operation.
* This method will be called when a nested scroll stops, for example when a nested touch
* scroll ends with a {@link MotionEvent#ACTION_UP} or {@link MotionEvent#ACTION_CANCEL} event.
* Implementations of this method should always call their superclass's implementation of this
* method if one is present.</p>
*
* @param target View that initiated the nested scroll
*/
public void onStopNestedScroll(View target);
/**
* 当子view调用dispatchNestedScroll方法时,会调用该方法
* @param target 实现了NestedScrollingChild的View
* @param dxConsumed 表示target已经消费的x方向的距离
* @param dyConsumed 表示target已经消费的y方向的距离
* @param dxUnconsumed 表示x方向剩下的滑动距离
* @param dyUnconsumed 表示y方向剩下的滑动距离
*/
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed);
/**
*
*consumed:表示父布局要消费的滚动距离,consumed[0]和consumed[1]分别表示父布局在x和y方向上消费的距离.
当子view调用dispatchNestedPreScroll方法是,会调用该方法
* @param target 实现了NestedScrollingChild的View
* @param dx 表示target本次滚动产生的x方向的滚动总距离
* @param dy 表示target本次滚动产生的y方向的滚动总距离
* @param consumed Output. The horizontal and vertical scroll distance consumed by this parent
*/
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
/**
* Request a fling from a nested scroll.
*
* <p>This method signifies that a nested scrolling child has detected suitable conditions
* for a fling. Generally this means that a touch scroll has ended with a
* {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
* the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
* along a scrollable axis.</p>
*
* <p>If a nested scrolling child view would normally fling but it is at the edge of
* its own content, it can use this method to delegate the fling to its nested scrolling
* parent instead. The parent may optionally consume the fling or observe a child fling.</p>
*
* @param target View that initiated the nested scroll
* @param velocityX Horizontal velocity in pixels per second
* @param velocityY Vertical velocity in pixels per second
* @param consumed true if the child consumed the fling, false otherwise
* @return true if this parent consumed or otherwise reacted to the fling
*/
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
/**
* React to a nested fling before the target view consumes it.
*
* <p>This method siginfies that a nested scrolling child has detected a fling with the given
* velocity along each axis. Generally this means that a touch scroll has ended with a
* {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
* the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
* along a scrollable axis.</p>
*
* <p>If a nested scrolling parent is consuming motion as part of a
* {@link #onNestedPreScroll(View, int, int, int[]) pre-scroll}, it may be appropriate for
* it to also consume the pre-fling to complete that same motion. By returning
* <code>true</code> from this method, the parent indicates that the child should not
* fling its own internal content as well.</p>
*
* @param target View that initiated the nested scroll
* @param velocityX Horizontal velocity in pixels per second
* @param velocityY Vertical velocity in pixels per second
* @return true if this parent consumed the fling ahead of the target view
*/
public boolean onNestedPreFling(View target, float velocityX, float velocityY);
/**
* Return the current axes of nested scrolling for this NestedScrollingParent.
*
* <p>A NestedScrollingParent returning something other than {@link ViewCompat#SCROLL_AXIS_NONE}
* is currently acting as a nested scrolling parent for one or more descendant views in
* the hierarchy.</p>
*
* @return Flags indicating the current axes of nested scrolling
* @see ViewCompat#SCROLL_AXIS_HORIZONTAL
* @see ViewCompat#SCROLL_AXIS_VERTICAL
* @see ViewCompat#SCROLL_AXIS_NONE
*/
public int getNestedScrollAxes();
}
整个嵌套滑动的流程child对应parent:
子view | 父view |
---|---|
startNestedScroll | onStartNestedScroll、onNestedScrollAccepted |
dispatchNestedPreScroll | onNestedPreScroll |
dispatchNestedScroll | onNestedScroll |
stopNestedScroll | onStopNestedScroll |
未完 |
自定义Behavior的艺术探索-仿UC浏览器主页
Android嵌套滑动机制(NestedScrolling)
4.拦截Window Insets
5.拦截Measurement 和 layout
拦截一切的CoordinatorLayout Behavior
Material Design之 AppbarLayout 开发实践总结
玩转AppBarLayout,更酷炫的顶部栏
CoordinatorLayout, AppBarLayout, CollapsingToolbarLayout使用
深入理解CoordinatorLayout
自定义Behavior的艺术探索-仿UC浏览器主页
一个神奇的控件——Android CoordinatorLayout与Behavior使用指南
CoordinatorLayout的使用如此简单
拦截一切的CoordinatorLayout Behavior