前言
- 在读《Android高级进阶-顾浩鑫》的过程中遇到一个没有用过的类 BottomSheetBehavior ;
- 这个类来自于 material design;
- 实现的效果如图所示:在手机界面上划,从下面调出 BottomSheetBehavior ,可以设置其制定高度;并可以设置其点击事件;
简介
- 这玩意好像就叫 ”底部弹窗“,所以……还有什么要问的吗?
- 添加依赖:
implementation 'com.android.support:design:28.0.0'
;
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
;
添加第一个包能够理解,为什么添加 constraintlayout 的依赖呢???
布局的根布局必须是CoordinatorLayout
;
代码
布局
-
activity.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.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" android:orientation="vertical" tools:context=".achartengine_test_item.AChartEngineTestActivity"> <ScrollView android:id="@+id/scv_bottom_sheet_behavior" android:layout_width="match_parent" android:layout_height="wrap_content" app:behavior_hideable="true" app:elevation="6dp" app:layout_behavior="@string/bottom_sheet_behavior"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#F0FFF0" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:padding="30dp" android:text="书单" android:id="@+id/tv_test" android:textColor="#000" android:textSize="20sp" /> …… </LinearLayout> </ScrollView> </androidx.coordinatorlayout.widget.CoordinatorLayout>
-
注意:
- 根布局是
CoordinatorLayout
,不是ConstraintLayout
; - ScrollView 即将来弹出的底部菜单,和底部的联系在代码中动态实现;
- 代码
app:layout_behavior="@string/bottom_sheet_behavior"
不能省略,用于和 BottomSheetBehavior 建立联系; - 我这里设置 ScrollView 的高为 wrap_content;因为实际上我的内容不一定能充满,如果固定了高度,内容却没有充满简直是一种奇葩的体验;
- 根布局是
Java
-
activity.java
private BottomSheetBehavior bottomSheetBehavior; private ScrollView scv_bottom_sheet_behavior; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { scv_bottom_sheet_behavior = findViewById(R.id.scv_bottom_sheet_behavior); bottomSheetBehavior = BottomSheetBehavior.from(scv_bottom_sheet_behavior); /** * 如果 ScrollView 位于列表顶端那么获取事件;否则释放 * 解决打开菜单向上滑动列表到列表底部时,向下滑东列表无响应的 BUG */ scv_bottom_sheet_behavior.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { //canScrollVertically(-1)的值表示是否能向下滚动,false表示已经滚动到顶部 if (!scv_bottom_sheet_behavior.canScrollVertically(-1)) { scv_bottom_sheet_behavior.requestDisallowInterceptTouchEvent(false); } else { scv_bottom_sheet_behavior.requestDisallowInterceptTouchEvent(true); } return false; } }); //获取设备的高度 Display display = getWindowManager().getDefaultDisplay(); int height = display.getHeight() * 1 / 3; //实际高度;如果 ScrollView 的高度没有达到 2/3 时的实际高度; //获取子空间(菜单 ScrollView 的实际高度) final int[] factHeight = new int[1]; scv_bottom_sheet_behavior.post(new Runnable() { @Override public void run() { factHeight[0] = scv_bottom_sheet_behavior.getHeight(); } }); //设置高度菜单的显示高度;静态设置方法: app:behavior_peekHeight="600dp" bottomSheetBehavior.setPeekHeight(height); if (height >= factHeight[0]) { CoordinatorLayout.LayoutParams coordParams = (CoordinatorLayout.LayoutParams) scv_bottom_sheet_behavior.getLayoutParams(); coordParams.height = height; scv_bottom_sheet_behavior.setLayoutParams(coordParams); } //设置默认先隐藏 bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); } /** * 通过 上滑动手势 打开底部弹窗 */ int originY = 0; @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: originY = (int) event.getY(); if (bottomSheetBehavior.getState() == STATE_COLLAPSED) { bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); } case MotionEvent.ACTION_MOVE: int lastY = (int) event.getY(); if (originY - lastY > 50) { if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN) { bottomSheetBehavior.setState(STATE_COLLAPSED); } } if (bottomSheetBehavior.getState() == STATE_COLLAPSED && lastY - originY > 50) { bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); } break; default: break; } return super.onTouchEvent(event); } //重写 返回键;如果菜单是打开的,关闭菜单,否则返回 activity; @Override public void finish() { if (bottomSheetBehavior.getState() == STATE_COLLAPSED) { bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); } else { super.finish(); } }
-
注意:
-
布局和菜单动态建立联系的方法 from() ;
-
解决:菜单上滑到列表底部时,向下滑东列表无响应的 BUG
-
默认设置菜单隐藏,建通手势在调出;
-
在重写 onTouchEvent() 方法中,记录起始位置坐标,该座标的初始化 一定要在 Down 操作之外;
-
onTouchEvent() 中的逻辑有:点击屏幕时如果菜单是打开的那么关闭菜单;下滑菜单时,如果菜单已经划到顶部了,那么关闭菜单;
-
重写 finish() 方法,如果菜单开着的,那么就关闭菜单;
-
获取设备宽度以及获取 View 的宽度;
Display display = getWindowManager().getDefaultDisplay();
final int[] factHeight = new int[1]; scv_bottom_sheet_behavior.post(new Runnable() { @Override public void run() { factHeight[0] = scv_bottom_sheet_behavior.getHeight(); } });
这里如果直接调用 getHeight(),那么一定没有结果,这和 activity 的生命周期无关,并非写在 onResume() 方法中就可以解决;实际上获取 View 的宽高应该在 onMeasure() 方法后,但并非走了生命周期就会走 onMeasure();所以要使用 post;
-
动态设置了菜单的高度为屏幕的 2/3,但如果数据没有那么多,但是才但却显示了 2/3 ,那……所以动态比较 ScrollView 和屏幕 2/3 的大小;
-
结尾
- 我的文章总是写的怎样使用,而从没有讲过具体的逻辑和代码,总是代码的搬运工,多少有些沮丧和遗憾吧!
- 下个阶段要尽可能多的去研究源码和逻辑,少一些浮躁,多一些踏实!加油!