作者:林冠巨集 / 指尖下的幽靈
掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8
部落格:http://www.cnblogs.com/linguanh/
GitHub : https://github.com/af913337456/
騰訊雲專欄: https://cloud.tencent.com/developer/user/1148436/activities
目錄
- 前序
- 直觀對比下 gif 效果
- Android SDK 自帶的
BottomSheetDialog
- 網易雲音樂 的
BottomSheetDialog
- 我開源 的仿網易雲音樂
BottomSheetDialog
- Android SDK 自帶的
- 核心程式碼簡述
前序:
因為
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 看到的效果,具體不詳說。
第一個步驟 --- 防止 CoordinatorLayout
對 Our 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;
}
}
);
}
複製程式碼