小动画之“上划菜单”(BottomSheetBehavior)

本文介绍如何使用BottomSheetBehavior实现上划菜单效果,包括添加依赖、布局配置及Java代码实现,解决菜单响应冲突等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

利用 BottomSheetBehavior 实现“上划菜单”小动画

在这里插入图片描述

前言

  1. 在读《Android高级进阶-顾浩鑫》的过程中遇到一个没有用过的类 BottomSheetBehavior ;
  2. 这个类来自于 material design;
  3. 实现的效果如图所示:在手机界面上划,从下面调出 BottomSheetBehavior ,可以设置其制定高度;并可以设置其点击事件;

简介

  1. 这玩意好像就叫 ”底部弹窗“,所以……还有什么要问的吗?
  2. 添加依赖:
    implementation 'com.android.support:design:28.0.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    添加第一个包能够理解,为什么添加 constraintlayout 的依赖呢???
    布局的根布局必须是 CoordinatorLayout

代码

布局

  1. 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>
    
  2. 注意:

    1. 根布局是 CoordinatorLayout,不是 ConstraintLayout
    2. ScrollView 即将来弹出的底部菜单,和底部的联系在代码中动态实现;
    3. 代码app:layout_behavior="@string/bottom_sheet_behavior" 不能省略,用于和 BottomSheetBehavior 建立联系;
    4. 我这里设置 ScrollView 的高为 wrap_content;因为实际上我的内容不一定能充满,如果固定了高度,内容却没有充满简直是一种奇葩的体验;

Java

  1. 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();
        }
    }
    
  2. 注意:

    1. 布局和菜单动态建立联系的方法 from() ;

    2. 解决:菜单上滑到列表底部时,向下滑东列表无响应的 BUG

    3. 默认设置菜单隐藏,建通手势在调出;

    4. 在重写 onTouchEvent() 方法中,记录起始位置坐标,该座标的初始化 一定要在 Down 操作之外

    5. onTouchEvent() 中的逻辑有:点击屏幕时如果菜单是打开的那么关闭菜单;下滑菜单时,如果菜单已经划到顶部了,那么关闭菜单;

    6. 重写 finish() 方法,如果菜单开着的,那么就关闭菜单;

    7. 获取设备宽度以及获取 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;

    8. 动态设置了菜单的高度为屏幕的 2/3,但如果数据没有那么多,但是才但却显示了 2/3 ,那……所以动态比较 ScrollView 和屏幕 2/3 的大小;


结尾

  1. 我的文章总是写的怎样使用,而从没有讲过具体的逻辑和代码,总是代码的搬运工,多少有些沮丧和遗憾吧!
  2. 下个阶段要尽可能多的去研究源码和逻辑,少一些浮躁,多一些踏实!加油!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

liusaisaiV1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值