實現一個網易雲音樂的 BottomSheetDialog

做者:林冠宏 / 指尖下的幽靈java

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

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

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

騰訊雲專欄: https://cloud.tencent.com/developer/user/1148436/activities佈局


Github 開源地址spa

目錄

  • 前序
  • 直觀對比下 gif 效果
    • Android SDK 自帶的 BottomSheetDialog
    • 網易雲音樂 的 BottomSheetDialog
    • 我開源 的仿網易雲音樂 BottomSheetDialog
  • 核心代碼簡述

前序:

由於APP 須要參照網易雲音樂的 BottomSheetDialog 的效果,找了一圈沒找到,因此動手寫了一個,涉及圈子裏常常露面的知識點有下面三點,也是個實戰應用。固然,也能夠把它當作一個典型的事件分發綜合教例。code

  • 事件分發系列的--衝突處理 & 分發順序
  • View 繪製流程的--Measure 模式
  • 相對屏幕取 View 的座標

先來直觀對比下 gif 效果

  • 首先是-- Android SDK 自帶的 BottomSheetDialog
  • 而後是--網易雲音樂 的 BottomSheetDialog
  • 最後是--我開源 的仿網易雲音樂 BottomSheetDialog

首先是-- Android SDK 自帶的 BottomSheetDialog

下面的 gif 圖是一個Android SDK 自帶的 BottomSheetDialog 內部加了 RecyclerView 列表控件的效果cdn

能夠看出:blog

  • 下滑動做會收起,隱藏掉 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;
                }
            }
    );
}
複製代碼

相關文章
相關標籤/搜索