几行代码巧妙解决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);

欢迎交流学习

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值