• 实现一个网易云音乐的 BottomSheetDialog


    作者:林冠宏 / 指尖下的幽灵

    掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8

    博客:http://www.cnblogs.com/linguanh/

    GitHub : https://github.com/af913337456/

    腾讯云专栏: https://cloud.tencent.com/developer/user/1148436/activities


    Github 开源地址

    目录

    • 前序
    • 直观对比下 gif 效果
      • Android SDK 自带的 BottomSheetDialog
      • 网易云音乐 的 BottomSheetDialog
      • 我开源 的仿网易云音乐 BottomSheetDialog
    • 核心代码简述

    前序:

    因为 APP 需要参照网易云音乐的 BottomSheetDialog 的效果,找了一圈没找到,所以动手写了一个,涉及圈子里经常露面的知识点有下面三点,也是个实战应用

    • 事件分发系列的--冲突处理 & 分发顺序
    • View 绘制流程的--Measure 模式
    • 相对屏幕取 View 的坐标

    先来直观对比下 gif 效果

    • 首先是-- Android SDK 自带的 BottomSheetDialog
    • 然后是--网易云音乐 的 BottomSheetDialog
    • 最后是--我开源 的仿网易云音乐 BottomSheetDialog

    首先是-- Android SDK 自带的 BottomSheetDialog

    下面的 gif 图是一个Android SDK 自带的 BottomSheetDialog 内部加了 RecyclerView 列表控件的效果

    可以看出:

    • 下滑动作会收起,隐藏掉 dialog
    • 上滑会完全展开
    • 展开后,才能滑动 RecyclerView 内部

    其次

    • 如果你内部使用的是 ListView 列表控件,你会发现会有其他奇怪的情况。

    然后是--网易云音乐 的 BottomSheetDialog

    下面的 gif 图是一个Android 版 网易云音乐BottomSheetDialog效果

    可以看出:

    • 下滑动作会有范围回弹,也就是下滑到一定距离才会收起,隐藏掉 dialog
    • 上滑不给展开
    • 能够在半展开的情况下,内嵌滑动列表控件,例如 listView
    • 和列表控件滑动不冲突,在列表控件滑尽的时候,可以下滑隐藏dialog

    最后是--我开源 的仿网易云音乐 BottomSheetDialog

    可以看出,效果和网易云的一样

    核心代码简述

    SDK 的 BottomSheetDialog 内部布局的结构如下:

    --FrameLayout
    --|--CoordinatorLayout
    --|--|--FrameLayout
    --|--|--|--Our ContentView // 最后是我们设置的 ContentView
    
    

    CoordinatorLayout 在 Action_Move 事件时,必要的时候对其子 View 进行事件拦截,所以有第一个 gif 看到的效果,具体不详说。

    第一个步骤 --- 防止 CoordinatorLayoutOur ContentView 拦截事件

    这里使用 ListView 做例子,设置onTouch,在内部做适当时候的适当阻止CoordinatorLayout 拦截事件。

    // ListView
    setOnTouchListener(
        new OnTouchListener() {
            @SuppressLint("ClickableViewAccessibility")
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (bottomCoordinator == null)
                    return false;
                // 拿出当前列表第一个可见 item 的 pos
                int firstVisiblePos = getFirstVisiblePosition();
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        downY = event.getRawY();
                        bottomCoordinator.requestDisallowInterceptTouchEvent(true);
                        break;
                    case MotionEvent.ACTION_MOVE:
                        moveY = event.getRawY();
                        if ((moveY - downY) > 10) {
                            // 下滑情况
                            if (firstVisiblePos == 0 && isOverScroll) {
                                // 列表控件,例如 listView 已经滑到头了,允许被拦截
                                bottomCoordinator.requestDisallowInterceptTouchEvent(false);
                                break;
                            }
                        }
                        // 上滑时,总是不允许被拦截,listView 消耗当前事件
                        bottomCoordinator.requestDisallowInterceptTouchEvent(true);
                        break;
                    case MotionEvent.ACTION_UP:
                        break;
                }
                return false;
            }
        }
    );
    

    第二个步骤,让 ListView 能在半展开的情况下,显示完整的数据条数

    重写 onMeasure,使用自定义的测量模式。

    // ListView
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if(bottomCoordinator == null){
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            return;
        }
        // 以黄金分割的尺寸来显示 listView 的高度
        int size = (int)((float)(getResources().getDisplayMetrics().heightPixels*0.618));
        int newHeightSpec = MeasureSpec.makeMeasureSpec(
                size,
                // mode,非法的情况,super 直接使用 size 做高,看源码后,你会发现也可以使用 exact 模式
                Integer.MIN_VALUE 
        );
        super.onMeasure(widthMeasureSpec, newHeightSpec);
    }
    

    第三个步骤,实现 BottomSheetDialog 范围回弹

    /**
     * 添加 top 距离顶部多少的时候触发收缩效果
     * @param targetLimitH int 高度限制
     */
    @SuppressWarnings("all")
    public void addSpringBackDisLimit(final int targetLimitH){
        if(coordinator == null)
            return;
        // totalHeight 屏幕的总像素高度
        final int totalHeight = getContext().getResources().getDisplayMetrics().heightPixels;
        // currentH 当前我们的 列表控件 展开的高度
        final int currentH = (int) ((float)totalHeight*0.618); // 0.618 是黄金分割点,随便自定义,对应 contentView
        final int leftH    = totalHeight - currentH;
        coordinator.setOnTouchListener(
                new View.OnTouchListener() {
                    @Override
                    public boolean onTouch(View v, MotionEvent event) {
                        switch (event.getAction()){
                            case MotionEvent.ACTION_MOVE:
                                // 计算相对于屏幕的 坐标
                                bottomSheet.getGlobalVisibleRect(r);
                                break;
                            case MotionEvent.ACTION_UP:
                                // 抬手的时候判断
                                int limitH;
                                if(targetLimitH < 0)
                                    limitH = (leftH + currentH/3);
                                else
                                    limitH = targetLimitH;
                                if(r.top <= limitH)
                                    if (mBehavior != null)
                                        // 范围内,让它继续是 半展开的状态
                                        mBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
                                break;
                        }
                        return false;
                    }
                }
        );
    }
    

  • 相关阅读:
    div+css清除浮动代码
    JavaScript for循环实现表格隔行变色
    JavaScript 四种显示数据方式
    table表格隔行变色
    table表格用tbody新属性获取DOM元素
    条形图
    子图--面向对象
    线的形状
    matplotlib--直线和点
    颜色和样式字符串
  • 原文地址:https://www.cnblogs.com/linguanh/p/8778208.html
Copyright © 2020-2023  润新知