1. 程式人生 > >CoordinatorLayout + AppBarLayout + NestScrollView 向上滑動卡頓問題解決方案

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