Android:巢狀滑動總結
一.何為滑動巢狀?
就是就有滑動功能的view,再巢狀另一個具有滑動功能的view,例如recyclerview巢狀recyclerview。
二.CoordinatorLayout是什麼?
是協調佈局,是一個超級framelayout,可以控制多個view協調運動。透過behavior來實現。主要原理是,子view獲取到滑動事件後,在滑動前,先詢問一下父view,問下父view要不要先滑,父view如果要滑,那就滑,然後把滑完之後把剩下的距離告訴子view,然後子view再滑。
所以說,如果處理不好,可以滑起來卡卡的,例如父view滑了一點,然後子view又滑了一點,你就會感覺卡卡的,難受。
三.behavior是怎樣的?
public static abstract class Behavior {
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type) { if (type == ViewCompat.TYPE_TOUCH) { onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); } } @Deprecated public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed) { // Do nothing } public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, @NestedScrollType int type) { if (type == ViewCompat.TYPE_TOUCH) { onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); } } public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, float velocityX, float velocityY, boolean consumed) { return false; } public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, float velocityX, float velocityY) { return false; }
.......
是個抽象類,看上去有點像NestedScrollingParent的方法。
例如,我們看看onDependentViewChanged是怎麼呼叫的把
image.png
可以看到,CoordinatorLayout註冊了PreDraw的監聽器,就是view在繪製前,會有回撥函式回撥到CoordinatorLayout的onChildViewsChanged,
if (b != null && b.layoutDependsOn(this, checkChild, child)) { //首先檢查是不是依賴的view發生了改變
..... //依賴的view發生改變了,回撥給需要協調運動的view
handled = b.onDependentViewChanged(this, checkChild, child);
.....
}
我們再看一種場景,就是使用onNestedPreScroll來實現的協調運動。
例如
的第二個test。
看看他的backtrace
image.png
可以看到事件是從recyclerview傳過來的,所以說move事件到達recyclerview後,recyclerview作為NestedScrollingChild2,會先迴圈一下作為NestedScrollingParent2的CoordinatorLayout,你CoordinatorLayout有沒有需要滑動的,有沒有需要處理move事件的,等CoordinatorLayou處理完之後,recyclerview再繼續跑。這樣看,像是子view和父view都消費了move事件,挺有意思。
recyclerview呼叫dispatchNestedPreScroll後,實際用使用NestedScrollingChildHelper來處理,
public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow, @NestedScrollType int type) { if (isNestedScrollingEnabled()) { //判斷是否支援巢狀滑動,我們常常可以在應用中關閉巢狀滑動 ViewParentCompat.onNestedPreScroll(parent, mView, dx, }
然後,最後就要回撥到CoordinatorLayout,然後CoordinatorLayout會遍歷每個子view,子view可以根據需要來確定是否處理。
四.分析一下BottomSheetBehavior的繪製流程
layout流程
@Override
@SuppressWarnings("unchecked")
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final Behavior behavior = lp.getBehavior(); //獲取childview的behavior,然後呼叫behavior的onLayoutChild去佈局。如果沒有behavior就走CoordinatorLayout自身的onLayoutChild if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) { onLayoutChild(child, layoutDirection); } } }
可以看到,有配置behavior的view,就走behavior的onLayoutChild方法。所以layout方法被behavior接管了。
viewDragHelper = ViewDragHelper.create(parent, dragCallback);
最後其實就是用viewdragger來實現拖拉位置的
@Override
public boolean onLayoutChild(
@NonNull CoordinatorLayout parent, @NonNull V child, int layoutDirection) {
if (state == STATE_EXPANDED) {
ViewCompat.offsetTopAndBottom(child, getExpandedOffset());
} else if (state == STATE_HALF_EXPANDED) {
ViewCompat.offsetTopAndBottom(child, halfExpandedOffset);
} else if (hideable && state == STATE_HIDDEN) {
ViewCompat.offsetTopAndBottom(child, parentHeight);
} else if (state == STATE_COLLAPSED) {
ViewCompat.offsetTopAndBottom(child, collapsedOffset);
} else if (state == STATE_DRAGGING || state == STATE_SETTLING) {
ViewCompat.offsetTopAndBottom(child, savedTop - child.getTop());
}
}
五.bottomsheet巢狀recyclerview後,recyclerview關閉巢狀滑動之後,滑recyclerview,bottomsheet會動嗎?
會動,別以為關閉巢狀滑動,父view就不攔截事件了,實際上,父view會會直接把在slopmove的時候,直接開始攔截move事件了。
image.png
那攔截之後,是怎麼拖到整個bottomsheet的呢?
image.png
其實就是攔截之後,事件去了CoordinatorLayout,然後再呼叫behavior,behavior使用ViewDragHelper去拖動配置了behavior的view網上挪。
關鍵是下面的函式,來確認是否攔截,返回true就攔截,因為touchingScrollingChild為false,所以最後返回了true。如果recyclerview設定了NestedScrollingEnabled,則touchingScrollingChild為true,最後這個函式返回false,父view就不攔截,move事件直接到達recyclerview。
@Override public boolean tryCaptureView(@NonNull View child, int pointerId) { Log.i("fengfeng", "tryCaptureView start:" + "child=" + child + " pointId=" + pointerId + " state=" + state); if (state == STATE_DRAGGING) { Log.i("fengfeng", "tryCaptureView false 1"); return false; } if (touchingScrollingChild) { Log.i("fengfeng", "tryCaptureView false 2"); return false; } if (state == STATE_EXPANDED && activePointerId == pointerId) { View scroll = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null; if (scroll != null && scroll.canScrollVertically(-1)) { // Let the content scroll up Log.i("fengfeng", "tryCaptureView false 3"); return false; } } Log.i("fengfeng", "tryCaptureView true 4"); return viewRef != null && viewRef.get() == child; } @Override public boolean tryCaptureView(@NonNull View child, int pointerId) { Log.i("fengfeng", "tryCaptureView start:" + "child=" + child + " pointId=" + pointerId + " state=" + state); if (state == STATE_DRAGGING) { Log.i("fengfeng", "tryCaptureView false 1"); return false; } if (touchingScrollingChild) { Log.i("fengfeng", "tryCaptureView false 2"); return false; } if (state == STATE_EXPANDED && activePointerId == pointerId) { View scroll = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null; if (scroll != null && scroll.canScrollVertically(-1)) { // Let the content scroll up Log.i("fengfeng", "tryCaptureView false 3"); return false; } } Log.i("fengfeng", "tryCaptureView true 4"); return viewRef != null && viewRef.get() == child; }
touchingScrollingChild在down事件賦值,代表點中了一個可以scroll的view,例如recyclerview就是可以scroll的。
case MotionEvent.ACTION_DOWN: if (state != STATE_SETTLING) { View scroll = nestedScrollingChildRef != null ? nestedScrollingChildRef.get() : null; if (scroll != null && parent.isPointInChildBounds(scroll, initialX, initialY)) { touchingScrollingChild = true; } }
作者:九九叔
連結:
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69983768/viewspace-2723155/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Android巢狀滑動邏輯淺析Android巢狀
- 一種巢狀滑動衝突的解決方案巢狀
- ScrollView巢狀RecyclerView滑動衝突相關問題View巢狀
- NestedScrolling機制解析,自定義巢狀滑動你也可以巢狀
- 一種非巢狀滑動衝突的解決方案巢狀
- 線上直播系統原始碼,flutter 巢狀滑動實現原始碼Flutter巢狀
- 【Android ViewPager】解決ViewPager巢狀時在API 13及其以下版本中不能滑動的問題AndroidViewpager巢狀API
- 解決 ScrollView 巢狀 RecyclerView 時,慣性滑動失效的問題View巢狀
- 不要在 XML 設定,解決 NestedScrollView 巢狀 RecyclerView 滑動卡頓XMLView巢狀
- [Flutter]從零開始實現一個巢狀滑動的PageView(一)Flutter巢狀View
- 自定義View事件之進階篇(一)-NestedScrolling(巢狀滑動)機制View事件巢狀
- [Flutter]從零開始實現一個巢狀滑動的PageView(三)Flutter巢狀View
- 滑動視窗問題總結
- Android實現雙層ViewPager巢狀AndroidViewpager巢狀
- python-if elif巢狀結構Python巢狀
- 滑動視窗(Sliding Window)技巧總結
- Android實現RecyclerView巢狀流式佈局AndroidView巢狀
- 解決Flutter的ListView巢狀ListView滑動衝突以及無限高度問題FlutterView巢狀
- android viewpager2和scrollerview巢狀百度地圖MapView導致滑動有黑邊或者陰影問題AndroidViewpager巢狀地圖
- Jetpack Compose(8)——巢狀滾動Jetpack巢狀
- 自定義 Behavior,實現巢狀滑動、平滑切換周月檢視的日曆巢狀
- 關於 Android 狀態列的適配總結Android
- Android 設定TextView滑動滾動條和滑動效果AndroidTextView
- 滑動視窗相關的題目總結
- Android 禁止ViewPager左右滑動AndroidViewpager
- Android動態許可權總結Android
- 巢狀滾動效果實現討論巢狀
- 三級 NestedScroll 巢狀滾動實踐巢狀
- iOS-多個UIScrollView滑動巢狀(仿微博、抖音、網易雲個人詳情頁)iOSUIView巢狀
- 列表巢狀操作巢狀
- vue路由巢狀Vue路由巢狀
- (轉)leetcode:Find All Anagrams in a String 滑動視窗方法總結LeetCode
- [20190506]檢視巢狀與繫結變數.txt巢狀變數
- react native ScrollView巢狀WebView 互動問題React Native巢狀WebView
- 多層 UIScrollView 巢狀滾動解決方案UIView巢狀
- 巢狀滾動設計和原始碼分析巢狀原始碼
- Android中活動間通訊總結Android
- Android筆記之Kotlin、Java的內部類?巢狀類?Android筆記KotlinJava巢狀