原本今天早上起来就想写这篇文章的,可是看着心爱的骑士队正在打比赛,想着詹皇的2:0。。。想着猛龙的东部第一,今天真的是给猛龙打成“朦胧”了。。。猛龙的球迷不会打我吧!!!哈哈android
昨天有小伙伴说要我说一下Behavior的使用,对于这个东西我也不是很了解,可是不了解能够学吗!!!其实做为程序员,咱们在平常开发中会接触到不少新东西,不是咱们每个都要去了解的,咱们也不会有那么多的精力。可是为何有的人能够会的那么多呢?其实有的时候我总在想这个问题,后来我发现一件颇有意思的事情,他们也不见得什么都懂,可是以往的经历让他们知道怎么去接触一个新的东西,怎么去快速上手这个东西。其实咱们应该培养的是解决问题的能力,而不是什么都会。。。好了,闲话就扯到这里吧!下面开始今天的内容。其实我挺喜欢詹皇的!哈哈。。。git
关于Behavior的描述是这样的程序员
Interaction behavior plugin for child views of CoordinatorLayout. A Behavior implements one or more interactions that a user can take on a child view. These interactions may include drags, swipes, flings, or any other gestures.github
简单的翻译一下:CoordinatorLayout中子View的交互行为,能够在CoordinatorLayout的子类中实现一个或多个交互,这些交互多是拖动,滑动,闪动或任何其余手势。其实就是实现CoordinatorLayout内部控件的交互行为,能够在非侵入的方式实现相应的交互!他能作什么呢?看了后面就知道了!!!哈哈。。。算法
关于这个问题,我去google找了找,下面这张图说明一切:bash
但其实,开发中经常使用的就BottomSheetBehavior、SwipeDismissBehavior剩下的就是自定义了。app
BottomSheetBehavior主要是实现从底部弹出内容的Behavior。其实这个里面包含不少内容,像BottomSheet、BottomSheetDialog、BottomSheetDialogFragment咱们这里一个一个说明一下:ide
这个通常是使用在相应的布局中的!为何这么说呢,由于它能够直接在布局中使用,就酱紫啊!!!先介绍一下里面比较重要的概念,不然我怕你吓啥么不知道啥么啥(原谅个人一口东北话)!布局
根布局是CoordinatorLayout,这个是重点啊!!!ui
这里注意几点问题:
app:layout_behavior="@string/bottom_sheet_behavior"
的布局xml中的代码
<?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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.jinlong.newmaterialdesign.behavior.BehaviorActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="bottomSheet"
android:text="展现bottomSheet" />
<LinearLayout
android:id="@+id/ll_bottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:behavior_peekHeight="50dp"
app:layout_behavior="@string/bottom_sheet_behavior">
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#f25b41"
android:gravity="center"
android:text="底部还有内容啊!!!" />
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#009988"
android:gravity="center"
android:text="标签1" />
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#002288"
android:gravity="center"
android:text="标签2" />
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#009922"
android:gravity="center"
android:text="标签3" />
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#00aa88"
android:gravity="center"
android:text="标签4" />
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#999988"
android:gravity="center"
android:text="标签5" />
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>
复制代码
主页面的逻辑
BottomSheetBehavior<LinearLayout> bottomSheetBehavior = BottomSheetBehavior.from(mLlBottomSheet);
if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
//展开状态,隐藏
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
} else {
//其余的状态展开
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
复制代码
其实这个东西的使用和对话框的使用基本上是同样的。你setContentView()进去一个布局,而后调用show()方法展现一下就能够了,可是这里有一个特别须要注意的地方,若是你在对话框中设置的布局超过整个屏幕的话(这里不是说你设置了match就是全屏了,是有效内容。这里建议你试试就知道了),整个内容不会铺满全屏,顶部会留出一段空间,和peek的效果相似,这里注意一下就能够了!其余的使用和对话框的使用同样,这里直接贴一下主要代码!!!
BottomSheetDialog sheetDialog = new BottomSheetDialog(this); sheetDialog.setContentView(R.layout.sheet_dialog);
sheetDialog.show();
复制代码
其实这个和写一个Fragment是同样的,可是也存在和上面弹出对话框的那种问题,就是当你布局过大的状况下会留出一段空间。
这里主要说明两点问题:
BottomSheetBehavior.from((View) view.getParent());
show(getSupportFragmentManager(), "dialog");
显示。这个是滑动消失和滑动关闭,不少状况下都是和5.0新出的Snackbar。一个和Toast相似的东西,由于不是本文重点,因此关于Snackbar就不展开说了!其实除了Snackbar使用到这个,基本上没有那个APP想把本身的页面划没了吧!!!其实用法仍是很简单的,主要建立一个对象,设置一些像一个的参数就能够了!直接上代码:
TextView tvTitle = findViewById(R.id.tv_title);
SwipeDismissBehavior<View> mSwipe = new SwipeDismissBehavior();
/*
* SWIPE_DIRECTION_START_TO_END 只能从左向右滑动
* SWIPE_DIRECTION_END_TO_START 只能从右向左滑动
* SWIPE_DIRECTION_ANY 左右滑动均可以
*/
mSwipe.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_ANY);
mSwipe.setListener(new SwipeDismissBehavior.OnDismissListener() {
@Override
public void onDismiss(View view) {
//View消失的回调
}
@Override
public void onDragStateChanged(int state) {
/*
* STATE_IDLE 空闲状态
* STATE_DRAGGING 滑动中
* STATE_SETTLING 消失
*/
}
});
复制代码
注释已经很详细了,这里注意一点啊,若是设置了滑动删除功能,这个页面就存在滑动删除的功能了,是页面存在这个功能,里门的大多数控件都能存在滑动删除功能,可是我尝试了,AppBarLayout等一些相应的控件不能,估计是设置了behavior的控件不能滑动删除,其余的均可以,可是这个只是个人猜想,没有验证!!!
这里先明确一个概念,behavior的嵌套滚动都是依照一个相应的参考物,因此在自定义的时候必定要区分哪一个是依照的View哪一个是被观察的View,只有区分了这些才能更好的理解下面的内容,下面出现的全部child都是被观察的View,也就是xml中定义behavior的View。
layoutDependsOn(CoordinatorLayout parent, View child, View dependency) 表示是否给应用了Behavior 的View 指定一个依赖的布局
onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) 当依赖的View发生变化的时候hi掉的方法
onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) 当用户手指按下的时候,你是否要处理此次操做。当你肯定要处理此次操做的时候,返回true;若是返回false的时候,就不会去响应后面的回调事件了。你想怎么滑就怎么话,我都不作处理。这里的(axes)滚动方向很重要,能够经过此参数判断滚动方向!
onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target, @ScrollAxis int axes, @NestedScrollType int type) 当onStartNestedScroll准备处理此次滑动的时候(返回true的时候),回调这个方法。能够在这个方法中作一些响应的准备工做!
onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, @NestedScrollType int type) 当滚动开始执行的时候回调这个方法。
onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) 上面这个方法结束的时候,coordinatorLayout处理剩下的距离,好比还剩10px。可是coordinatorLayout发现滚动2px的时候就已经到头了。那么结束其滚动,调用该方法,并将coordinatorLayout处理剩下的像素数做为参(dxUnconsumed、dyUnconsumed)
传过来,这里传过来的就是 8px。参数中还会有coordinatorLayout处理过的像素数(dxConsumed、dyConsumed)。老大开始处理剩下的距离了!这个方法主要处理一些越界后的滚动。仍是不懂对吧!还拿大家老大作比喻:好比上面还剩 10%的工做,这时老大处理了2%后发现已经能够上线了,因而老大结束了工做,并将处理剩下的内容(dxUnconsumed、dyUnconsumed)纪录下来,告诉你。老大处理了的内容(dxConsumed、dyConsumed)也告诉了你。
if (dyConsumed > 0 && dyUnconsumed == 0) {
System.out.println("上滑中。。。");
}
if (dyConsumed == 0 && dyUnconsumed > 0) {
System.out.println("到边界了还在上滑。。。");
}
if (dyConsumed < 0 && dyUnconsumed == 0) {
System.out.println("下滑中。。。");
}
if (dyConsumed == 0 && dyUnconsumed < 0) {
System.out.println("到边界了,还在下滑。。。");
}
复制代码
onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, float velocityX, float velocityY) 当手指松开发生惯性动做以前调用,这里提供了响应的速度,你能够根据速度判断是否须要进行折叠等一系列的操做,你要肯定响应这个方法的话,返回true。
onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int type) 中止滚动的时候回调的方法。当你不去响应Fling的时候会直接回调这个方法。在这里能够作一些清理工做。或者其余的内容。。。
onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) 肯定子View位置的方法,这个方法能够从新定义子View的位置(这里明确是设置behavior的那个View哦),例以下面这样
基本上能用到的API就这么多,可是这里面的内容不少,先好好理解一下,我其实都不怎么理解,没事不理解没事,后面几个例子就ok了!
这里说明一下自定义Behavior分为两种类型,一种是依赖相应的View变化而变化、一种是依赖滚动变化。
这个最经典的案例就是底栏跟随AppBarLayout移动给移动,其实代码很简单,只要算出AppBarLayout的移动距离,动态的设置给相应的依赖控件就能够了。一波代码走起!!!先上一张效果图。
public class TwoBehavior extends CoordinatorLayout.Behavior<View> {
private String TAG = TwoBehavior.class.getSimpleName();
//这个千万要写,不然会出异常
public TwoBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
//依赖于AppBarLayout的
return dependency instanceof AppBarLayout;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
//计算出AppBarLayout移动的距离
float top = Math.abs(dependency.getTop());
Log.e(TAG, "AppBarLayout移动的距离" + top);
child.setTranslationY(top);
return true;
}
}
复制代码
而后代码里使用:app:layout_behavior="全路径"
就能够实现了,由于CoordinatorLayout里面的内容怕你不知道,因此这里我仍是贴一下相应的清单文件,要不你又该把实现不了的锅让我背了,这个锅我不背。。。
<?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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.jinlong.newmaterialdesign.behavior.TwoBehaviorActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:title="底部联动的Behavior"
app:titleTextColor="@android:color/white" />
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#009988"
android:gravity="center"
android:text="标签1" />
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#002288"
android:gravity="center"
android:text="标签2" />
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#009922"
android:gravity="center"
android:text="标签3" />
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#00aa88"
android:gravity="center"
android:text="标签4" />
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#999988"
android:gravity="center"
android:text="标签5" />
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
<TextView
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="bottom"
android:background="@color/colorPrimary"
android:gravity="center"
android:text="这个一个底栏"
android:textColor="@android:color/white"
app:layout_behavior="com.jinlong.newmaterialdesign.behavior.TwoBehavior" />
</android.support.design.widget.CoordinatorLayout>
复制代码
这里面处理View的变化才是难点,反正我是这么认为的,不懂的同窗能够补充一下相应的知识,什么View变化,获取相应位置的方法等些许内容。网上仍是挺多的,依赖的View基本上都是这么实现的。都是实现这两个方法的,玩转了就行了,多写写天然就熟了。
这个就比较难了,由于涉及到相应的滚动计算什么的,只有多写多看才能熟,先来一个简单的例子吧。剩下的就靠你们多多练习了!!!
@Override
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
//设置了behavior的布局
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
if(params!=null && params.height == CoordinatorLayout.LayoutParams.MATCH_PARENT){
child.layout(0,0,parent.getWidth(),parent.getHeight());
child.setTranslationY(getHeaderHeight());
return true;
}
return super.onLayoutChild(parent, child, layoutDirection);
}
/**
* 这里是Header的高度,能够设置成任何你想的高度
*/
public int getHeaderHeight(){
// 当你设置到相应的清单文件的时候,你就这么弄
// return Context.getResources().getDimensionPixelOffset(R.dimen.header_height);
return 500;
}
复制代码
这里的位置肯定主要用到了一个View.setTranslationY()的方法,这个方法我查了查,和View.getTop()的方法有些相似,是相对于父控件左上角的偏移量。那么就很好理解了,设置Behavior的View便宜到指定的位置下面,由于这里设置了一个相应的图片高度,因此这里就是在图片的下面。
@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 || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type);
}
复制代码
首先点你手指开始滑动的时候,会执行下面这个方法。
@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);
// 在这个方法里面只处理向上滑动
if(dy < 0){
return;
}
//计算每次移动的距离
float transY = child.getTranslationY() - dy;
if(transY > 0){
child.setTranslationY(transY);
consumed[1]= dy;
}
}
复制代码
说明一下:dy<0 表明的是向下滑动。child.getTranslationY()获取的是设置behavior的View距离CoordinatorLayout顶部的偏移量。dy表明的是每一次移动的距离。因此transY计算的就是每一次移动后应该距离顶部的距离设置给相应的View。这里consumed[1]= dy表示的是你处理的距离(后面会用到的)。因此就不难理解了,当你每次向上滑动的时候,会计算相应的数值,设置给child,使它逐渐的向上移动,当到达顶部以后就不进行改变相应的竖直了。就酱紫了...
每次上面的方法执行完成的时候会调用下面这个方法
@Override
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
// 在这个方法里只处理向下滑动
if(dyUnconsumed >0){
return;
}
float transY = child.getTranslationY() - dyUnconsumed;
Log.i(TAG,"------>transY:"+transY+"****** child.getTranslationY():"+child.getTranslationY()+"--->dyUnconsumed"+dyUnconsumed);
if(transY > 0 && transY < getHeaderHeight()){
child.setTranslationY(transY);
}
}
复制代码
还记的我上面说的那个老大的问题吧?当你consumed[1]= dy的时候,就会传递过来相应的参数dxUnconsumed/dyUnconsumed表明处理剩下的参数,可是这里要注意一点。针对于上面这个例子当你上一个方法return的时候,那么这个dxUnconsumed/dyUnconsumed就会有值的!那就不难理解了,当你向下滑动的时候会之间改变child的位置,直到child所有显示出来为止!
public class OneBehavior extends CoordinatorLayout.Behavior {
private String TAG = OneBehavior.class.getSimpleName();
public OneBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int type) {
super.onStopNestedScroll(coordinatorLayout, child, target, 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 || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type);
}
@Override
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
// 在这个方法里只处理向下滑动
if(dyUnconsumed >0){
return;
}
float transY = child.getTranslationY() - dyUnconsumed;
Log.i(TAG,"------>transY:"+transY+"****** child.getTranslationY():"+child.getTranslationY()+"--->dyUnconsumed"+dyUnconsumed);
if(transY > 0 && transY < getHeaderHeight()){
child.setTranslationY(transY);
}
}
@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);
// 在这个方法里面只处理向上滑动
if(dy < 0){
return;
}
float transY = child.getTranslationY() - dy;
Log.i(TAG,"transY:"+transY+"++++child.getTranslationY():"+child.getTranslationY()+"---->dy:"+dy);
if(transY > 0){
child.setTranslationY(transY);
consumed[1]= dy;
}
}
@Override
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
//设置了behavior的布局
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
if(params!=null && params.height == CoordinatorLayout.LayoutParams.MATCH_PARENT){
child.layout(0,0,parent.getWidth(),parent.getHeight());
child.setTranslationY(getHeaderHeight());
return true;
}
return super.onLayoutChild(parent, child, layoutDirection);
}
/**
* 这里是Header的高度,能够设置成任何你想的高度
*/
public int getHeaderHeight(){
// 当你设置到相应的清单文件的时候,你就这么弄
// return Context.getResources().getDimensionPixelOffset(R.dimen.header_height);
return 500;
}
}
复制代码
布局的内容就简单多了。直接引入一个behavior就能够了。有一个问题注意下,由于上面面我是写死的高度,因此布局里面就是一个写死的高度了。若是你想作好适配的话,就直接从xml中获取就能够了。
<?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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="500px"
android:scaleType="centerCrop"
android:src="@mipmap/heard_1" />
<!--这是一张图片-->
<android.support.v4.widget.NestedScrollView
android:id="@+id/nested_scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
app:layout_behavior="com.jinlong.newmaterialdesign.behavior.OneBehavior">
<!--这是是全路径-->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/large_text" />
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
复制代码
基本上就这么多了,其实我在算法上面真的很渣、很渣一个很渣已经不能形容我了。我已经很努力的给大家讲明白了。不懂的能够给我留言,其实自定义Behavior真的是看见一个你练一个,慢慢的你就能开车了!其实我也是看了别人分享的东西以后总结的。有什么很差的地方还请多多指教!关于Behavior原理感兴趣的同窗能够看看HelloCsld的这篇文章讲的也是挺透彻的!但愿今天讲的这些能帮到你!今天就到这里吧!拜拜。。。
代码在gitHub上的地址感兴趣的同窗能够去看看!!!