@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);
}
以上就是Behavior的一些重要方法,当我们要自定义一个Behavior的时候,就会去重写上面的一些方法。自定义Behavior 会放在文章最后讲。对Behavior 有了一些了解后,接下来我们看一下Google给我提供了一些特殊场景的Behavior。
2,BottomSheetBehavior/BottomSheetDialog 的使用 BottomSheetBehavior 实现的效果在我们的项目中用的比较多,它就是从底部弹出一个布局,在很多的应用中,分享功能都有这样一个交互。在以前我们通常都是用PopupWindow来搞定,前面也写了一篇文章了,关于PupupWindow的使用和封装,通用PopupWindow,几行代码搞定PopupWindow弹窗,有了BottomSheetBehavior 实现起来就简单一点了。请看效果图:
bottomSheetBehavior.gif
看看怎么用BottomSheetBehavior:
1,在xml布局文件中为需要从底部弹出的布局绑定BottomSheetBehavior,代码如下:
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=“match_parent”>
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=“显示/隐藏 BottomSheet”
android:background="@android:color/darker_gray"
android:textColor="@color/black"
android:padding=“10dp”
/>
app:layout_behavior="@string/bottom_sheet_behavior"
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:background="@android:color/white"
android:orientation=“vertical”
app:behavior_peekHeight=“0dp”
注意上面这行代码: app:behavior_peekHeight=“0dp”,peekHeight 属性是设置bottomSheet 折叠时的高度,我们设置为0表示折叠的时候完全隐藏,默认情况时显示布局的高度,布局会显示在界面,所以,如果要一开始布局不显示在界面上的话,需要将peekHeight 设置为0。也可以在代码中设置, 通过sheetBehavior.setPeekHeight(0)。2,在代码中获取到与布局相关联的BottomSheetBehavior,设置展开与折叠的状态就可以了,BottomSheetBehavior有5种状态:
1, STATE_EXPANDED 展开状态,显示完整布局。
2,STATE_COLLAPSED 折叠状态,显示peekHeigth 的高度,如果peekHeight为0,则全部隐藏,与STATE_HIDDEN效果一样。
3,STATE_DRAGGING 拖拽时的状态
4,STATE_HIDDEN 隐藏时的状态
5,STATE_SETTLING 释放时的状态看代码:
View shareView = findViewById(R.id.share_view);
//获取BottomSheetBehavior
final BottomSheetBehavior sheetBehavior = BottomSheetBehavior.from(shareView);
//设置折叠时的高度
//sheetBehavior.setPeekHeight(BottomSheetBehavior.PEEK_HEIGHT_AUTO);
//监听BottomSheetBehavior 状态的变化
sheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
});
//下滑的时候是否可以隐藏
sheetBehavior.setHideable(true);
findViewById(R.id.btn_show_bottom_sheet).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(sheetBehavior.getState() != BottomSheetBehavior.STATE_EXPANDED){
sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}else {
sheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
}
}
});
代码很简单,重要的就是通过方法 sheetBehavior.setState()来改变状态,是显示还是隐藏。其他的几个方法都添加了注释,不用多讲。
2.1, BottomSheetDialog 上面说了BottomSheetBehavior, 接下来看一下BottomSheetDialog, 一看名字就知道,它就是一个Dialog,使用方法和Dialog 一样,它是对BootomSheetBehavior 进行了包装,从底部弹出一个Dialog。BottomSheetDialog 使用起来比BottomSheetBahvior更方便,效果更佳。看一下它的源码也非常简单,就是Dialog 显示的布局绑定了BottomSheeBehavior,源码如下:
private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) {
final CoordinatorLayout coordinator = (CoordinatorLayout) View.inflate(getContext(),
R.layout.design_bottom_sheet_dialog, null);
if (layoutResId != 0 && view == null) {
view = getLayoutInflater().inflate(layoutResId, coordinator, false);
}
FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet);
mBehavior = BottomSheetBehavior.from(bottomSheet);
mBehavior.setBottomSheetCallback(mBottomSheetCallback);
mBehavior.setHideable(mCancelable);
if (params == null) {
bottomSheet.addView(view);
} else {
bottomSheet.addView(view, params);
}
// We treat the CoordinatorLayout as outside the dialog though it is technically inside
coordinator.findViewById(R.id.touch_outside).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mCancelable && isShowing() && shouldWindowCloseOnTouchOutside()) {
cancel();
}
}
});
return coordinator;
}
就这样一个方法,获取到Behavior,设置了一个监听状态的回调,设置了下滑可以隐藏。然后将Dialog 显示的布局添加到了绑定了BottomSheetBehavior 的ViewGroup 里。这个方法在setContent()方法被调用:
@Override
public void setContentView(View view) {
super.setContentView(wrapInBottomSheet(0, view, null));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
super.setContentView(wrapInBottomSheet(0, view, params));
}
接下来看一下使用方法,非常简单,以网易云音乐的歌单和分享UI为例:
网易云音乐歌单UI效果 如下:
文章图片
网易云音乐歌单.png
来张gif图效果更清楚:
网易云音乐效果图.gif
本文通过BottomSheetDialog 实现的效果图如下:
bottomSheetDialog.gif
歌单代码如下:
private void showBottomSheetDialog(){
BottomSheetDialog dialog = new BottomSheetDialog(this);
View view = LayoutInflater.from(this).inflate(R.layout.bottom_sheet_dialog,null);
handleList(view);
dialog.setContentView(view);
dialog.setCancelable(true);
dialog.setCanceledOnTouchOutside(true);
dialog.show();
}
private void handleList(View contentView){
RecyclerView recyclerView = (RecyclerView) contentView.findViewById(R.id.recyclerView);
LinearLayoutManager manager = new LinearLayoutManager(this);
manager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(manager);
MusicAdapter adapter = new MusicAdapter();
recyclerView.setAdapter(adapter);
adapter.setData(mockData());
adapter.notifyDataSetChanged();
}
分享代码如下:
/**
- share Dialog
private void showShareDialog(){
if(mBottomSheetDialog == null){
mBottomSheetDialog = new BottomSheetDialog(this);
View view = LayoutInflater.from(this).inflate(R.layout.bottom_sheet_share_dialog,null);
mBottomSheetDialog.setContentView(view);
mBottomSheetDialog.setCancelable(true);
mBottomSheetDialog.setCanceledOnTouchOutside(true);
// 解决下滑隐藏dialog 后,再次调用show 方法显示时,不能弹出Dialog
View view1 = mBottomSheetDialog.getDelegate().findViewById(android.support.design.R.id.design_bottom_sheet);
final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(view1);
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
Log.i(“BottomSheet”,“onStateChanged”);
mBottomSheetDialog.dismiss();
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
});
}else{
mBottomSheetDialog.show();
}
}
代码很简单,和其他普通Dialog的用法一样。值的主意的一点是这里有个bug ,那就是当你下滑隐藏了Dialog 之后,下次直接调用show方法来显示Dialog时(没有重新new 的情况下),Dialog不能显示,原因是因为BottomSheetDialog 源码中,关闭的Dialog 是依赖BottomSheetBehavior 的,当下滑隐藏的时候,BottomSheet的状态也为STATE_HIDDEN,并且同时dismiss Dialog,下次show 的时候,是没有办法显示一个状态为STATE_HIDDEN 的布局的。 网上搜了一下,有很多人都碰到过,解决方法来自这篇文章Material之Behavior实现支付宝密码弹窗 仿淘宝/天猫商品属性选择, 解决思路:获取到BottomSheetDialog 的布局,然后拿到绑定的BottomSheetBehavior,重新设置监听,在调用dismiss 方法时,我们重新设置一些Behavior 的状态。代码如下:
// 解决下滑隐藏dialog 后,再次调用show 方法显示时,不能弹出Dialog
View view1 = mBottomSheetDialog.getDelegate().findViewById(android.support.design.R.id.design_bottom_sheet);
final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(view1);
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
Log.i(“BottomSheet”,“onStateChanged”);
mBottomSheetDialog.dismiss();
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
});
以上就是BottomSheetBehavior 和BottomSheetDialog 的用法。
3,SwipeDissmissBehavior 的使用 上面讲了BottomSheetBehavior 和BottomSheetDialog 的用法,接下来看另一种场景的Behavior-SwipeDissmissBehavior,叫滑动消失或者滑动关闭,这个Behavior 在我们项目中用得可能就不是很多了。有个场景就是Snackbar的使用了,Android 5.0 以上 ,增加了Snackbar提示消息,Snackbar 的Behavior 的就是 SwipeDissmissBehavior 的应用,当滑动Snackbar 的时候,Snackbar 消失,效果如下:
snackbar的behavir.gif
使用也非常简单,在代码中只接new 一个SwipeDismissBehavior,设置一些属性后,添加到CoordinatorLayout.LayoutParams,代码如下:
mSwipeLayout = findViewById(R.id.swipe_layout);
SwipeDismissBehavior swipe = new SwipeDismissBehavior();
/**
- //设置滑动的方向,有3个值
- 1,SWIPE_DIRECTION_ANY 表示向左像右滑动都可以,
- 2,SWIPE_DIRECTION_START_TO_END,只能从左向右滑
- 3,SWIPE_DIRECTION_END_TO_START,只能从右向左滑
swipe.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END);
swipe.setStartAlphaSwipeDistance(0f);
swipe.setSensitivity(0.2f);
swipe.setListener(new SwipeDismissBehavior.OnDismissListener() {
@Override
public void onDismiss(View view) {
Log.e(TAG,"------>onDissmiss");
}
@Override
public void onDragStateChanged(int state) {
Log.e(TAG,"------>onDragStateChanged");
}
});
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) mSwipeLayout.getLayoutParams();
if(layoutParams!=null){
layoutParams.setBehavior(swipe);
}
有两个重要的方法,
wipe.setSwipeDirection
设置滑动方向,有三个取值,上面已经注释,不过多解释,还有就是swipe.setListener
可以监听dissmiss 和状态改变,在这些回调里面可以做一些自己的逻辑。最后效果图:swipeDissmissBehavir.gif
4,自定义Behavior 上面讲了Google 为我们提供的一些场景使用的Behavior,当然还有一些Google 提供的一些组件使用的Behavior,AppbarLayout内部的Behavior,如专门协调 AppbarLayout 与可滚动View(NestedScr
文章图片
ollView,RecyclerView )的, FloatActionButton内部的Behavior ,协调和Snackbar 的关系,保证Snackbar 弹出的时候不被FAB 遮挡。还有就是上面说的Snackbar内部的Behavior 等等。但是有时候,要实现多个View之间的的交互时,我们可以自定义Behavior ,下面就说说怎么自定义一个Behavior。
自定义Behavior 最关键的就是文章第一部分介绍的Behavior 提供的那一些方法,忘了的请到回去看一下第一部分的方法注释。自定义Behavior 分为两种:
- 第一种是通过监听一个View的状态,如位置、大小的变化,来改变其他View的行为,这种只需要重写2个方法就可以了,分别是
layoutDependsOn
和onDependentViewChanged
, layoutDependsOn方法判断是指定依赖的View时,返回true,然后在onDependentViewChanged 里,被依赖的View做需要的行为动作。
- 第二种就是重写
onStartNestedScroll
、onNestedPreScroll
、onNestedScroll
等一系列方法,前面第一步分已经讲过。
开眼首页效果.gif
效果如上:就是列表滑动的时候是覆盖Header(不是Header缩小,Header没动),然后就是Header有一个alpha 的变化。
1,首先是整个布局,Header 固定在顶部,列表在Header 的下方,CoordinatorLayout 是一个FrameLayout,不能提供这样的布局,我们需要重写onLayoutChild 来让列表位于Header下面:
@Override
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
Log.i(TAG,“onLayoutChild…”);
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);
}
开眼首页效果.gif
效果如上:就是列表滑动的时候是覆盖Header(不是Header缩小,Header没动),然后就是Header有一个alpha 的变化。
1,首先是整个布局,Header 固定在顶部,列表在Header 的下方,CoordinatorLayout 是一个FrameLayout,不能提供这样的布局,我们需要重写onLayoutChild 来让列表位于Header下面:
@Override
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
Log.i(TAG,“onLayoutChild…”);
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);
【程序员|Meterial Design常见控件的使用(四),android开发实战项目】}
推荐阅读
- Android|安卓 实现网易云音乐底部播放栏效果之使用自定义BaseActivity实现 下
- Android|Android 5.0 Handler 消息机制源码流程分析
- Android|Android全面解析之由浅及深Handler消息机制
- Android面试题|从源码角度分析Handler核心机制
- android|Android学习------Handler源码分析到手写简版Handler实现
- Android|带着问题学习 Android Handler 消息机制