CoordinatorLayout + AppBarLayout + NestScrollView 向上滑動卡頓問題解決方案
問題描述
在商品詳情頁使用CoordinatorLayout + AppBarLayout + NestScrollView佈局組合展示。當向上滾動時,不會產生Fling效果,每當手指離開螢幕,佈局就會停止滾動。當向下滾動時沒有該問題。
問題分析
巢狀滑動的原理為由NestScrollView接收觸控事件並反饋到父檢視,產生聯動效果。猜測沒有產生Fling效果的原因有可能是在NestScrollView中計算手指滑動速度有錯誤。
程式碼分析
關鍵程式碼在NestScrollView的OnTouchEvent方法中:
@Override
public boolean onTouchEvent(MotionEvent ev) {
//部分程式碼省略
//手指滑動事件
case MotionEvent.ACTION_MOVE:
final int activePointerIndex = MotionEventCompat.findPointerIndex(ev,
mActivePointerId);
if (activePointerIndex == -1) {
Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
break;
}
final int y = (int) MotionEventCompat.getY(ev, activePointerIndex);
int deltaY = mLastMotionY - y;
if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) {
deltaY -= mScrollConsumed[1 ];
vtev.offsetLocation(0, mScrollOffset[1]);
mNestedYOffset += mScrollOffset[1];
}
if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
//此處將mIsBeingGragged賦值為true
mIsBeingDragged = true;
if (deltaY > 0) {
deltaY -= mTouchSlop;
} else {
deltaY += mTouchSlop;
}
}
//部分程式碼省略
case MotionEvent.ACTION_UP:
//計算手指滑動速率的標準為mIsBeingGragged為True
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(velocityTracker,
mActivePointerId);
if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
flingWithNestedDispatch(-initialVelocity);
} else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
getScrollRange())) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
mActivePointerId = INVALID_POINTER;
endDrag();
break;
由以上程式碼可以看出,計算手勢滑動速率的前提是滑動增量大於最小滑動距離,即Math.abs(deltaY) > mTouchSlop。而在15行-20行程式碼中有一些對deltaY的計算dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)。
方法呼叫跟蹤
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
if (dx != 0 || dy != 0) {
int startX = 0;
int startY = 0;
if (offsetInWindow != null) {
mView.getLocationInWindow(offsetInWindow);
startX = offsetInWindow[0];
startY = offsetInWindow[1];
}
if (consumed == null) {
if (mTempNestedScrollConsumed == null) {
mTempNestedScrollConsumed = new int[2];
}
consumed = mTempNestedScrollConsumed;
}
consumed[0] = 0;
consumed[1] = 0;
//再次呼叫 ViewParentCompat.onNestedPreScroll(mNestedScrollingParent, mView, dx, dy, consumed);
if (offsetInWindow != null) {
mView.getLocationInWindow(offsetInWindow);
offsetInWindow[0] -= startX;
offsetInWindow[1] -= startY;
}
return consumed[0] != 0 || consumed[1] != 0;
} else if (offsetInWindow != null) {
offsetInWindow[0] = 0;
offsetInWindow[1] = 0;
}
}
return false;
}
CoordinatorLayout的onNestPreScroll方法
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
int xConsumed = 0;
int yConsumed = 0;
boolean accepted = false;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View view = getChildAt(i);
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (!lp.isNestedScrollAccepted()) {
continue;
}
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
mTempIntPair[0] = mTempIntPair[1] = 0;
viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair);
xConsumed = dx > 0 ? Math.max(xConsumed, mTempIntPair[0])
: Math.min(xConsumed, mTempIntPair[0]);
yConsumed = dy > 0 ? Math.max(yConsumed, mTempIntPair[1])
: Math.min(yConsumed, mTempIntPair[1]);
accepted = true;
}
}
consumed[0] = xConsumed;
//將Y軸滾動賦值給consumed[1]
consumed[1] = yConsumed;
if (accepted) {
dispatchOnDependentViewChanged(true);
}
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
View target, int dx, int dy, int[] consumed) {
if (dy != 0 && !mSkipNestedPreScroll) {
int min, max;
if (dy < 0) {
// We're scrolling down
min = -child.getTotalScrollRange();
max = min + child.getDownNestedPreScrollRange();
} else {
// We're scrolling up
min = -child.getUpNestedPreScrollRange();
max = 0;
}
consumed[1] = scroll(coordinatorLayout, child, dy, min, max);
}
}
NestScrollView :: OnTouchEvent() —>ACTION_MOVE —> dispatchNestedPreScroll()方法將NestedScrollView的滾動距離分發給parentView(AppBarLayout),
會呼叫AppBarLayout:: onNestedPreScroll()方法,
該方法會根據滾動距離設定Header的頂部和底部偏移(setHeaderTopBottomOffset()),並返回偏移的距離即滾動距離。
返回到NestScrollView的OnTouchEvent方法後,會重新計算Y軸滾動距離deltaY -= mScrollConsumed[1];計算完成之後deltaY結果為0。
當手指擡起的時候ACTION_UP,此時mIsBeingGragged為false,所以不會Fling。
解決方案
為AppBarLayout自定義Behavior,在巢狀滾動之前(onNestedPreScroll)記錄手指滑動速率,在滾動完成之後(onStopNestedScroll),再次手動呼叫onNestedFling方法。
程式碼實現:
public final class FlingBehavior extends AppBarLayout.Behavior {
private static final String TAG = FlingBehavior.class.getName();
private static final int TOP_CHILD_FLING_THRESHOLD = 1;
private static final float OPTIMAL_FLING_VELOCITY = 3500;
private static final float MIN_FLING_VELOCITY = 20;
boolean shouldFling = false;
float flingVelocityY = 0;
public FlingBehavior() {
}
public FlingBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target,
int velocityX, int velocityY, int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, velocityX, velocityY, consumed);
if (velocityY > MIN_FLING_VELOCITY) {
shouldFling = true;
flingVelocityY = velocityY;
} else {
shouldFling = false;
}
}
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl, View target) {
super.onStopNestedScroll(coordinatorLayout, abl, target);
if (shouldFling) {
Log.d(TAG, "onNestedPreScroll: running nested fling, velocityY is " + flingVelocityY);
onNestedFling(coordinatorLayout, abl, target, 0, flingVelocityY, true);
}
}
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target,
float velocityX, float velocityY, boolean consumed) {
if (target instanceof RecyclerView && velocityY < 0) {
Log.d(TAG, "onNestedFling: target is recyclerView");
final RecyclerView recyclerView = (RecyclerView) target;
final View firstChild = recyclerView.getChildAt(0);
final int childAdapterPosition = recyclerView.getChildAdapterPosition(firstChild);
consumed = childAdapterPosition > TOP_CHILD_FLING_THRESHOLD;
}
// prevent fling flickering when going up
if (target instanceof NestedScrollView && velocityY > 0) {
consumed = true;
}
if (Math.abs(velocityY) < OPTIMAL_FLING_VELOCITY) {
velocityY = OPTIMAL_FLING_VELOCITY * (velocityY < 0 ? -1 : 1);
}
Log.d(TAG, "onNestedFling: velocityY - " + velocityY + ", consumed - " + consumed);
return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
}
在佈局中新增這個Behavior:
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="packagename.FlingBehavior"
android:fitsSystemWindows="true">
相關推薦
CoordinatorLayout + AppBarLayout + NestScrollView 向上滑動卡頓問題解決方案
問題描述 在商品詳情頁使用CoordinatorLayout + AppBarLayout + NestScrollView佈局組合展示。當向上滾動時,不會產生Fling效果,每當手指離開螢幕,佈局就會停止滾動。當向下滾動時沒有該問題。 問題分析 巢狀
UITableView滑動卡頓解決方案
UITableView是一個非常常用的基本檢視,在各類app中隨處可見。對於一般佈局簡單的tableView,效能上基本上看不出來什麼問題。但是對於cell中檢視繁多的tableView,有時候可能就會出現滑動不流暢的現象,以下是本人的一些解決方案,僅供參考。
vue 介面在蘋果手機上滑動點選事件等卡頓解決方案
用vue編寫專案接近尾聲,需要整合到移動端中,在webstorm上介面,執行效果都很完美,但是在蘋果手機上各種問題都出現了,原生專案一向滑動流暢,事件響應迅速,可是蘋果手機開啟這個專案有兩個問題,(1).滑動頁面卡頓,(2).點選事件響應緩慢,百度才發現在蘋果手機上有300ms的延遲。 一.滑動
viewpager+fragment+slidingmenu滑動卡頓解決方法
這兩天在做專案的時候遇到了一個嚴重的問題,就是viewpager中的fragment新增使用slidingmenu實現側滑欄效果後出現滑動卡頓,甚至出現了"java.lang.StackOverflo
平板以及小運存手機APP使用RecyclerView卡頓解決方案
前言 最近一直在做平板類應用的開發,關於自動售貨機的系統,我也是第一次接觸硬體以及Android盒子的開發,從剛開始的陌生到現在的熟悉,感覺還是收穫挺多的,最近專案快要上線了,介面真的很簡單,展示商品用的只是RecyclerView,也沒有巢狀,但是就是滑動卡頓
iOS進階--提高XCode編譯速度、Xcode卡頓解決方案
提升編譯連結的速度主要有以下三個方式: 1. 提高XCode編譯時使用的執行緒數 defaults write com.apple.Xcode PBXNumberOfParallelBuildSubtas
CSS3 動畫卡頓解決方案
為什麼會卡頓? 有一個前提必須要提,前端開發者們都知道,瀏覽器是單執行緒執行的。但是我們要明確以下幾個概念:單執行緒,主執行緒和合成執行緒。 雖然說瀏覽器執行js是單執行緒執行(注意,是執行,並不是說瀏覽器只有1個執行緒,而是執行時,runing),但實際上瀏覽器的2個重要的執行執行緒,
EditText 自動搜尋本地資料庫(大資料量)卡頓解決方案
假設本地存了很多資料,按關鍵字搜尋,而且要求自動搜尋,沒有搜尋按鈕,輸入法上也沒有,就要求這種體驗,當你輸入一個字元的時候,EditText的addTextChangedListener其實就開始監聽了,比如你想搜尋abc ,其實查詢了三次資料庫,先搜a,再ab,然後才是
Android Scrollview嵌套RecyclerView導致滑動卡頓問題解決
private 模式 gin -a ron android ole toc 禁止 一個比較長的界面一般都是Scrollview嵌套RecyclerView來解決.不過這樣的UI並不是我們開發人員想看到的,實際上嵌套之後.因為Scrollview和RecyclerView都是
解決Fragment裡巢狀ViewPager滑動卡頓的問題
一、先來看一下ViewPager左右滑動正常情況: 如下圖所示,Activity 裡有一個ViewPager,ViewPager左右滑動的時候切換“我申請上”和“我受邀上”兩個Fragment 程式碼如下: 二、再來看一下ViewPager左右滑動卡頓的情況: 如
關於移動端滑動卡頓現象的解決方案
-webkit-overflow-scrolling: touch IOS 端特屬屬性,手指離開螢幕會保持滾動一段距離,繼續滾動的速度和持續的時間和滾動手勢的強烈程度成正比。 overflow: scr
iscroll在安卓高版本(6.0以上)某些機型上滑動卡頓問題的解決方法
問題:發現公司專案移動端的分類頁面在某些安卓機型上滑動時異常卡頓,而且出現卡頓的手機都是非常新的安卓手機,除錯的時候發現在谷歌瀏覽器的手機模擬滾動時也非常卡頓 在一段糾結異常的除錯和搜尋下找到了解決方法: 使用用fixed版本的iscroll就可以了:https://github.com/
Android 介面滑動卡頓分析與解決方案
導致Android介面滑動卡頓主要有兩個原因: 1.UI執行緒(main)有耗時操作 2.檢視渲染時間過長,導致卡頓 目前只講第1點,第二點相對比較複雜待以後慢慢研究。 眾所周知,介面的流暢度主要依賴FPS這個值,這個值是通過(1s/渲染1幀所花費的時間)計算所得,FPS值越大視訊越流暢,所以就需要渲染1幀
解決頁面使用overflow: scroll在iOS上滑動卡頓的問題
http://www.jianshu.com/p/1f4693d0ad2d 以下程式碼可解決這種卡頓的問題:-webkit-overflow-scrolling: touch;,是因為這行程式碼啟用了硬體加速特性,所以滑動很流暢。 實際上,Safari真的用了原生控制元
解決ScrollView巢狀RecyclerView 滑動卡頓和巢狀多個RecyclerView 顯示不全的問題
ScrollView巢狀RecyclerView ,滑動會卡頓,解決方法是: //防止滑動卡頓 GridLayoutManager gridLayoutManager=new GridLayoutManager(this, 4){
Android Scrollview巢狀RecyclerView導致滑動卡頓問題解決
一個比較長的介面一般都是Scrollview巢狀RecyclerView來解決.不過這樣的UI並不是我們開發人員想看到的,實際上巢狀之後.因為Scrollview和RecyclerView都是滑動控制元件.會有一點滑動上的衝突.導致滑動起來有些卡頓.這個時候.我們重寫一下L
NestedScrollView+RecyclerView 滑動卡頓簡單解決方案
這個是在工作中發現的問題 以下xml是當前佈局: <code> <android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" android
解決RecycleView巢狀RecycleView滑動卡頓的問題
1.話不多說,直接上程式碼 public class MyRecycleView extends RecyclerView { public MyRecycleView(Context co
Android AppBarLayout + RecyclerView 下滑到第一條卡頓解決之道
網上給出的方法大致為一下四種,擇優食用 1. 自定義一個 behavior public class FlingBehavior extends AppBarLayout.Behavior { private static
android中scrollview巢狀HorizontalScrollView導致橫向滑動卡頓現象解決
也許會有人遇到,在這裡說下解決方法。方便以後有人糾結這個問題。 開發中經驗會遇到滑動裡面嵌入滑動的問題,但是這種情況下觸控事件就會發生衝突。導致滑動非常卡,甚至出現程式停止響應。這種情況下我們一般需要重寫view。下面給出重新scrollview的方法 ? pub