LinearSnapHelper 的 一個(gè)坑

前記

我上篇文章 Android Support Library 24.2.0 更新介紹 簡(jiǎn)單介紹了 LinearSnapHelper,有點(diǎn)類似 ViewPager,比如可以用于橫向?yàn)g覽圖片,LinearSnapHelper 保證了每次滾動(dòng)完自動(dòng)滑動(dòng)到中心的 Item.

正題

前幾天項(xiàng)目有個(gè)需求,看了一下剛好可以用上 LinearSnapHelper,用起來非常簡(jiǎn)單

SnapHelper snapHelper = new LinearSnapHelper();
snapHelper.attachToRecyclerView(recyclerView);

實(shí)際運(yùn)行效果如圖

運(yùn)行效果圖.png

因?yàn)橛锌v橫向的滾動(dòng),所以用了 RecyclerView 的嵌套,目前看運(yùn)行良好。

但是實(shí)際體驗(yàn)發(fā)現(xiàn)有個(gè)問題,有時(shí)候在手指在內(nèi)層的 RecyclerView 上下滾動(dòng)時(shí),觸摸事件被吃了,沒有反應(yīng),按道理講,橫向滾動(dòng)的 RecyclerView 應(yīng)該不攔截這種事件才對(duì)。而且這個(gè)現(xiàn)象并不是必現(xiàn),發(fā)現(xiàn)如果是橫向滾動(dòng)到第二個(gè) Item 在縱向滾 就沒有問題,只有第一個(gè) Item (其實(shí)還有最后一個(gè) Item)會(huì)出現(xiàn)這種現(xiàn)象,簡(jiǎn)單講 出現(xiàn)的步驟大概是這樣的,先滾動(dòng)到 第二個(gè) Item 在滾回來第一個(gè) Item 就會(huì)出現(xiàn)這個(gè)bug。

Fix 之路

首先 debug 了 內(nèi)層 RecyclerView 的 onInterceptTouchEvent,發(fā)現(xiàn)這個(gè)時(shí)候 return 為 true, 而且這時(shí) RecyclerView 的狀態(tài) 不是 SCROLL_STATE_IDLE,而是 SCROLL_STATE_SETTLING ,看 RecyclerView 的源碼

if (mScrollState == SCROLL_STATE_SETTLING) {
    getParent().requestDisallowInterceptTouchEvent(true);
    setScrollState(SCROLL_STATE_DRAGGING);
}
...
return mScrollState == SCROLL_STATE_DRAGGING;

因此這個(gè)時(shí)候 RecyclerView 會(huì)攔截事件。
事實(shí)上導(dǎo)致 RecyclerView 的狀態(tài) 為 SCROLL_STATE_SETTLING 是因?yàn)?LinearSnapHelper。
LinearSnapHelper 原理是監(jiān)聽 RecyclerView 的滾動(dòng)狀態(tài),一旦處于RecyclerView.SCROLL_STATE_IDLE (當(dāng)然還有 Fling 的情況,這里先不看) 就會(huì)計(jì)算得到要處于中心的 Item View,然后在計(jì)算需要滾動(dòng)的距離。
處于中心這個(gè)點(diǎn)很重要,細(xì)心的讀者可能會(huì)發(fā)現(xiàn)第一個(gè) Item 并不會(huì)處于中心的,那么這個(gè)時(shí)候 LinearSnapHelper 會(huì)怎么處理呢?
LinearSnapHelper 會(huì)根據(jù) calculateDistanceToFinalSnap 得到的距離 利用 RecyclerView 的 smoothScrollBy 來滾動(dòng),而這個(gè)時(shí)候第一個(gè) Item 永遠(yuǎn)都滾動(dòng)不到中心的位置,于是不停的 ScrollBy 也就導(dǎo)致 狀態(tài) 為 SCROLL_STATE_SETTLING。

知道原因后就好辦了, 重寫 calculateDistanceToFinalSnap 修正距離就可以了
用以下的 FixLinearSnapHelper 代替 LinearSnapHelper 就可以了。

public class FixLinearSnapHelper extends LinearSnapHelper{

    private OrientationHelper mVerticalHelper;

    private OrientationHelper mHorizontalHelper;

    private RecyclerView mRecyclerView;

    @Override
    public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager,
                                              @NonNull View targetView) {
        int[] out = new int[2];

        if (layoutManager.canScrollHorizontally()) {
            out[0] = distanceToCenter(targetView, getHorizontalHelper(layoutManager));
        } else {
            out[0] = 0;
        }

        if (layoutManager.canScrollVertically()) {
            out[1] = distanceToCenter(targetView, getVerticalHelper(layoutManager));
        } else {
            out[1] = 0;
        }
        return out;
    }

    @Override
    public void attachToRecyclerView(@Nullable RecyclerView recyclerView) throws IllegalStateException {
        this.mRecyclerView = recyclerView;
        super.attachToRecyclerView(recyclerView);
    }

    private int distanceToCenter(View targetView, OrientationHelper helper) {
        //如果已經(jīng)滾動(dòng)到盡頭 并且判斷是否是第一個(gè)item或者是最后一個(gè),直接返回0,不用多余的滾動(dòng)了 
        if ((helper.getDecoratedStart(targetView) == 0 && mRecyclerView.getChildAdapterPosition(targetView) == 0)
                || (helper.getDecoratedEnd(targetView) == helper.getEndAfterPadding()
                && mRecyclerView.getChildAdapterPosition(targetView) == mRecyclerView.getAdapter().getItemCount() - 1) )
            return 0;

        int viewCenter = helper.getDecoratedStart(targetView) + (helper.getDecoratedEnd(targetView) - helper.getDecoratedStart(targetView))/2;
        int correctCenter = (helper.getEndAfterPadding() - helper.getStartAfterPadding())/2;
        return viewCenter - correctCenter;
    }

    private OrientationHelper getVerticalHelper(RecyclerView.LayoutManager layoutManager) {
        if (mVerticalHelper == null) {
            mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager);
        }
        return mVerticalHelper;
    }

    private OrientationHelper getHorizontalHelper(RecyclerView.LayoutManager layoutManager) {
        if (mHorizontalHelper == null) {
            mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager);
        }
        return mHorizontalHelper;
    }

}

最后編輯于
?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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