MaterialDesign系列文章(十七)Behavior的相
参考文献:(感谢作者的开源精神)
严振杰的博客
希小风的博客
1.Behavior介绍
Behavior是Android新出的Design库里新增的布局概念。Behavior只有是CoordinatorLayout的直接子View才有意义。可以为任何View添加一个Behavior。
Behavior是一系列回调。让你有机会以非侵入的为View添加动态的依赖布局,和处理父布局(CoordinatorLayout)滑动手势的机会。如果我们想实现控件之间任意的交互效果,完全可以通过自定义 Behavior 的方式达到。
Behavior官方提供的app:layout_behavior属性
关于这个我仔细找了一下就找到了两个
- appbar_scrolling_view_behavior 这个是appBarLayout的一个子类android.support.design.widget.AppBarLayout$ScrollingViewBehavior中提供的
- bottom_sheet_behavior 这个是单独的一个类中实现的android.support.design.widget.BottomSheetBehavior
这里说明了两个问题:
- 第一这个字符串设置的值应该是类的全路径名称
- 第二这个类可以自定义(但是自定义的时候应该也指定全路径名称)
2.Behavior的自定义
这里我准备按照希小风的博客风格去逐步实
其实Behavior就是一个应用于View的观察者模式,一个View跟随着另一个View的变化而变化,或者说一个View监听另一个View,在Behavior中,被观察View也就是事件源被称为denpendcy,而观察View被成为child
这里先贴出继承继承CoordinatorLayout.Behavior<V extends View>常用的方法
/**
* 表示是否给应用了Behavior 的View 指定一个依赖的布局,通常,当依赖的View 布局发生变化时
* 不管被被依赖View 的顺序怎样,被依赖的View也会重新布局
* @param parent
* @param child 绑定behavior 的View
* @param dependency 依赖的view
* @return 如果child 是依赖的指定的View 返回true,否则返回false
*/
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return super.layoutDependsOn(parent, child, dependency);
}
/**
* 当被依赖的View 状态(如:位置、大小)发生变化时,这个方法被调用
* @param parent
* @param child
* @param dependency
* @return
*/
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
return super.onDependentViewChanged(parent, child, dependency);
}
/**
* 当coordinatorLayout 的子View试图开始嵌套滑动的时候被调用。当返回值为true的时候表明
* coordinatorLayout 充当nested scroll parent 处理这次滑动,需要注意的是只有当返回值为true
* 的时候,Behavior 才能收到后面的一些nested scroll 事件回调(如:onNestedPreScroll、onNestedScroll等)
* 这个方法有个重要的参数nestedScrollAxes,表明处理的滑动的方向。
*
* @param coordinatorLayout 和Behavior 绑定的View的父CoordinatorLayout
* @param child 和Behavior 绑定的View
* @param directTargetChild
* @param target
* @param nestedScrollAxes 嵌套滑动 应用的滑动方向,看 {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
* {@link ViewCompat#SCROLL_AXIS_VERTICAL}
* @return
*/
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
/**
* 嵌套滚动发生之前被调用
* 在nested scroll child 消费掉自己的滚动距离之前,嵌套滚动每次被nested scroll child
* 更新都会调用onNestedPreScroll。注意有个重要的参数consumed,可以修改这个数组表示你消费
* 了多少距离。假设用户滑动了100px,child 做了90px 的位移,你需要把 consumed[1]的值改成90,
* 这样coordinatorLayout就能知道只处理剩下的10px的滚动。
* @param coordinatorLayout
* @param child
* @param target
* @param dx 用户水平方向的滚动距离
* @param dy 用户竖直方向的滚动距离
* @param consumed
*/
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
}
/**
* 进行嵌套滚动时被调用
* @param coordinatorLayout
* @param child
* @param target
* @param dxConsumed target 已经消费的x方向的距离
* @param dyConsumed target 已经消费的y方向的距离
* @param dxUnconsumed x 方向剩下的滚动距离
* @param dyUnconsumed y 方向剩下的滚动距离
*/
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
}
/**
* 嵌套滚动结束时被调用,这是一个清除滚动状态等的好时机。
* @param coordinatorLayout
* @param child
* @param target
*/
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
super.onStopNestedScroll(coordinatorLayout, child, target);
}
/**
* onStartNestedScroll返回true才会触发这个方法,接受滚动处理后回调,可以在这个
* 方法里做一些准备工作,如一些状态的重置等。
* @param coordinatorLayout
* @param child
* @param directTargetChild
* @param target
* @param nestedScrollAxes
*/
@Override
public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
/**
* 用户松开手指并且会发生惯性动作之前调用,参数提供了速度信息,可以根据这些速度信息
* 决定最终状态,比如滚动Header,是让Header处于展开状态还是折叠状态。返回true 表
* 示消费了fling.
*
* @param coordinatorLayout
* @param child
* @param target
* @param velocityX x 方向的速度
* @param velocityY y 方向的速度
* @return
*/
@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
//可以重写这个方法对子View 进行重新布局
@Override
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
return super.onLayoutChild(parent, child, layoutDirection);
}
2.1Button与TextView联动
这里主要是自定义了一个Behavior
/**
* 作者 : 贺金龙
* 创建时间 : 2017/11/8 11:33
* 类描述 : 这个是第一个简单的自定义EasyBehavior
* 类说明 : 这里的泛型应给是被观察者,也就是child
*/
public class EasyBehavior extends CoordinatorLayout.Behavior<TextView> {
public EasyBehavior(Context context, AttributeSet attrs) {
/*这里说明一下这个构造方法一定要些上,否则会报错的*/
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
/*这里主要是说明观察者是什么类型的,如果返回true说明是观察者观察的View否则返回false也就不会产生联动了*/
return dependency instanceof Button;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency) {
/*这里主要是观察者位置改变的时候,被观察者的位置在观察者位置的基础上响应的增加了200*/
child.setX(dependency.getX() + 200);
child.setY(dependency.getY() + 200);
child.setText(dependency.getX() + "," + dependency.getY());
return true;
}
}
注释已经写的很详细了,所以这里就不在赘述了...
2.2仿UC首页折叠的Behavior效果
这个效果可以下载一个UC去看一下,其实这个效果基本上就是顶上放一个TextView和AppBarLayout中底部ToolBar进行
联动(其实我觉得就是通过这两个都是通过计算位置进行处理的),这里我就粘一下代码了,这里一看就能懂
清单文件:
<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:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="RtlHardcoded">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:elevation="0dp">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<ImageView
android:layout_width="match_parent"
android:layout_height="300dp"
android:scaleType="centerCrop"
android:src="@mipmap/ic_launcher"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.9"/>
<FrameLayout
android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_gravity="bottom|center_horizontal"
android:background="@color/colorPrimaryDark"
android:orientation="vertical"
app:layout_collapseMode="parallax"
app:layout_collapseParallaxMultiplier="0.3">
</FrameLayout>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none"
app:behavior_overlapTop="30dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<include layout="@layout/uc_behavior"/>
</android.support.v4.widget.NestedScrollView>
<android.support.v7.widget.Toolbar
android:id="@+id/main.toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimaryDark"
app:layout_anchor="@id/frameLayout"
app:theme="@style/ThemeOverlay.AppCompat.Dark"
app:title="这个是toolBar的标题">
</android.support.v7.widget.Toolbar>
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/colorPrimaryDark"
android:gravity="center"
android:textColor="#fff"
android:textSize="18sp"
app:layout_behavior="com.hejin.materialdesign.behavior.ToolBarBehavior"/>
</android.support.design.widget.CoordinatorLayout>
这里注意一点啊,里面有一个属性app:layout_anchor="@id/frameLayout"这个属性代表有依附的意思,简单的说就是通过依附达到共同享用滑动事件的意思,也就是说上面的FramLayout滑动的时候ToolBar就会一起跟着滑动的,这里依附的话,会在被依附的控件的最上边
behavior文件
/**
* 作者 : 贺金龙
* 创建时间 : 2017/11/8 14:41
* 类描述 : 实现ToolBar和TextView联动的Behavior
* 类说明 :
*/
public class ToolBarBehavior extends CoordinatorLayout.Behavior<TextView> {
private int mStartY;
public ToolBarBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
return dependency instanceof Toolbar;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency) {
if (mStartY == 0) {/*这里获取的是点击处对控件顶部的距离*/
mStartY = (int) dependency.getY();
}
/*计算ToolBar从开始引动到最后的百分比,也就是ToolBar的当前高度比上总高度*/
float percent = dependency.getY() / mStartY;
/*改变child的坐标(从消失到可见)*/
child.setY(child.getHeight() * (1 - percent) - child.getHeight());
return true;
}
}
2017年11月09日添加:
自定义简书的Behavior
public class BottomBehavior extends CoordinatorLayout.Behavior<View> {
public BottomBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency instanceof AppBarLayout;
}
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
float translationY = Math.abs(dependency.getTop());//获取更随布局的顶部位置
child.setTranslationY(translationY);
return true;
}
}
这里写了好久都没有成功,后来我发现了一个问题,toolBar在移动的时候是嵌套在AppBarLayout中的,所以你是监听不到,因此这里不能使用ToolBar而是使用的AppBarLayout
感觉要是真的像自定义一些很难的还是不行,加强学习吧
2018年03月19日补充
其实关于自定义Behavior的内容,之前自己了解的不够,今天补充一些内容
补充说明1:
首先你要理解那个是依赖的View,那个是被观察的View(我这里是这么理解的)
上面所有的child代表的是被观察的View(也就是绑定的View,说白了就是在xml布局中设置layout_behavior的那个View)
补充说明2:
这里面有一个API->ViewCompat.offsetTopAndBottom(view, offset);
这个方法的意思是,使view移动相应的位置,位置的大小取决于offset,向下为正,向上为负,这里面还有左右移动的,api和这个类似,自己找一下就可以了
补充说明3:
- 一般处理两个View之间的移动的时候,都会用到
boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency)
这个方法是处理相应的依赖关系的,也就是上面说的依赖和被观察的关系
onDependentViewChanged(CoordinatorLayout parent, TextView child, View dependency)
这个方法是处理相应位置的改变的一些内容的,说简单点就是当你被观察的View位置什么的发生改变就会回调这个方法.
2.当处理滑动的时候会用到几个相应的方法
- boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type)
这个是在你手指触碰到控件的时候调用的方法,这里面参数有必要说明一下:
参数1:coordinatorLayout对象,这个没有什么好说的
参数2:child这个是相应的被观察者,也就是要被移动的那个view
参数3:directTargetChild我理解这个参数是滑动的直接子View(这个我不太确定)
参数4:target这个是被观察的View
参数5:代表是水平滑动还是竖直滑动的一个类型值取值包含ViewCompat#SCROLL_AXIS_HORIZONTAL\ViewCompat#SCROLL_AXIS_VERTICAL分别对应着竖直和水平滑动,
参数6:代表一个type的值,没用到可能是下面的那个方法做区分用的吧;
这里引用一个判断竖直滚动的例子
@Override
public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
- void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type)
这个方法是滚动的时候调用的,当滚动发生的时候,这里计算出了相应的数值,还是简单的说明一下不一样的参数吧!
参数4参数5:dx代表x/y轴的速度值
参数6我也没弄明白是什么,我打印的时候一直是0
这里引用一个联动的例子
@Override
public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
int leftScrolled = target.getScrollY();//获取滚动的Y轴的距离
child.setScrollY(leftScrolled);
}
很好理解,就是目标滚的距离是多少就让依赖的View滚多远
boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, float velocityX, float velocityY)
这个方法就是滚动的惯性,就是当你使用了很大力气的时候推一下,当你松手的时候他还会滚一段时间的.也是简单说一下参数
参数4和参数5代表的是松手时刻的瞬时速度
上个例子
@Override
public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, float velocityX, float velocityY) {
((NestedScrollView) child).fling((int) velocityY);
return true;
}
这里就直接把相应的速度进行传递就可以了!
就先些这些吧!今天有点累了,眼睛有点疼,其实我觉得这个后期多些两个就会了~~~
下面这些文章准备不累的时候好好实现一下...
http://blog.csdn.net/king1425/article/details/61923877
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0224/3991.html
http://www.jianshu.com/p/488283f74e69
http://www.jianshu.com/p/82d18b0d18f4