亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么在Android中實現無限循環的RecyclerView

發布時間:2021-05-31 17:23:23 來源:億速云 閱讀:310 作者:Leah 欄目:移動開發

本篇文章為大家展示了怎么在Android中實現無限循環的RecyclerView,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

方案1 對Adapter進行修改

網上大部分博客的解決方案都是這種方案,對Adapter做修改。具體如下

首先,讓 Adapter 的 getItemCount() 方法返回 Integer.MAX_VALUE,使得position數據達到很大很大;

其次,在 onBindViewHolder() 方法里對position參數取余運算,拿到position對應的真實數據索引,然后對itemView綁定數據

最后,在初始化RecyclerView的時候,讓其滑動到指定位置,如 Integer.MAX_VALUE/2,這樣就不會滑動到邊界了,如果用戶一根筋,真的滑動到了邊界位置,再加一個判斷,如果當前索引是0,就重新動態調整到初始位置

這個方案是挺簡單,但并不完美。一是對我們的數據和索引做了計算操作,二是如果滑動到邊界,再動態調整到中間,會有一個不明顯的卡頓操作,使得滑動不是很順暢。所以,直接看方案二。

方案2 自定義LayoutManager,修改RecyclerView的布局方式

這個算得上是一勞永逸的解決方案了,也是我今天要詳細介紹的方案。我們都知道,RecyclerView的數據綁定是通過Adapter來處理的,而排版方式以及View的回收控制等,則是通過LayoutManager來實現的,因此我們直接修改itemView的排版方式就可以實現我們的目標,讓RecyclerView無限循環。

自定義LayoutManager

1.創建自定義LayoutManager

首先,自定義 LooperLayoutManager 繼承自 RecyclerView.LayoutManager,然后需要實現抽象方法 generateDefaultLayoutParams(),這個方法的作用是給 itemView 設置默認的LayoutParams,直接返回如下就行。

public class LooperLayoutManager extends RecyclerView.LayoutManager {
    @Override
  public RecyclerView.LayoutParams generateDefaultLayoutParams() {
    return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
        ViewGroup.LayoutParams.WRAP_CONTENT);
  }
}

2.打開滾動開關

接著,對滾動方向做處理,重寫canScrollHorizontally()方法,打開橫向滾動開關。注意我們是實現橫向無限循環滾動,所以實現此方法,如果要對垂直滾動做處理,則要實現canScrollVertically()方法。

  @Override
  public boolean canScrollHorizontally() {
    return true;
  }

3.對RecyclerView進行初始化布局

好了,以上兩部是基礎工作,接下來,重寫 onLayoutChildren() 方法,開始對itemView初始化布局。

  @Override
  public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    if (getItemCount() <= 0) {
      return;
    }
    //標注1.如果當前時準備狀態,直接返回
    if (state.isPreLayout()) {
      return;
    }
    //標注2.將視圖分離放入scrap緩存中,以準備重新對view進行排版
    detachAndScrapAttachedViews(recycler);

    int autualWidth = 0;
    for (int i = 0; i < getItemCount(); i++) {
      //標注3.初始化,將在屏幕內的view填充
      View itemView = recycler.getViewForPosition(i);
      addView(itemView);
      //標注4.測量itemView的寬高
      measureChildWithMargins(itemView, 0, 0);
      int width = getDecoratedMeasuredWidth(itemView);
      int height = getDecoratedMeasuredHeight(itemView);
      //標注5.根據itemView的寬高進行布局
      layoutDecorated(itemView, autualWidth, 0, autualWidth + width, height);

      autualWidth += width;
      //標注6.如果當前布局過的itemView的寬度總和大于RecyclerView的寬,則不再進行布局
      if (autualWidth > getWidth()) {
        break;
      }
    }
  }

onLayoutChildren() 方法顧名思義,就是對所有的 itemView 進行布局,一般會在初始化和調用 Adapter 的 notifyDataSetChanged() 方法時調用。代碼思路已經注釋的很清楚了,其中有幾個方法需要簡單提下:

標注2處 detachAndScrapAttachedViews(recycler) 方法會將所有的 itemView 從View樹中全部detach,然后放入scrap緩存中。了解過RecyclerView的同學應該知道,RecyclerView是有一個二級緩存的,一級緩存是 scrap 緩存,二級緩存是 recycler 緩存,其中從View樹上detach的View會放入scrap緩存里,調用removeView()刪除的View會放入recycler緩存中。

標注3處 recycler.getViewForPosition(i) 方法會從緩存中拿到對應索引的 itemView,這個方法內部會先從 scrap 緩存中取 itemView,如果沒有則從 recycler 緩存中取,如果還沒有則調用 adapter 的 onCreateViewHolder() 去創建 itemView。

標注5處 layoutDecorated() 方法會對 itemView 進行布局排版,這里可以看出來,我們是根據寬依次往父容器的右邊排下去,直到下一個 itemView的頂點位置超過了RecyclerView 的寬度。

4.對RecyclerView進行滾動和回收itemView處理

對RecyclerView的子item進行排版布局后,運行一下效果就會出現了,不過這時候我們滑動列表會發現滑動后變成空白了,所以就該對滑動操作進行處理了。

前面說過,我們打開了橫向滾動的開關,所以對應的,我們要重寫 scrollHorizontallyBy()方法進行橫向滑動操作。

@Override
  public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
    //標注1.橫向滑動的時候,對左右兩邊按順序填充itemView
    int travl = fill(dx, recycler, state);
    if (travl == 0) {
      return 0;
    }

    //2.滑動
    offsetChildrenHorizontal(-travl);

    //3.回收已經不可見的itemView
    recyclerHideView(dx, recycler, state);
    return travl;
  }

可以看到,滑動邏輯很簡單,總結為三步:

  • 橫向滑動的時候,對左右兩邊按順序填充itemView

  • 滑動itemView

  • 回收已經不可見的itemView

下面一步一步介紹:

首先第一步,滑動的時候調用自定義的 fill() 方法,對左右兩邊進行填充。還沒忘了,我們是來實現循環滑動的,所以這一步尤其重要,先看代碼:

  /**
   * 左右滑動的時候,填充
   */
  private int fill(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
    if (dx > 0) {
      //標注1.向左滾動
      View lastView = getChildAt(getChildCount() - 1);
      if (lastView == null) {
        return 0;
      }
      int lastPos = getPosition(lastView);
      //標注2.可見的最后一個itemView完全滑進來了,需要補充新的
      if (lastView.getRight() < getWidth()) {
        View scrap = null;
        //標注3.判斷可見的最后一個itemView的索引,
        // 如果是最后一個,則將下一個itemView設置為第一個,否則設置為當前索引的下一個
        if (lastPos == getItemCount() - 1) {
          if (looperEnable) {
            scrap = recycler.getViewForPosition(0);
          } else {
            dx = 0;
          }
        } else {
          scrap = recycler.getViewForPosition(lastPos + 1);
        }
        if (scrap == null) {
          return dx;
        }
        //標注4.將新的itemViewadd進來并對其測量和布局
        addView(scrap);
        measureChildWithMargins(scrap, 0, 0);
        int width = getDecoratedMeasuredWidth(scrap);
        int height = getDecoratedMeasuredHeight(scrap);
        layoutDecorated(scrap,lastView.getRight(), 0,
            lastView.getRight() + width, height);
        return dx;
      }
    } else {
      //向右滾動
      View firstView = getChildAt(0);
      if (firstView == null) {
        return 0;
      }
      int firstPos = getPosition(firstView);

      if (firstView.getLeft() >= 0) {
        View scrap = null;
        if (firstPos == 0) {
          if (looperEnable) {
            scrap = recycler.getViewForPosition(getItemCount() - 1);
          } else {
            dx = 0;
          }
        } else {
          scrap = recycler.getViewForPosition(firstPos - 1);
        }
        if (scrap == null) {
          return 0;
        }
        addView(scrap, 0);
        measureChildWithMargins(scrap,0,0);
        int width = getDecoratedMeasuredWidth(scrap);
        int height = getDecoratedMeasuredHeight(scrap);
        layoutDecorated(scrap, firstView.getLeft() - width, 0,
            firstView.getLeft(), height);
      }
    }
    return dx;
  }

代碼是有點長,不過邏輯很清晰。首先分為兩部分,往左填充或是往右填充,dx為將要滑動的距離,如果 dx > 0,則是往左邊滑動,則需要判斷右邊的邊界,如果最后一個itemView完全顯示出來后,在右邊填充一個新的itemView。
看標注3,往右邊填充的時候需要檢測當前最后一個可見itemView的索引,如果索引是最后一個,則需要新填充的itemView為第0個,這樣就可以實現往左邊滑動時候無限循環了。然后將需要新填充的itemView進行測量布局操作,將填充進去了。

同理,往右滑動的邏輯跟往左滑動相似,就不一一再闡述了。

第二步:填充完新的itemView后,就開始進行滑動了,這里直接調用 LayoutManager 的 offsetChildrenHorizontal() 方法滑動-travl 距離,travl 是通過fill方法計算出來的,通常情況下都為 dx,只有當滑動到最后一個itemView,并且循環滾動開關沒有打開的時候才為0,也就是不滾動了。

//2.滾動
    offsetChildrenHorizontal(travl * -1);

第三步:回收已經不可見的itemView。只有對不可見的itemView進行回收,才能做到回收利用,防止內存爆增。

  /**
   * 回收界面不可見的view
   */
  private void recyclerHideView(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
    for (int i = 0; i < getChildCount(); i++) {
      View view = getChildAt(i);
      if (view == null) {
        continue;
      }
      if (dx > 0) {
        //標注1.向左滾動,移除左邊不在內容里的view
        if (view.getRight() < 0) {
          removeAndRecycleView(view, recycler);
          Log.d(TAG, "循環: 移除 一個view childCount=" + getChildCount());
        }
      } else {
        //標注2.向右滾動,移除右邊不在內容里的view
        if (view.getLeft() > getWidth()) {
          removeAndRecycleView(view, recycler);
          Log.d(TAG, "循環: 移除 一個view childCount=" + getChildCount());
        }
      }
    }

  }

代碼也很簡單,遍歷所有添加進 RecyclerView 里的item,然后根據 itemView 的頂點位置進行判斷,移除不可見的item。移除 itemView 調用 removeAndRecycleView(view, recycler) 方法,會對移除的item進行回收,然后存入 RecyclerView 的緩存里。

至此,一個可以實現左右無限循環的LayoutManager就實現了,調用方式跟通常我們用RrcyclerView沒有任何區別,只需要給 RecyclerView 設置 LayoutManager 時指定我們的LayoutManager,如下:

recyclerView.setAdapter(new MyAdapter());
    LooperLayoutManager layoutManager = new LooperLayoutManager();
    layoutManager.setLooperEnable(true);
    recyclerView.setLayoutManager(layoutManager);

上述內容就是怎么在Android中實現無限循環的RecyclerView,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

许昌市| 喀喇沁旗| 大连市| 乐昌市| 白银市| 灵璧县| 兴隆县| 乌鲁木齐县| 若尔盖县| 垫江县| 蚌埠市| 改则县| 庆安县| 荥经县| 和龙市| 延边| 黑河市| 大冶市| 隆安县| 靖边县| 宁河县| 太仆寺旗| 乌兰浩特市| 贵定县| 阆中市| 高台县| 宁南县| 手游| 苗栗县| 类乌齐县| 都匀市| 湘潭市| 宁都县| 延庆县| 临沂市| 太仆寺旗| 新和县| 繁昌县| 金昌市| 宁国市| 府谷县|