Android入門教程 | RecyclerView實際使用

Android_anzi發表於2021-11-08

RecyclerView實際使用

用 RecyclerView 做出一個列表。顯示測試資料。

這個示例會分為以下幾個步驟:

  • 確定資料。這裡用的是模擬資料。
  • 設計UI和表現形式。
  • 編寫 layout 與介面卡。

模擬資料

巧婦難為無米之炊,模擬資料就是模擬需求。

新建一個類 DataTest,設計 4 個屬性。

public class DataTest {
    private String timezone;
    private int number;
    private int personCount;
    private int count;
    public DataTest(String timezone, int number, int personCount, int count) {
        this.timezone = timezone;
        this.number = number;
        this.personCount = personCount;
        this.count = count;
    }
// getter setter...
}

設計 item 的佈局

設計UI,在一行裡顯示 4 個屬性值。item 用的是 item_recy2.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <TextView
        android:id="@+id/tv1"
        style="@style/RePage2Header"
        android:layout_marginEnd="@dimen/re_2_half_gap" />
    <TextView
        android:id="@+id/tv2"
        style="@style/RePage2Header"
        android:layout_marginStart="@dimen/re_2_half_gap"
        android:layout_marginEnd="@dimen/re_2_half_gap" />
    <TextView
        android:id="@+id/tv3"
        style="@style/RePage2Header"
        android:layout_marginStart="@dimen/re_2_half_gap"
        android:layout_marginEnd="@dimen/re_2_half_gap" />
    <TextView
        android:id="@+id/tv4"
        style="@style/RePage2Header"
        android:layout_marginStart="@dimen/re_2_half_gap" />
</LinearLayout>

準備顏色,尺寸等資源

相關的style和顏色,尺寸配置檔案,在 res/values目錄下。

style檔案 style.xml

<style name="RePage2Header">
    <item name="android:layout_width">0dp</item>
    <item name="android:layout_height">40dp</item>
    <item name="android:layout_weight">2</item>
    <item name="android:textColor">#ffffff</item>
    <item name="android:gravity">center</item>
    <item name="android:background">@color/rePage2Item</item>
</style>

我們給 layout 裡的每個 TextView都設定了  layout_width 為 0dp。是為了使用  layout_weight 屬性。 讓它們 4 個  TextView 按比例分割父 View 的寬度。

這裡的分割用到了LinearLayout的分割佔位元性。

顏色配置檔案 color.xml,新增如下顏色設定。

<color name="rePage2Item">#082941</color>

尺寸配置dimens.xml。

<dimen name="re_2_gap">4dp</dimen>
<dimen name="re_2_half_gap">2dp</dimen>
設計 ViewHolder

資原始檔和 layout 準備妥當,開始寫對應的 viewHolder。這裡也是把  VH 類和  Adapter 類放在 activity 類裡面。

private class VH extends RecyclerView.ViewHolder {
    TextView tv1;
    TextView tv2;
    TextView tv3;
    TextView tv4;
    public VH(@NonNull View itemView) {
        super(itemView);
        tv1 = itemView.findViewById(R.id.tv1);
        tv2 = itemView.findViewById(R.id.tv2);
        tv3 = itemView.findViewById(R.id.tv3);
        tv4 = itemView.findViewById(R.id.tv4);
    }
}
設計 Adapter 類

介面卡 Adapter 類。

private class Adapter extends RecyclerView.Adapter<VH> {
    private List<DataTest> dataList = new ArrayList<>();
    public Adapter() {
    }
    public void setDataList(List<DataTest> dataList) {
        this.dataList = dataList; // 最好進行判空操作
        notifyDataSetChanged();
    }
    @NonNull
    @Override
    public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new VH(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recy2, parent, false));
    }
    @Override
    public void onBindViewHolder(@NonNull VH holder, int position) {
        DataTest dataTest = dataList.get(position);
        holder.tv1.setText(dataTest.getTimezone());
        holder.tv2.setText(String.valueOf(dataTest.getNumber()));
        holder.tv3.setText(String.valueOf(dataTest.getPersonCount()));
        holder.tv4.setText(String.valueOf(dataTest.getCount()));
    }
    @Override
    public int getItemCount() {
        return dataList.size();
    }
}
配置 RecyclerView

設定 recyclerview。

// 在activity或者fragment裡
private Adapter mAdapter = new Adapter();
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.act_recy_2);
    RecyclerView recyclerView = findViewById(R.id.re_view);
    recyclerView.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false));
    recyclerView.setAdapter(mAdapter);
    mAdapter.setDataList(genDataTestList());
    recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
        @Override
        public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            outRect.top = getResources().getDimensionPixelOffset(R.dimen.re_2_gap);
        }
    });
}
// 生成模擬資料
private List<DataTest> genDataTestList() {
    List<DataTest> list = new ArrayList<>();
    for (int i = 1; i <= 60; i++) {
        DataTest d = new DataTest("時區" + i, i, i, i);
        list.add(d);
    }
    return list;
}

在 onCreate 方法中配置 recyclerView。 recyclerView.addItemDecoration 方法是給 item 設定間隔樣式。 getItemOffsets 可以設定子項的間距。這裡給子項底部一個間距值。具體數值設定在 dimen 中。 genDataTestList() 是生成模擬的資料。

使用 include 修改 layout

有的朋友問:表頭和item的結構是一樣的,可以複用嗎?

其實是可以的。我們可以在layout中使用 include標籤,把另一個layout檔案“包含”進來。 複製 act_recy_2.xml貼上得到 act_recy_2_include.xml,把原來的表頭的 LinearLayout 改成 include。

<include layout="@layout/item_recy2" />

給 include 設定 layout,即我們定義的 item 的佈局 item_recy2。 當我們想新增個 margin-top的時候,比如這樣

<include
    android:layout_marginTop="4dp"
    layout="@layout/item_recy2" />

as會彈出警告:

Layout parameter layout_marginTop ignored unless both layout_width and layout_height are also specified on tag

也就是在 include 標籤中,如果要設定其他屬性,需要先設定 layout_width 和 layout_height。

修改一下,再加個 id,變成這樣。

<include
    android:id="@+id/header"
    layout="@layout/item_recy2"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="4dp" />

讓Activity使用這個layout。修改一下RecyclerViewDemo2Act。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.act_recy_2_include);
    initHeader();
    // 配置RecyclerView的部分
}
private void initHeader() {
    View header = findViewById(R.id.header);
    TextView tv1 = header.findViewById(R.id.tv1);
    tv1.setText("時區");
    TextView tv2 = header.findViewById(R.id.tv2);
    tv2.setText("序號");
    TextView tv3 = header.findViewById(R.id.tv3);
    tv3.setText("人員");
    TextView tv4 = header.findViewById(R.id.tv4);
    tv4.setText("數量");
}

我們新增了一個方法 initHeader()。裡面先把 header 找到,透過 header 找到它的子 view,也就是那4個TextView。 分別設定文字即可。

RecyclerView 相關面試題

1. RecyclerView第一次layout時,會發生預佈局pre-layout嗎?

第一次佈局時,並不會觸發pre-layout。pre-layout只會在每次notify change時才會被觸發,目的是透過saveOldPosition方法將螢幕中各位置上的ViewHolder的座標記錄下來,並在重新佈局之後,透過對比實現Item的動畫效果。

2. ViewHolder何時被快取到RecycledViewPool中?

主要有以下2種情況:

  • 當ItemView被滑動出螢幕時,並且CachedView已滿,則ViewHolder會被快取到RecycledViewPool中
  • 當資料發生變動時,執行完disappearrance的ViewHolder會被快取到RecycledViewPool中

3. CachedView 和 RecycledViewPool 的關係

當一個ItemView被滑動滾出螢幕之後,預設會先被儲存在CachedView中。CachedView的預設大小為2,可以透過 setItemViewCacheSize 方法修改它的值。

當CachedView已滿後,後續有新的ItemView從螢幕內滑出時,會迫使CachedView根據FIFO規則,將之前的快取的ViewHolder轉移到RecycledViewPool中.

RecycledViewPool預設大小為5,可以透過以下方式修改RecycledViewPool的快取大小:

RecyclerView.getRecycledViewPool().setMaxRecycledViews(int viewType, int max);

4. CachedView 和 RecycledViewPool 兩者區別

快取到CachedView中的ViewHolder並不會清理相關資訊(比如position、state等),因此剛移出螢幕的ViewHolder,再次被移回螢幕時,只要從CachedView中查詢並顯示即可,不需要重新繫結(bindViewHolder)。

而快取到RecycledViewPool中的ViewHolder會被清理狀態和位置資訊,因此從RecycledViewPool查詢到ViewHolder,需要重新呼叫bindViewHolder繫結資料。

5. 你是從哪些方面最佳化RecyclerView的?

主要可以從以下幾個方面對 RecyclerView 進行最佳化:

  • 儘量將複雜的資料處理操作放到非同步中完成。RecyclerView 需要展示的資料經常是從遠端伺服器上請求獲取,但是在網路請求拿到資料之後,需要將資料做扁平化操作,儘量將最優質的資料格式返回給UI執行緒。
  • 最佳化 RecyclerView 的佈局,避免將其與 ConstraintLayout 使用
  • 針對快速滑動事件,可以使用 addOnScrollListener 新增對快速滑動的監聽,當使用者快速滑動時,停止載入資料操作。
  • 如果 ItemView 的高度固定,可以使用 setHasFixSize(true)。這樣 RecyclerView 在 onMeasure 階段可以直接計算出高度,不需要多次計運算元 ItemView 的高度,這種情況對於垂直 RecyclerView 中巢狀橫向 RecyclerView 效果非常顯著。
  • 當 UI 是 Tab feed 流時,可以考慮使用 RecycledViewPool 來實現多個 RecyclerView 的快取共享。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70008155/viewspace-2841198/,如需轉載,請註明出處,否則將追究法律責任。

相關文章