幾行代碼巧妙解決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);
歡迎交流學習