今天的效果在支付宝、淘宝、京东等电商App中很常见。好比支付宝输入密码弹窗、商城下单时选择商品属性时,老的作法是从下面浮动上来一个PopupWindow
,那么今天就带你们用Behavior
来实现这两个效果,结果你会发现简直只须要“一”行代码。php
总结下如今用的APP:html
源码下载:download.csdn.net/detail/yanz…,推荐先阅读博客理解原理。java
在个人技术群里有小伙伴们讨论Behavior,我也去玩了玩,我也对Behavior写了系列博客。选中Behavior
而后ctrol + t
后发现Behavior
的一个实现类:BottomSheetBehavior
,我就到Android官网上翻了下资料,一翻就发现了惊喜啊,下面就把这些惊喜介绍给你们。android
更多文章请Google/百度搜索我名字:严振杰,排名第一的就是我。程序员
玩这个东西,首先Behavior
做为CoordinatorLayout
的子View的LayoutParams
(缘由看后文解释),因此CoordinatorLayout
是万万不能少的,先来亮出整个布局:api
<android.support.design.widget.CoordinatorLayout xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_above="@+id/tab_layout" android:gravity="center" android:orientation="vertical" app:layout_behavior="@string/appbar_scrolling_view_behavior">
<Button android:id="@+id/btn_bottom_sheet_control" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="sheet 显示/隐藏" />
</LinearLayout>
<LinearLayout android:id="@+id/tab_layout" android:layout_width="match_parent" android:layout_height="?actionBarSize" android:layout_alignParentBottom="true" android:background="@android:color/holo_purple" app:layout_behavior="@string/bottom_sheet_behavior">
<Button android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="第一" />
<Button android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="第二" />
<Button android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="第三" />
<Button android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="第四" />
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>复制代码
大概介绍下,页面上只能看到Toolbar
和一个Button
:sheet 显示/隐藏
,而后android:id="@+id/tab_layout"
这个布局是横向的,给它设置了Behavior
:app:layout_behavior="@string/bottom_sheet_behavior"
,通过测试发现,若是不给tab_layout
设置BottomSheetBehavior
,它会浮动在整个页面的顶部,并在Toolbar
的下面。设置了BottomSheetBehavior
它会被BottomSheetBehavior
自动移动到页面底部外边,因此在页面上是看不到android:id="@+id/tab_layout"
这个布局的。app
页面画好了,难道它会自动开关吗,怎么去控制它的打开和关闭呢?那么咱们就来看看这货的真实面貌,通过我看Android
的官方api发现,BottomSheetBehavior
这个货有一个静态方法BottomSheetBehavior.from(View)
,会返回这个View引用的BottomSheetBehavior
:ide
public static <V extends View> BottomSheetBehavior<V> from(V view) {
ViewGroup.LayoutParams params = view.getLayoutParams();
if (!(params instanceof CoordinatorLayout.LayoutParams)) {
throw new Exception("The view is not a child of CoordinatorLayout");
}
CoordinatorLayout.Behavior behavior = params.getBehavior();
if (!(behavior instanceof BottomSheetBehavior)) {
throw new IllegalArgumentException("...");
}
return (BottomSheetBehavior<V>) behavior;
}复制代码
这个方法会检查这个View是不是CoordinatorLayout
的子View,若是是才会去拿到这个View的Behavior
,因此诸位也应该明白为何我开头说Behavior
做为CoordinatorLayout
子View的LayoutParams
了。布局
接下来咱们看看拿到这个货后怎么用:测试
private BottomSheetBehavior mBottomSheetBehavior;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.bsbehavior_activity);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
findViewById(R.id.btn_bottom_sheet_control).setOnClickListener(onClickListener);
// 拿到这个tab_layout对应的BottomSheetBehavior
mBottomSheetBehavior = BottomSheetBehavior.from(findViewById(R.id.tab_layout));
}复制代码
findViewById(R.id.btn_bottom_sheet_control).setOnClickListener(onClickListener);
是给刚才说的页面中的Button
设置了监听,咱们用这个按钮来控制tab_layout
的显示和隐藏。
我发现有一个方法能够获取到它所依附的View此时的状态:mBottomSheetBehavior.getState()
,翻阅了源码后发现它的返回值有如下几种:
/** * The bottom sheet is dragging. */
public static final int STATE_DRAGGING = 1;
/** * The bottom sheet is settling. */
public static final int STATE_SETTLING = 2;
/** * The bottom sheet is expanded. */
public static final int STATE_EXPANDED = 3;
/** * The bottom sheet is collapsed. */
public static final int STATE_COLLAPSED = 4;
/** * The bottom sheet is hidden. */
public static final int STATE_HIDDEN = 5;复制代码
当我看到STATE_EXPANDED
和STATE_COLLAPSED
就明白了它的用法了,不就是展开和隐藏起来了麽?因此咱们判断这个状态,若是是隐藏就显示,若是是显示就隐藏:
@Override
public void onClick(View v) {
if (v.getId() == R.id.btn_bottom_sheet_control) {
if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
} else if (mBottomSheetBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) {
mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
}
}复制代码
到这里,知乎首页的Bottom的隐藏和显示也就讲玩了,接下来咱们来看看支付宝淘宝的下方弹窗如何实现。
这个类的发现也是在Android官网搜索BottomSheetBehavior
时发现的,一看到BottomSheetDialog
后心中狂喜,后来通过我验证,它显示的效果和我猜测的如出一辙啊,既然是个Dialog,那么用法应该和普通Dialog没啥去区别了吧。
而后我就顺势new了一个BottomSheetDialog
:
private BottomSheetDialog mBottomSheetDialog;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
createBottomSheetDialog();
}
private void createBottomSheetDialog() {
mBottomSheetDialog = new BottomSheetDialog(this);
View view = LayoutInflater.from(this).inflate(R.layout.dialog_bottom_sheet, null, false);
mBottomSheetDialog.setContentView(view);
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
...
recyclerView.setAdapter(adapter);
}复制代码
View
里面是一个RecyclerView
:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" />复制代码
而后用下面的代码去控制它的显示和隐藏。
if (mBottomSheetDialog.isShowing()) {
mBottomSheetDialog.dismiss();
} else {
mBottomSheetDialog.show();
}复制代码
当这个Dialog Show出来的时候发现它显示了一半,嗯这个效果确实不错,这样就达到了咱们最初说的支付宝密码弹窗和淘宝/天猫商品属性选择。咱们滑动的时候若是下面有内容它就会EXPANDED
,若是是一个普通的View(非RecyclerView、NestedScrollView)将不会继续往上滑动,下面的内容会继续跟着出来,可是一样能够向下滑动隐藏,也能够调用dismiss
和close
关闭。
做为一个有情怀的程序员,这里把我踩过的坑和解决方案跟你们分享一下。
我发现当这个Dilaog打开再关闭后,没法用Dialog.show()
再次打开,为何呢?
我去阅读了一下BottomSheetDialog
源代码,发现了以下代码:
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
super.setContentView(wrapInBottomSheet(0, view, params));
}
private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) {
final CoordinatorLayout coordinator = View.inflate(getContext(),R.layout...., null);
FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet);
BottomSheetBehavior.from(bottomSheet).setBottomSheetCallback(mBottomSheetCallback);
...
return coordinator;
}
private BottomSheetCallback mBottomSheetCallback = new BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
dismiss();
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
};复制代码
也就是说,系统的BottomSheetDialog
是基于BottomSheetBehavior
封装的,这里判断了当咱们滑动隐藏了BottomSheetBehavior
中的View后,内部是设置了BottomSheetBehavior
的状态为STATE_HIDDEN
,接着它替咱们关闭了Dialog
,因此咱们再次调用dialog.show()
的时候Dialog
无法再此打开状态为HIDE的dialog了。
这里就有个疑问了: Google为啥没有提供咱们本身设置BottomSheetCallback
的接口呢?
没有关系,看了源码发现很简单,咱们本身来实现,而且在监听到用户滑动关闭BottomSheetDialog
后,咱们把BottomSheetBehavior
的状态设置为BottomSheetBehavior.STATE_COLLAPSED
,也就是半个打开状态(BottomSheetBehavior.STATE_EXPANDED
为全打开),根据源码我把设置的方法提供下:
private void setBehaviorCallback() {
View view = mBottomSheetDialog.getDelegate().findViewById(android.support.design.R.id.design_bottom_sheet);
final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(view);
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
mBottomSheetDialog.dismiss();
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
}
});
}复制代码
这样就解决了BottomSheetDialog
关闭后不能再次打开的问题了。