这里记录一下在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() } }