CoordinatorLayout+AppBarLayout+RecyclerView 滑動衝突引發螢幕抖動
專案示例圖
出現的問題連結:https://ask.csdn.net/questions/363070
跟這個哥們遇到的問題一樣一樣的;
原因
用到的佈局結構就是CoordinatorLayout+AppBarLayout+ViewPager 然後Viewpager裡是兩個RecyclerView;
當你appbar高度低的時候一般不會觸發這個問題,因為appbar fling 豪無用武之地。
只有你的appbar的高度到達一定的程度,那麼問題就出來。
So You know !! 原因是什麼了吧。其實就是appbar 有向下滑動的事件 ,然後你recylerView有向上滑動的事件,兩個事件衝突,你往下挪動一下,我往上挪動一下,沙卡拉卡一會。
解決
問題出來首先要找根源,recyclerview 跟appbar 關聯的橋樑是什麼, Behavior ,具體說是 AppBarLayout.Behavior
但是這個類中並麼有處理滑動的東東,so 向上刨根問題。 看他的父類HeaderBehavior
@Override
public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
if (mTouchSlop < 0) {
mTouchSlop = ViewConfiguration.get(parent.getContext()).getScaledTouchSlop();
}
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
final int x = (int) ev.getX();
final int y = (int) ev.getY();
if (parent.isPointInChildBounds(child, x, y) && canDragView(child)) {
mLastMotionY = y;
mActivePointerId = ev.getPointerId(0 );
ensureVelocityTracker();
} else {
return false;
}
break;
}
case MotionEvent.ACTION_MOVE: {
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
if (activePointerIndex == -1) {
return false;
}
final int y = (int) ev.getY(activePointerIndex);
int dy = mLastMotionY - y;
if (!mIsBeingDragged && Math.abs(dy) > mTouchSlop) {
mIsBeingDragged = true;
if (dy > 0) {
dy -= mTouchSlop;
} else {
dy += mTouchSlop;
}
}
if (mIsBeingDragged) {
mLastMotionY = y;
// We're being dragged so scroll the ABL
scroll(parent, child, dy, getMaxDragOffset(child), 0);
}
break;
}
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);
}
// $FALLTHROUGH
case MotionEvent.ACTION_CANCEL: {
mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
break;
}
}
if (mVelocityTracker != null) {
mVelocityTracker.addMovement(ev);
}
return true;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
看到了吧 在這個類中賦予了滑動屬性。 fling(parent, child, -getScrollRangeForDragFling(child), 0, yvel); 這個就是問題出現的原因, fling 還在進行 你就開始向上滑動,肯定有問題啊。
解決辦法跟NestedScrolling機制 有關。
不懂這套機制的可以參考這篇文章:https://blog.csdn.net/lmj623565791/article/details/52204039
簡單說呢 就是recyclerView滑動前會觸發onNestedPreScroll 方法 告訴他爸爸 讓他爸爸知道他要開始滑動了,so我們在這個裡做操作直接結束Appbar的滑動就行。
程式碼
import android.content.Context;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.OverScroller;
import java.lang.reflect.Field;
/**
* Created by 于德海 on 2018/4/27.
* package inter.baisong.widgets
* email : [email protected]
*
* @describe 自定義behavior 以解決滑動抖動
*
*/
public class CustomBehavior extends AppBarLayout.Behavior {
private OverScroller mScroller;
public CustomBehavior() {
}
public CustomBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
getParentScroller(context);
}
/**
* 反射獲得滑動屬性。
*
* @param context
*/
private void getParentScroller(Context context) {
if (mScroller != null) return;
mScroller = new OverScroller(context);
try {
Class<?> reflex_class = getClass().getSuperclass().getSuperclass();//父類AppBarLayout.Behavior 父類的父類 HeaderBehavior
Field fieldScroller = reflex_class.getDeclaredField("mScroller");
fieldScroller.setAccessible(true);
fieldScroller.set(this, mScroller);
} catch (Exception e) {}
}
//fling上滑appbar然後迅速fling下滑recycler時, HeaderBehavior的mScroller並未停止, 會導致上下來回晃動
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed, int type) {
if(mScroller!=null){ //當recyclerView 做好滑動準備的時候 直接幹掉Appbar的滑動
if (mScroller.computeScrollOffset()) {
mScroller.abortAnimation();
}
}
if (type == ViewCompat.TYPE_NON_TOUCH&&getTopAndBottomOffset() == 0) { //recyclerview 雞兒的 慣性比較大 會頂在頭部一會兒 到頭直接幹掉它的滑動
ViewCompat.stopNestedScroll(target, type);
}
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type);
}
@Override
public boolean onTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent e) {
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
break;
}
return super.onTouchEvent(parent,child,e);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
使用位置是在佈局檔案的appbarlayout中加behavior
示例:
<android.support.design.widget.AppBarLayout
android:id="@+id/mAppbar"
app:elevation="0dip"
app:layout_behavior="inter.****.widgets.CustomBehavior"
android:layout_width="match_parent"
android:layout_height="wrap_content">
- 1
- 2
- 3
- 4
- 5
- 6
結尾
大工告成, game over !
轉載至:https://blog.csdn.net/a940659387/article/details/80136852