【日常小問題】解決BottomSheetDialogFragment中多個fragment滑動衝突
highlight: androidstudio theme: smartblue
提出問題
前段時間工作上遇到個UI需求,需要在BottomSheetDialogFragment中巢狀多個fragment,且每個fragment都有個列表需要滑動,但是出現了個問題,彈窗在滑動的時候和列表滑動的時候出現衝突,在外部彈窗完全展開的時候,內部fragment列表無法進行滑動
思考了下,總結分析了需要解決的問題
- 如何保證在彈窗隨手勢完全展開的時候,列表繼續滑動;在收縮的時候,列表滑動回到頂部後,再收縮彈窗?
- 因為Fragment中的RecyclerView接收不到滑動事件,導致無法滑動麼?
原始碼分析
然後我們帶著問題去分析,同時在網上搜索了大量相關的問題解答,發現基本都是因為BottomSheetBehavior的ViewPager巢狀RecyclerView導致滑動失效,可以舉一反三,其實本質的問題都是類似的
因為BottomSheetDialogFragment手勢滑動是通過BottomSheetBehavior來實現,通過啟發我們去看了下BottomSheetBehavior的原始碼,它的程式碼量不
首先看到nestedScrollingChildRef,它是從綁定了BottomSheetBehavior的child中找可以巢狀滑動的控制元件
nestedScrollingChildRef = new WeakReference<>(findScrollingChild(child));
然後看下findScrollingChild方法,發現它只支援內部有一個可以上下滑動的控制元件,多個的話就取第一個
@Nullable
@VisibleForTesting
View findScrollingChild(View view) {
if (ViewCompat.isNestedScrollingEnabled(view)) {
return view;
}
if (view instanceof ViewGroup) {
ViewGroup group = (ViewGroup) view;
for (int i = 0, count = group.getChildCount(); i < count; i++) {
View scrollingChild = findScrollingChild(group.getChildAt(i));
if (scrollingChild != null) {
return scrollingChild;
}
}
}
return null;
}
而且mNestedScrollingChildRef在處理touch事件的時候會用到,那麼我們只要修改為在fragment切換的時候重新設定下這個mNestedScrollingChildRef就可以了,乍一想,好像問題變得簡單起來了
解決方案
結合網上大部分的解決方案,我們都需要去重新修改BottomSheetBehavior,因為原來的behavior已經不適用這種情況
這裡看到很久之前的git就有人提交過Viewpager導致BottomSheet滑動衝突的問題,所幸就在大佬提供的BottomSheetBehavior基礎上去修改,有點小遺憾的是這裡的版本有點陳舊,後期需要優化下,保持和現版本的功能大致形同。
先上個地址 : (http://github.com/laenger/ViewPagerBottomSheet)
其實改動很簡單,大致就是如下兩點
- 在上面說的findScrollingChild方法中,找到viewpager拿到當前的子child
- 然後在viewpager切換頁面的時候重新設定下mNestedScrollingChildRef即可
@VisibleForTesting
View findScrollingChild(View view) {
if (ViewCompat.isNestedScrollingEnabled(view)) {
return view;
}
if (view instanceof ViewPager) {
ViewPager viewPager = (ViewPager) view;
View currentViewPagerChild = ViewPagerUtils.getCurrentView(viewPager);
if (currentViewPagerChild == null) {
return null;
}
View scrollingChild = findScrollingChild(currentViewPagerChild);
if (scrollingChild != null) {
return scrollingChild;
}
} else if (view instanceof ViewGroup) {
ViewGroup group = (ViewGroup) view;
for (int i = 0, count = group.getChildCount(); i < count; i++) {
View scrollingChild = findScrollingChild(group.getChildAt(i));
if (scrollingChild != null) {
return scrollingChild;
}
}
}
return null;
}
....
mNestedScrollingChildRef = new WeakReference<>(findScrollingChild(child));
看到這裡,問題基本就迎刃而解了,其實我只要在切換fragment的時候重新設定mNesetdScrollingChildRef,讓它去改變需要滑動的子view,就稍微做了以下改動
首先寫了個方法去重新重新整理mNesetdScrollingChildRef
public void invalidateScrollingChild(View scrollingChildView) {
mNestedScrollingChildRef = new WeakReference<>(scrollingChildView);
}
然後我另外寫了個介面去返回當前可滑動的子view,可以是RecyclerView,ListView等等
interface CallBackScrollChild {
fun backScrollChild(scrollChild: View)
}
在fragment的onVisible可見方法中呼叫
override fun onVisible() {
super.onVisible()
binding?.rvFirst?.let { callBackScrollChild?.backScrollChild(it) }
}
需要在bottomSheetFragment中呼叫當前behavior中的invalidateScrollingChild方法
override fun backScrollChild(scrollChild: View) {
behavior?.invalidateScrollingChild(scrollChild)
}
可以看下最終效果
附上demo地址:http://github.com/RainyJiang22/ViewPagerBottomSheetBehavior
對於BottomSheetBehavior的限制,以上方法僅供參考,如果還有更好的方式,歡迎各位大佬留言
- 請收下這些Kotlin開發必知必會的編碼實踐方式
- 日常思考,目前Kotlin協程能完全取代Rxjava嗎
- 誇誇其談,簡單說說HTTP的優化歷程
- 【一起學習開源框架】Retrofit相對於OKHttp,解決了什麼問題
- Kotlin Sequences Api:入門
- 【日常小問題】談談Rxjava中的操作符以及幾種常見的Subject,應用到實際場景中
- 【一起學習Android開源框架】Retrofit原始碼解析-1(第四部分)
- 【日常小問題】解決BottomSheetDialogFragment中多個fragment滑動衝突
- 【一起學習Android開源框架】Retrofit註解解析(第三部分)
- 【一起學習Android開源框架】Retrofit使用的代理模式解析(第二部分)
- 【一起學習Android開源框架】Retrofit的簡單使用(第一部分)