RecyclerView滾動(dòng)位置,滾動(dòng)速度設(shè)置

自行車Boy.jpg

前言

最近開發(fā)中遇到了一個(gè)需求,需要RecyclerView滾動(dòng)到指定位置后置頂顯示,當(dāng)時(shí)遇到這個(gè)問題的時(shí)候,心里第一反應(yīng)是直接使用RecyclerView的smoothScrollToPosition()方法,實(shí)現(xiàn)對(duì)應(yīng)位置的平滑滾動(dòng)。但是在實(shí)際使用中發(fā)現(xiàn)并沒有到底自己想要的效果。本想著偷懶直接從網(wǎng)上Copy下,但是發(fā)現(xiàn)效果并不是很好。于是就自己去研究源碼。

該系列文章分為兩篇文章。

  • 如果你想解決通過smoothScrollToPosition滾動(dòng)到頂部,或者滾動(dòng)加速,請(qǐng)觀看本篇文章,
  • 如果你想了解其內(nèi)部實(shí)現(xiàn),請(qǐng)看RecyclerView.smoothScrollToPosition了解一下

注意?。?!注意?。?!注意!?。?br> 這是使用的LinearLayoutManager且是豎直方向上的,橫向的思路是一樣的,只是修改的方法不一樣,大家一定要注意前提條件。

如何使用smoothScrollToPosition滾動(dòng)到頂部?

如果你看了我的另一篇文章RecyclerView.smoothScrollToPosition了解一下,大家應(yīng)該會(huì)清楚,其實(shí)在你設(shè)定目標(biāo)位置后,當(dāng)找到目標(biāo)視圖后,最后讓RecyclerView進(jìn)行滾動(dòng)的方法是其對(duì)應(yīng)LinearLayoutManager中的LinearSmoothScroller的calculateDtToFit()方法。

 public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int
            snapPreference) {
        switch (snapPreference) {
            case SNAP_TO_START:
                return boxStart - viewStart;
            case SNAP_TO_END:
                return boxEnd - viewEnd;
            case SNAP_TO_ANY:
                final int dtStart = boxStart - viewStart;
                if (dtStart > 0) {
                    return dtStart;
                }
                final int dtEnd = boxEnd - viewEnd;
                if (dtEnd < 0) {
                    return dtEnd;
                }
                break;
            default:
                throw new IllegalArgumentException("snap preference should be one of the"
                        + " constants defined in SmoothScroller, starting with SNAP_");
        }
        return 0;
    }

也就是說在LinerlayoutManager為豎直的情況下,snapPreference默認(rèn)為SNAP_ANY,那么我們就可以得到,下面三種情況。

  • 當(dāng)滾動(dòng)位置在可見范圍之內(nèi)時(shí)
    滾動(dòng)距離為0,故不會(huì)滾動(dòng)。
  • 當(dāng)滾動(dòng)位置在可見范圍之前時(shí)
    內(nèi)容向上滾動(dòng)且只能滾動(dòng)到頂部。
  • 當(dāng)滾動(dòng)位置在可見范圍距離之外時(shí)
    內(nèi)容向下滾動(dòng),且只能滾動(dòng)到底部。

同時(shí)snapPreference的值是通過LinearSmoothScroller中的getVerticalSnapPreference()與getHorizontalSnapPreference() 來設(shè)定的。

所以為了使?jié)L動(dòng)位置對(duì)應(yīng)的目標(biāo)視圖在頂部顯示,那么我們創(chuàng)建一個(gè)新類并繼承LinearLayoutManager。同時(shí)創(chuàng)建TopSnappedSmoothScroller繼承LinearSmoothScroller,并重寫它的getVerticalSnapPreference()方法就行了。(如果你是橫向的,請(qǐng)修改getHorizontalSnapPreference方法)


public class LinearLayoutManagerWithScrollTop extends LinearLayoutManager {

    public LinearLayoutManagerWithScrollTop(Context context) {
        super(context);
    }

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

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

    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
        TopSnappedSmoothScroller topSnappedSmoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext());
        topSnappedSmoothScroller.setTargetPosition(position);
        startSmoothScroll(topSnappedSmoothScroller);
    }

    class TopSnappedSmoothScroller extends LinearSmoothScroller {

        public TopSnappedSmoothScroller(Context context) {
            super(context);
        }

        @Nullable
        @Override
        public PointF computeScrollVectorForPosition(int targetPosition) {
            return LinearLayoutManagerWithScrollTop.this.computeScrollVectorForPosition(targetPosition);
        }

        @Override
        protected int getVerticalSnapPreference() {
            return SNAP_TO_START;//設(shè)置滾動(dòng)位置
        }
    }
}

創(chuàng)建該類后,我們接下來就只用給RecyclerView設(shè)置對(duì)應(yīng)的新的布局管理器,并調(diào)用smoothScrollToPosition()方法就行了。

如何設(shè)置smoothScrollToPosition滾動(dòng)的速度?

其實(shí)在RecyclerView中,滾動(dòng)到指定位置是分為了兩個(gè)部分,第一個(gè)是沒有找到目標(biāo)位置對(duì)應(yīng)的視圖之前的速度,一種是找到目標(biāo)位置對(duì)應(yīng)的視圖之后滾動(dòng)的速度。

沒有找到目標(biāo)位置之前

 action.update((int) (mInterimTargetDx * TARGET_SEEK_EXTRA_SCROLL_RATIO),
                (int) (mInterimTargetDy * TARGET_SEEK_EXTRA_SCROLL_RATIO),
                (int) (time * TARGET_SEEK_EXTRA_SCROLL_RATIO), mLinearInterpolator);

在開始尋找目標(biāo)位置時(shí),默認(rèn)的開始距離是12000(單位:px),且這里大家注意,我們使用了LinearInterpolator,也就是說在沒有找到目標(biāo)位置之前,我們的RecyclerView速度是恒定的。

找到目標(biāo)位置之后

action.update(-dx, -dy, time, mDecelerateInterpolator);

這里我們使用了DecelerateInterpolator。也就是說,找到目標(biāo)位置之后,RecyclerView是速度是慢慢減小。

所以現(xiàn)在就提供了一個(gè)思路,我們可以去修改兩個(gè)部分的插值器,來改變RecyclerView的滾動(dòng)速度,當(dāng)然我這里并沒有給實(shí)例代碼,因?yàn)槲野l(fā)現(xiàn)Google并沒有想讓我們?nèi)バ薷牟逯灯鞯南敕?,因?yàn)樵谄銵inearSmoothScroller中,他直接把兩個(gè)插值器用protected修飾。(所以我覺得這樣改,感覺不優(yōu)雅)如果有興趣的小伙伴,可以去修改。

那現(xiàn)在怎么修改速度呢?

既然以修改插值器的方式比較麻煩,那么我們可以修改滾動(dòng)時(shí)間啊!!!!!!希望大家還記得,我們?cè)谡{(diào)用Action的update方法時(shí),我們不僅保存了RecyclerView需要滾動(dòng)的距離,我們還保存了滑動(dòng)總共需要的時(shí)間。

滑動(dòng)所需要的時(shí)間是通過calculateTimeForScrolling()這個(gè)方法來進(jìn)行計(jì)算的。

    protected int calculateTimeForScrolling(int dx) {
        //這里對(duì)時(shí)間進(jìn)行了四舍五入操作。 
        return (int) Math.ceil(Math.abs(dx) * MILLISECONDS_PER_PX);
    }

其中MILLISECONDS_PER_PX 會(huì)在LinearSmoothScroller初始化的時(shí)候創(chuàng)建。

public LinearSmoothScroller(Context context) {
      MILLISECONDS_PER_PX = calculateSpeedPerPixel(context.getResources().getDisplayMetrics());
    }

查看calculateSpeedPerPixel()方法

    private static final float MILLISECONDS_PER_INCH = 25f;// 默認(rèn)為移動(dòng)一英寸需要花費(fèi)25ms
    //
    protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
        return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
    }

也就是說,當(dāng)前滾動(dòng)的速度是與屏幕的像素密度相關(guān), 通過獲取當(dāng)前手機(jī)屏幕每英寸的像素密度,與每英寸移動(dòng)所需要花費(fèi)的時(shí)間,用每英寸移動(dòng)所需要花費(fèi)的時(shí)間除以像素密度就能計(jì)算出移動(dòng)一個(gè)像素密度需要花費(fèi)的時(shí)間。

那么現(xiàn)在,就可以通過兩個(gè)方法來修改RecyclerView的滾動(dòng)速度,要么我們修改calculateSpeedPerPixel方法修改移動(dòng)一個(gè)像素需要花費(fèi)的時(shí)間。要么我們修改calculateTimeForScrolling方法。

這里我采用修改calculateSpeedPerPixel方法來改變速度。這里我修改移動(dòng)一英寸需要花費(fèi)為10ms,那代表著滾動(dòng)速度加快了。那么對(duì)應(yīng)的滾動(dòng)時(shí)間就變小了

  protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {   
      return 10f / displayMetrics.densityDpi;                                                      
  }               
                                                            

到了這里我相信大家已經(jīng)明白了,怎么去修改速度與滾動(dòng)位置了。好啦好啦,先睡了太困了。

對(duì)了對(duì)了,源碼在這里。大家如果有興趣,可以去研究一下。

最后

最后,附上我寫的一個(gè)基于Kotlin 仿開眼的項(xiàng)目SimpleEyes(ps: 其實(shí)在我之前,已經(jīng)有很多小朋友開始仿這款應(yīng)用了,但是我覺得要做就做好。所以我的項(xiàng)目和其他的人應(yīng)該不同,不僅僅是簡單的一個(gè)應(yīng)用。但是,但是。但是。重要的話說三遍。還在開發(fā)階段,不要打我),歡迎大家follow和start.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 最近開發(fā)中遇到了一個(gè)需求,需要RecyclerView滾動(dòng)到指定位置后置頂顯示,當(dāng)時(shí)遇到這個(gè)問題的時(shí)候,心里...
    AndyJennifer閱讀 19,888評(píng)論 3 30
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,777評(píng)論 25 709
  • 【Android 控件 RecyclerView】 概述 RecyclerView是什么 從Android 5.0...
    Rtia閱讀 308,440評(píng)論 27 440
  • 手機(jī)閱讀近年來迅猛發(fā)展,借助微信公眾平臺(tái)的自媒體逐步成熟。微信公眾號(hào)成為各行業(yè)的標(biāo)配,用于傳遞價(jià)值觀,公布消息,發(fā)...
    漉真的后花園閱讀 701評(píng)論 0 0
  • 中學(xué)時(shí)代我默默的喜歡一個(gè)男生他大我一屆我不知道我當(dāng)初是怎么想的,也許那時(shí)是被他的笑容迷惑了就沒有想那么多。 如今每...
    如鯨向海一般閱讀 282評(píng)論 0 0

友情鏈接更多精彩內(nèi)容