這裏記錄一下在support-compat27包中主要發現了兩個滑動時候的問題。java
首先看下xml文件:android
<?xml version="1.0" encoding="utf-8"?> <FrameLayout android:id="@+id/testscor" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" tools:context=".MainActivity"> <com.sogou.testforall.CustomCoordinatorLayout android:id="@+id/coord" android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.design.widget.AppBarLayout android:id="@+id/appbar" android:layout_width="match_parent" android:layout_height="400dp" android:orientation="vertical" app:layout_behavior="com.sogou.testforall.CustomBehavior"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" app:layout_scrollFlags="scroll"> </LinearLayout> </android.support.design.widget.AppBarLayout> <android.support.v7.widget.RecyclerView android:id="@+id/rec" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> </android.support.v7.widget.RecyclerView> </com.sogou.testforall.CustomCoordinatorLayout> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btn1" android:text="打開滑動問題一"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="50dp" android:id="@+id/btn2" android:text="打開滑動問題二"/> </FrameLayout>
主要是以CoordinatorLayout+AppBarLayout+RecyclerView的方式呈現滑動嵌套的佈局方式。在使用當前佈局的時候主要遇到了兩個滑動時候的問題,下面依次介紹。app
該問題的復現場景描述爲:觸摸AppBarLayout手指向上滑動,即佈局向下移動,當進行fling時候,手指向下滑動RecyclerView,就會形成滑動的問題。能夠看下下面的gif圖:ide
形成這個的緣由主要是AppBarLayout的fling操做和NestedScrollView聯動形成的問題,關於源碼的分析能夠看我寫的文章:佈局
在AppBarLayout的Behavior中的onTouchEvent()事件中處理了fling事件:學習
public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) { ... case MotionEvent.ACTION_UP: if (mVelocityTracker != null) { mVelocityTracker.addMovement(ev); mVelocityTracker.computeCurrentVelocity(1000); float yvel = mVelocityTracker.getYVelocity(mActivePointerId); fling(parent, child, -getScrollRangeForDragFling(child), 0, yvel); } ... return true; }
在fling的方法中使用OverScroller來模擬進行fling操做,最終會調到setHeaderTopBottomOffset(...)
來使AppBarLayout進行fling的滑動操做。在絕大部分滑動邏輯中,這樣處理是正確的,可是若是在AppBarLayout在fling的時候主動滑動RecyclerView,那麼就會形成動畫抖動的問題了。動畫
在當前狀況下,RecyclerView滑動到頭了,那麼就會把未消費的事件經過NestedScrollingChild2交付由CoordinatorLayout(實現了NestedScrollingParent)處理,parent又最終交付由AppBarLayout.Behavior進行處理的,其中調用的方法以下:this
@Override public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) { if (dyUnconsumed < 0) { // If the scrolling view is scrolling down but not consuming, it's probably be at // the top of it's content scroll(coordinatorLayout, child, dyUnconsumed, -child.getDownNestedScrollRange(), 0); } }
這裏的scroll方法最終會調用setHeaderTopBottomOffset(...)
,因爲兩次分別觸摸在AppBarLayout和RecyclerView的方向不一致,致使了最終的抖動的效果。spa
解決方式也很簡單,只要在CoordinatorLayout的onInterceptedTouchEvent()
中中止AppBarLayout的fling操做就能夠了,直接操做的對象就是AppBarLayout中的Behavior,該Behavior繼承自HeaderBehavior,而fling操做由OverScroller產生,因此自定義一個CustomBehavior:.net
public class CustomBehavior extends AppBarLayout.Behavior { private OverScroller mOverScroller; public CustomBehavior() { super(); } public CustomBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params) { super.onAttachedToLayoutParams(params); } @Override public void onDetachedFromLayoutParams() { super.onDetachedFromLayoutParams(); } @Override public boolean onTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_UP) { reflectOverScroller(); } return super.onTouchEvent(parent, child, ev); } /** * */ public void stopFling() { if (mOverScroller != null) { mOverScroller.abortAnimation(); } } /** * 解決AppbarLayout在fling的時候,再主動滑動RecyclerView致使的動畫錯誤的問題 */ private void reflectOverScroller() { if (mOverScroller == null) { Field field = null; try { field = getClass().getSuperclass() .getSuperclass().getDeclaredField("mScroller"); field.setAccessible(true); Object object = field.get(this); mOverScroller = (OverScroller) object; } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } } }
而後在重寫CoordinatorLayout,暴露一個接口:
public class CustomCoordinatorLayout extends CoordinatorLayout { private OnInterceptTouchListener mListener; public void setOnInterceptTouchListener(OnInterceptTouchListener listener) { mListener = listener; } public CustomCoordinatorLayout(Context context) { super(context); } public CustomCoordinatorLayout(Context context, AttributeSet attrs) { super(context, attrs); } public CustomCoordinatorLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (mListener != null) { mListener.onIntercept(); } return super.onInterceptTouchEvent(ev); } public interface OnInterceptTouchListener { void onIntercept(); } }
接着在接口中處理滑動問題便可:
val customCoordinatorLayout = findViewById<CustomCoordinatorLayout>(R.id.coord) customCoordinatorLayout.setOnInterceptTouchListener { //RecyclerView滑動的時候禁止AppBarLayout的滑動 if (customBehavior != null && !flagOne) { customBehavior!!.stopFling() } }
第二個問題產生的緣由跟第一個問題的操做相反,首先在RecyclerView到頭的時候手指向下滑動RecyclerView,在手指離開後,再經過手指向上滑動AppBarLayout,就會形成這個問題,能夠看下gif圖:
能夠看到手指向上滑動AppBarLayout的時候,直至AppBarLayout徹底滑出屏幕,接着又反彈回到屏幕中了,這個問題形成的緣由是由於在手指向上滑動後形成RecyclerView的fling操做執行,具體的代碼在RecyclerView內部類ViewFlinger中。因爲對RecyclerView的源碼不是很熟,因此經過debug發現ViewFlinger中一直調用dispatchNestedScroll(...)
方法,天然而然就通知到了CoordinatorLayout中,也就天然到了AppBarlayout.Behavior當中的onNestedScroll(...)
中了。問題一也說了AppBarlayout.Behavior當中的onNestedScroll(...)
會調用setHeaderTopBottomOffset(...)
,因爲RecyclerView一直在fling致使了反彈效果的出現。
解決方式就是在CoordinatorLayout中中止RecyclerView的滑動,因爲RecyclerView提供了對應的stopScroll()
方法,因此直接調用便可:
customCoordinatorLayout.setOnInterceptTouchListener { if (!flagTwo) { mRecyclerView.stopScroll() } }