几行代码巧妙解决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);
欢迎交流学习