幾行代碼巧妙解決RecycleView 短距離滾動速度太快引起的閃屏問題

幾行代碼巧妙解決RecycleView 短距離滾動速度太快引起的閃屏問題

在實際開發工作中,我們會在項目裏面大量使用RecycleView 控件,在大多數情況下RecycleView 都能很好滿足我們的需求,但是注重細節的同學會發現在使用RecycleView 的 smoothScrollBy 時,當滾動距離很小時,滾動太快,給人造成一種閃屏的感覺,
這就是因爲RecycleView 默認滾動速度太快引起。說到這裏,大部分同學已經有了解決方案,自定義控件繼承RecycleView,複寫各種方法,但是實際使用起來非常不舒服。
這裏就分享一種樓主使用的“偷巧”方案,完美解決無法控制滾動速度的問題

    /**
     * Animate a scroll by the given amount of pixels along either axis.
     *
     * @param dx Pixels to scroll horizontally
     * @param dy Pixels to scroll vertically
     */
    public void smoothScrollBy(int dx, int dy) {
        smoothScrollBy(dx, dy, null);
    }

    /**
     * Animate a scroll by the given amount of pixels along either axis.
     *
     * @param dx Pixels to scroll horizontally
     * @param dy Pixels to scroll vertically
     * @param interpolator {@link Interpolator} to be used for scrolling. If it is
     *                     {@code null}, RecyclerView is going to use the default interpolator.
     */
    public void smoothScrollBy(int dx, int dy, Interpolator interpolator) {
        if (mLayout == null) {
            Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. "
                    + "Call setLayoutManager with a non-null argument.");
            return;
        }
        if (mLayoutFrozen) {
            return;
        }
        if (!mLayout.canScrollHorizontally()) {
            dx = 0;
        }
        if (!mLayout.canScrollVertically()) {
            dy = 0;
        }
        if (dx != 0 || dy != 0) {
            mViewFlinger.smoothScrollBy(dx, dy, interpolator);
        }
    }

可以看到最後滾動實際調用的是mViewFlinger.smoothScrollBy(dx, dy, interpolator);方法,我們進入mViewFlinger 源碼查看

        public void smoothScrollBy(int dx, int dy, Interpolator interpolator) {
            smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, 0, 0),
                    interpolator == null ? sQuinticInterpolator : interpolator);
        }

發現computeScrollDuration 就是計算時間滾動時間

        private int computeScrollDuration(int dx, int dy, int vx, int vy) {
            final int absDx = Math.abs(dx);
            final int absDy = Math.abs(dy);
            final boolean horizontal = absDx > absDy;
            final int velocity = (int) Math.sqrt(vx * vx + vy * vy);
            final int delta = (int) Math.sqrt(dx * dx + dy * dy);
            final int containerSize = horizontal ? getWidth() : getHeight();
            final int halfContainerSize = containerSize / 2;
            final float distanceRatio = Math.min(1.f, 1.f * delta / containerSize);
            final float distance = halfContainerSize + halfContainerSize
                    * distanceInfluenceForSnapDuration(distanceRatio);

            final int duration;
            if (velocity > 0) {
                duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
            } else {
                float absDelta = (float) (horizontal ? absDx : absDy);
                duration = (int) (((absDelta / containerSize) + 1) * 300);
            }
            return Math.min(duration, MAX_SCROLL_DURATION);
        }

可以看出這裏不管最小的滾動距離是多大,最小的滾動時間300ms,Google 沒有給我們暴露修改時間的方法,我們無法複寫修改

高潮來了…

可以看出,這裏計算時間是判斷控件時左右滾動還是垂直滾動,當左右滾動距離大於垂直滾動距離時,就使用左右滾動的距離計算滾動時間,我們在控制垂直滾動距離時,是否可以使用大的水平滾動距離來計算垂直的滾動時間呢,答案是肯定的。這點也剛好是取巧的地方,通過水平滾動距離控制垂直滾動時間。

    public void smoothScrollBy(int dx, int dy, Interpolator interpolator) {
        if (mLayout == null) {
            Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. "
                    + "Call setLayoutManager with a non-null argument.");
            return;
        }
        if (mLayoutFrozen) {
            return;
        }
        if (!mLayout.canScrollHorizontally()) {
            dx = 0;
        }
        if (!mLayout.canScrollVertically()) {
            dy = 0;
        }
        if (dx != 0 || dy != 0) {
            mViewFlinger.smoothScrollBy(dx, dy, interpolator);
        }
    }

通過查看smoothScrollBy源碼,發現Google在計算滾動距離前做了水平滾動還是垂直滾動的判斷,也就是說只能水平和垂直兩個方向二選一

if (!mLayout.canScrollHorizontally()) {
            dx = 0;
        }

那我們是否可以修改這個方法,讓我們傳遞的dx值不被重新歸零呢,這不就可以實現利用水平滾動距離控制垂直滾動時間嗎,答案是肯定的。這裏的mLayout 就是RecycleView 設置的LayoutManager。我們完全可以自定義LayoutManager 控制

import android.content.Context;
import android.graphics.PointF;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearSmoothScroller;
import android.support.v7.widget.FoldGridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.DisplayMetrics;

public class MyManager extends GridLayoutManager {

    private boolean isStartScroll;

    public MyManager (Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    public MyManager (Context context, int spanCount) {
        super(context, spanCount);
    }

    public MyManager (Context context, int spanCount, int orientation, boolean reverseLayout) {
        super(context, spanCount, orientation, reverseLayout);
    }


    public void setStartScroll(boolean startScroll) {
        isStartScroll = startScroll;
    }

    @Override
    public boolean canScrollHorizontally() {
        return isStartScroll;
    }
}

這樣在我們使用中可以方便的控制垂直方向短距離滾動時間

       //爲了繞過dx被歸零,我們先設置水平可以滾動
       scrollManager.setStartScroll(true);
       //dx爲screenHeight*2 就可以增加一倍的滾動時間
       smoothScrollBy(screenHeight*2, screenHeight);
       //還原回水平不可滾
       scrollManager.setStartScroll(false);

歡迎交流學習

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章