1. 程式人生 > >使用RecyclerView滾動到螢幕指定位置

使用RecyclerView滾動到螢幕指定位置

背景:

    需求中有一個詳情頁新增懸浮錨點條,要求滾動到不同的item,對應錨點高亮,點選錨點滾動到指定位置。

實現及問題:

    監聽滾動過程使錨點高亮並不難實現,問題在於點選錨點滾動到指定位置;

    因為專案結構使用的listview,自然想到了listview.smoothScrollToPositionFromTop(int pos, int offset);

因為在頂部有一個titleBar漸變的浮層,所以使用帶有offset的過載方法。但這個方法存在問題,就是滾動定位不準確。listview的setSelectionFromTop(int pos, int offset)這個方法很準確,但是沒有滾動效果(很突兀)。從google也找了最優的解決方案,就是監聽listview的ScrollState,在滾動停止的第一時間呼叫setSelectionFromTop,最後一閃而過的短距離突兀可以忽略。但是在條目特別複雜(比如我們的詳情頁)極端情況還是會出現不準確的問題。

    無奈只好使用RecyclerView重構。RecyclerView滾動方法:

rv.smoothScrollToPosition(int pos):沒有offeset引數,因為我要考慮titleBar的高度。

rv.smoothScrollBy(int dx, int dy):存在橫向偏移量,但是沒有position位置。

    recyclerView的layoutManger還存在一個方法:

linearLayoutManger.scrollToPositionWithOffset(int pos, int offset):這個看似沒問題,其實也是沒有滾動效果。

最終方案:

後來從 stackOver

 看到可以通過自定義LinearSmoothScroller,傳入LinearlayoutManager可以實現類似需求:

mSmoothScroller.setTargetPosition(mCurrentPos);
mDetailRvManager.startSmoothScroll(mSmoothScroller);
package com.xx.xx.detailpage;

import android.content.Context;
import android.support.v7.widget.LinearSmoothScroller;
import android.view.View;

/**
 * @description recyclerView滾動器
 */
public class DetailRvSmoothScroller extends LinearSmoothScroller {

    private View mTitleBar;

    DetailRvSmoothScroller(Context context, View titleBar) {
        super(context);
        this.mTitleBar = titleBar;
    }

    /**
     * 指定滾動停留位置
     * @return {@link #LinearSmoothScroller#SNAP_TO_START},{@link #LinearSmoothScroller#SNAP_TO_END},{@link #LinearSmoothScroller#SNAP_TO_ANY}
     * 1.將子檢視的左側或頂部與父檢視的左側或頂部對齊;
     * 2.將子檢視的右側或底部與父檢視的右側或底部對齊;
     * 3.具體取決於其當前與其父代相關的位置,也是預設設定。
     */
    @Override
    protected int getVerticalSnapPreference() {
        return LinearSmoothScroller.SNAP_TO_START;
    }

    /**
     * 計算最終需要滾動的距離
     * @param viewStart
     * @param viewEnd
     * @param boxStart
     * @param boxEnd
     * @param snapPreference
     * @return
     */
    @Override
    public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference) {
        switch (snapPreference) {
            case SNAP_TO_START:
                return boxStart - viewStart + mTitleBar.getMeasuredHeight();
            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;
    }
}

這裡只是重寫了getVerticalSnapPreference和calculateDtToFit方法。

當然這個滾動器很強大,如果你需要實現指定滾動速度還可以實現calculateSpeedPerPixel方法。