最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

RecyclerView無限循環(huán)效果實現(xiàn)及示例解析

 更新時間:2023年03月29日 11:35:21   作者:紫槐  
這篇文章主要為大家介紹了RecyclerView無限循環(huán)效果實現(xiàn)及示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

前言

前兩天在逛博客的時候發(fā)現(xiàn)了這樣一張直播間的截圖,其中這個商品列表的切換和循環(huán)播放效果感覺挺好:

熟悉android的同學應該很快能想到這是recyclerView實現(xiàn)的線性列表,其主要有兩個效果:

1.頂部item切換后樣式放大+轉(zhuǎn)場動畫。2.列表自動、無限循環(huán)播放。

第一個效果比較好實現(xiàn),頂部item布局的變化可以通過對RecyclerView進行OnScroll監(jiān)聽,判斷item位置,做scale縮放?;蛘咴谧远xlayoutManager在做layoutChild相關(guān)操作時判斷第一個可見的item并修改樣式。

自動播放則可以通過使用手勢判斷+延時任務來做。

本文主要提供關(guān)于第二個無限循環(huán)播放效果的自定義LayoutManager的實現(xiàn)。

有現(xiàn)成的輪子嗎?

先看看有沒有合適的輪子,“不要重復造輪子”,除非輪子不滿足需求。

1、修改adpter和數(shù)據(jù)映射實現(xiàn)

google了一下,有關(guān)recyclerView無限循環(huán)的博客很多,內(nèi)容基本一模一樣。大部分的博客都提到/使用了一種修改adpter以及數(shù)據(jù)映射的方式,主要有以下幾步:

1. 修改adapter的getItemCount()方法,讓其返回Integer.MAX_VALUE

2. 在取item的數(shù)據(jù)時,使用索引為position % list.size

3. 初始化的時候,讓recyclerView滑到近似Integer.MAX_VALUE/2的位置,避免用戶滑到邊界。

在逛stackOverFlow時找到了這種方案的出處: java - How to cycle through items in Android RecyclerView? - Stack Overflow

這個方法是建立了一個數(shù)據(jù)和位置的映射關(guān)系,因為itemCount無限大,所以用戶可以一直滑下去,又因?qū)ξ恢门c數(shù)據(jù)的取余操作,就可以在每經(jīng)歷一個數(shù)據(jù)的循環(huán)后重新開始??瓷先ecyclerView就是無限循環(huán)的。

很多博客會說這種方法并不好,例如對索引進行了計算/用戶可能會滑到邊界導致需要再次動態(tài)調(diào)整到中間之類的。然后自己寫了一份自定義layoutManager后覺得用自定義layoutManager的方法更好。

其實我倒不這么覺得。

事實上,這種方法已經(jīng)可以很好地滿足大部分無限循環(huán)的場景,并且由于它依然沿用了LinearLayoutManager。就代表列表依舊可以使用LLM(LinearLayoutManager)封裝好的布局和緩存機制。

  • 首先索引計算這個談不上是個問題。至于用戶滑到邊界的情況,也可以做特殊處理調(diào)整位置。(另外真的有人會滑約Integer.MAX_VALUE/2大約1073741823個position嗎?
  • 性能上也無需擔心。從數(shù)字的直覺上,設置這么多item然后初始化scrollToPosition(Integer.MAX_VALUE/2)看上去好像很可怕,性能上可能有問題,會卡頓巴拉巴拉。

實際從初始化到scrollPosition到真正onlayoutChildren系列操作,主要經(jīng)過了以下幾步。

先上一張流程圖:

  • 設置mPendingScrollPosition,確定要滑動的位置,然后requestLayout()請求布局;
/**
 * <p>Scroll the RecyclerView to make the position visible.</p>
 *
 * <p>RecyclerView will scroll the minimum amount that is necessary to make the
 * target position visible. If you are looking for a similar behavior to
 * {@link android.widget.ListView#setSelection(int)} or
 * {@link android.widget.ListView#setSelectionFromTop(int, int)}, use
 * {@link #scrollToPositionWithOffset(int, int)}.</p>
 *
 * <p>Note that scroll position change will not be reflected until the next layout call.</p>
 *
 * @param position Scroll to this adapter position
 * @see #scrollToPositionWithOffset(int, int)
 */
@Override
public void scrollToPosition(int position) {
    mPendingScrollPosition = position;//更新position
    mPendingScrollPositionOffset = INVALID_OFFSET; 
    if (mPendingSavedState != null) {
        mPendingSavedState.invalidateAnchor();
    }
    requestLayout();
}
  • 請求布局后會觸發(fā)recyclerView的dispatchLayout,最終會調(diào)用onLayoutChildren進行子View的layout,如官方注釋里描述的那樣,onLayoutChildren最主要的工作是:確定錨點、layoutState,調(diào)用fill填充布局。

onLayoutChildren部分源碼:

@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    // layout algorithm:
    // 1) by checking children and other variables, find an anchor coordinate and an anchor
    //  item position.
    // 2) fill towards start, stacking from bottom
    // 3) fill towards end, stacking from top
    // 4) scroll to fulfill requirements like stack from bottom.
    //..............
    // 省略,前面主要做了一些異常狀態(tài)的檢測、針對焦點的特殊處理、確定錨點對anchorInfo賦值、偏移量計算
    int startOffset;
    int endOffset;
    final int firstLayoutDirection;
    if (mAnchorInfo.mLayoutFromEnd) {
        // fill towards start
        updateLayoutStateToFillStart(mAnchorInfo); //根據(jù)mAnchorInfo更新layoutState
        mLayoutState.mExtraFillSpace = extraForStart;
        fill(recycler, mLayoutState, state, false);//填充
        startOffset = mLayoutState.mOffset;
        final int firstElement = mLayoutState.mCurrentPosition;
        if (mLayoutState.mAvailable > 0) {
            extraForEnd += mLayoutState.mAvailable;
        }
        // fill towards end
        updateLayoutStateToFillEnd(mAnchorInfo);//更新layoutState為fill做準備
        mLayoutState.mExtraFillSpace = extraForEnd;
        mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
        fill(recycler, mLayoutState, state, false);//填充
        endOffset = mLayoutState.mOffset;
        if (mLayoutState.mAvailable > 0) {
            // end could not consume all. add more items towards start
            extraForStart = mLayoutState.mAvailable;
            updateLayoutStateToFillStart(firstElement, startOffset);//更新layoutState為fill做準備
            mLayoutState.mExtraFillSpace = extraForStart;
            fill(recycler, mLayoutState, state, false);
            startOffset = mLayoutState.mOffset;
        }
    } else {
        //layoutFromStart 同理,省略
    }
    //try to fix gap , 省略
  • onLayoutChildren中會調(diào)用updateAnchorInfoForLayout更新anchoInfo錨點信息,updateLayoutStateToFillStart/End再根據(jù)anchorInfo更新layoutState為fill填充做準備。
  • fill的源碼: `
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {
    // max offset we should set is mFastScroll + available
    final int start = layoutState.mAvailable;
    if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
        // TODO ugly bug fix. should not happen
        if (layoutState.mAvailable &lt; 0) {
            layoutState.mScrollingOffset += layoutState.mAvailable;
        }
        recycleByLayoutState(recycler, layoutState);
    }
    int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    // (不限制layout個數(shù)/還有剩余空間) 并且 有剩余數(shù)據(jù)
    while ((layoutState.mInfinite || remainingSpace &gt; 0) &amp;&amp; layoutState.hasMore(state)) {
        layoutChunkResult.resetInternal();
        if (RecyclerView.VERBOSE_TRACING) {
            TraceCompat.beginSection("LLM LayoutChunk");
        }
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
        if (RecyclerView.VERBOSE_TRACING) {
            TraceCompat.endSection();
        }
        if (layoutChunkResult.mFinished) {
            break;
        }
        layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
        /**
         * Consume the available space if:
         * * layoutChunk did not request to be ignored
         * * OR we are laying out scrap children
         * * OR we are not doing pre-layout
         */
        if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
                || !state.isPreLayout()) {
            layoutState.mAvailable -= layoutChunkResult.mConsumed;
            // we keep a separate remaining space because mAvailable is important for recycling
            remainingSpace -= layoutChunkResult.mConsumed;
        }
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
            if (layoutState.mAvailable &lt; 0) {
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            recycleByLayoutState(recycler, layoutState);//回收子view
        }
        if (stopOnFocusable &amp;&amp; layoutChunkResult.mFocusable) {
            break;
        }
    }
    if (DEBUG) {
        validateChildOrder();
    }
    return start - layoutState.mAvailable;

fill主要干了兩件事:

  • 循環(huán)調(diào)用layoutChunk布局子view并計算可用空間
  • 回收那些不在屏幕上的view

所以可以清晰地看到LLM是按需layout、回收子view。

就算創(chuàng)建一個無限大的數(shù)據(jù)集,再進行滑動,它也是如此??梢詫懸粋€修改adapter和數(shù)據(jù)映射來實現(xiàn)無限循環(huán)的例子,驗證一下我們的猜測:

//adapter關(guān)鍵代碼
@NonNull
@Override
public DemoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    Log.d("DemoAdapter","onCreateViewHolder");
    return new DemoViewHolder(inflater.inflate(R.layout.item_demo, parent, false));
}
@Override
public void onBindViewHolder(@NonNull DemoViewHolder holder, int position) {
    Log.d("DemoAdapter","onBindViewHolder: position"+position);
    String text = mData.get(position % mData.size());
    holder.bind(text);
}
@Override
public int getItemCount() {
    return Integer.MAX_VALUE;
}

在代碼我們里打印了onCreateViewHolder、onBindViewHolder的情況。我們只要觀察這viewHolder的情況,就知道進入界面再滑到Integer.MAX_VALUE/2時會初始化多少item。 `

RecyclerView recyclerView = findViewById(R.id.rv);
recyclerView.setAdapter(new DemoAdapter());
LinearLayoutManager layoutManager =  new LinearLayoutManager(this);
layoutManager.setOrientation(RecyclerView.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
recyclerView.scrollToPosition(Integer.MAX_VALUE/2);

初始化后ui效果:

日志打?。?/p>

可以看到,頁面上共有5個item可見,LLM也按需創(chuàng)建、layout了5個item。

2、自定義layoutManager

找了找網(wǎng)上自定義layoutManager去實現(xiàn)列表循環(huán)的博客和代碼,拷貝和復制的很多,找不到源頭是哪一篇,這里就不貼鏈接了。大家都是先說第一種修改adapter的方式不好,然后甩了一份自定義layoutManager的代碼。

然而自定義layoutManager難點和坑都很多,很容易不小心就踩到,一些博客的代碼也有類似問題。 基本的一些坑點在張旭童大佬的博客中有提及, 【Android】掌握自定義LayoutManager

比較常見的問題是:

  • 不計算可用空間和子view消費的空間,layout出所有的子view。相當于拋棄了子view的復用機制
  • 沒有合理利用recyclerView的回收機制
  • 沒有支持一些常用但比較重要的api的實現(xiàn),如前面提到的scrollToPosition。

其實最理想的辦法是繼承LinearLayoutManager然后修改,但由于LinearLayoutManager內(nèi)部封裝的原因,不方便像GridLayoutManager那樣去繼承LinearLayoutManager然后進行擴展(主要是包外的子類會拿不到layoutState等)。

要實現(xiàn)一個線性布局的layoutManager,最重要的就是實現(xiàn)一個類似LLM的fill(前面有提到過源碼,可以翻回去看看)和layoutChunk方法。

(當然,可以照著LLM寫一個丐版,本文就是這么做的。)

fill方法很重要,就如同官方注釋里所說的,它是一個magic func。

從OnLayoutChildren到觸發(fā)scroll滑動,都是調(diào)用fill來實現(xiàn)布局。

/**
 * The magic functions :). Fills the given layout, defined by the layoutState. This is fairly
 * independent from the rest of the {@link LinearLayoutManager}
 * and with little change, can be made publicly available as a helper class.
 */
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
        RecyclerView.State state, boolean stopOnFocusable) {

前面提到過fill主要干了兩件事:

  • 循環(huán)調(diào)用layoutChunk布局子view并計算可用空間
  • 回收那些不在屏幕上的view

而負責子view布局的layoutChunk則和把一個大象放進冰箱一樣,主要分三步走:

  • add子view
  • measure
  • layout 并計算消費了多少空間

就像下面這樣:

/**
 * layout具體子view
 */
private void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                         LayoutState layoutState, LayoutChunkResult result) {
    View view = layoutState.next(recycler, state);
    if (view == null) {
        result.mFinished = true;
        return;
    }
    RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
    // add
    if (layoutState.mLayoutDirection != LayoutState.LAYOUT_START) {
        addView(view);
    } else {
        addView(view, 0);
    }
    Rect insets = new Rect();
    calculateItemDecorationsForChild(view, insets);
    // 測量
    measureChildWithMargins(view, 0, 0);
    //布局
    layoutChild(view, result, params, layoutState, state);
    // Consume the available space if the view is not removed OR changed
    if (params.isItemRemoved() || params.isItemChanged()) {
        result.mIgnoreConsumed = true;
    }
    result.mFocusable = view.hasFocusable();
}

那最關(guān)鍵的如何實現(xiàn)循環(huán)呢??

其實和修改adapter的實現(xiàn)方法有異曲同工之妙,本質(zhì)都是修改位置與數(shù)據(jù)的映射關(guān)系。

修改layoutStae的方法:

    boolean hasMore(RecyclerView.State state) {
        return Math.abs(mCurrentPosition) &lt;= state.getItemCount();
    }
    View next(RecyclerView.Recycler recycler, RecyclerView.State state) {
        int itemCount = state.getItemCount();
        mCurrentPosition = mCurrentPosition % itemCount;
        if (mCurrentPosition &lt; 0) {
            mCurrentPosition += itemCount;
        }
        final View view = recycler.getViewForPosition(mCurrentPosition);
        mCurrentPosition += mItemDirection;
        return view;
    }
}

最終效果:

源碼地址:aFlyFatPig/cycleLayoutManager (github.com)

注:也可以直接引用依賴使用,詳見readme.md。

后記

本文介紹了recyclerview無限循環(huán)效果的兩種不同實現(xiàn)方法與解析。

雖然自定義layoutManager坑點很多并且很少用的到,但了解下也會對recyclerView有更深的理解。

以上就是RecyclerView無限循環(huán)效果實現(xiàn)及示例解析的詳細內(nèi)容,更多關(guān)于RecyclerView無限循環(huán)的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Android設計模式系列之單例模式

    Android設計模式系列之單例模式

    單例模式,可以說是GOF的23種設計模式中最簡單的一個。接下來通過本文給大家實例講解android設計模式系列之單例模式的相關(guān)知識,感興趣的朋友一起看看吧
    2016-09-09
  • Android 中IntentFilter的匹配規(guī)則實例詳解

    Android 中IntentFilter的匹配規(guī)則實例詳解

    這篇文章主要介紹了Android 中IntentFilter的匹配規(guī)則實例詳解的相關(guān)資料,希望通過本文大家能了解掌握IntentFilter的匹配規(guī)則問題,需要的朋友可以參考下
    2017-09-09
  • Android開發(fā)Activity毛玻璃背景效果

    Android開發(fā)Activity毛玻璃背景效果

    這篇文章主要為大家詳細介紹了Android開發(fā)Activity毛玻璃背景效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • Android Studio升級到3.0 Terminal 中文顯示異常解決

    Android Studio升級到3.0 Terminal 中文顯示異常解決

    本篇文章主要介紹了Android Studio升級到3.0 Terminal 中文顯示異常解決,非常具有實用價值,需要的朋友可以參考下
    2017-10-10
  • Android10 啟動Zygote源碼解析

    Android10 啟動Zygote源碼解析

    這篇文章主要為大家介紹了Android 10 啟動分析之Zygote篇,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-10-10
  • 模仿美團點評的Android應用中價格和購買欄懸浮固定的效果

    模仿美團點評的Android應用中價格和購買欄懸浮固定的效果

    這篇文章主要介紹了模仿美團點評的Android應用中價格和購買欄懸浮固定的效果,文章后半還針對快速滑動操作給出了一個響應優(yōu)化的方法,需要的朋友可以參考下
    2016-04-04
  • Android應用中圖片瀏覽時實現(xiàn)自動切換功能的方法詳解

    Android應用中圖片瀏覽時實現(xiàn)自動切換功能的方法詳解

    這篇文章主要介紹了Android應用中圖片瀏覽時實現(xiàn)自動切換功能的方法,文中還講解了一個觸摸大圖進行圖片切換的深入功能,需要的朋友可以參考下
    2016-04-04
  • Android給圖片加文字和圖片水印實例代碼

    Android給圖片加文字和圖片水印實例代碼

    本篇文章主要介紹了Android給圖片加文字和圖片水印實例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-04-04
  • Android Google AutoService框架使用詳解

    Android Google AutoService框架使用詳解

    AutoService是Google開發(fā)一個自動生成SPI清單文件的框架??催^一些基于APT的三方框架源碼的讀者應該有所了解。比如Arouter、EventBus等等
    2022-11-11
  • Kotlin新手基礎學習之Elvis操作符

    Kotlin新手基礎學習之Elvis操作符

    Kotlin 是一種在 Java 虛擬機上運行的靜態(tài)類型編程語言,被稱之為 Android 世界的Swift,由 JetBrains 設計開發(fā)并開源,下面這篇文章主要給大家介紹了關(guān)于Kotlin新手基礎學習之Elvis操作符的相關(guān)資料,需要的朋友可以參考下。
    2017-12-12

最新評論

东阳市| 习水县| 南漳县| 涞水县| 肇州县| 鄄城县| 防城港市| 九龙城区| 河东区| 疏勒县| 长白| 沐川县| 安义县| 苏州市| 西乌| 靖州| 新河县| 文成县| 泽普县| 乐陵市| 玛多县| 如皋市| 涡阳县| 黄平县| 达尔| 伊金霍洛旗| 永城市| 丰城市| 金昌市| 灵丘县| 沧源| 通海县| 上思县| 华宁县| 樟树市| 新巴尔虎右旗| 托里县| 安康市| 郧西县| 行唐县| 梁河县|