天天看點

RecyclerView 基本用法及另類加分割線技巧

1 RecyclerView 介紹

1.1 RecyclerView 的優點

我們先來看看谷歌官方的介紹:

A flexible view for providing a limited window into a large data set.

翻譯成中文大緻就是:

用來在有限的視窗上展示大量資料的靈活視圖

關鍵字在于靈活,以前展示資料主要用的控件是 ListView,通過使用 RecyclerView,我們不僅可以很友善的實作類似 ListView 的效果,同樣稍微改下代碼便可以實作 GridView 和瀑布流的效果。還可以通過定制,實作條目增加或删除時的動畫。總體來看,通過設定不同的 LayoutManager,ItemDecoretion,ItemAnimator 可以實作各種炫酷的效果。

另外,以前使用 ListView 時,為了性能複用 view 時需要自己定義 ViewHolder,而在 RecyclerView 中谷歌已經封裝好了 ViewHolder,使用起來非常的友善。

1.2 RecyclerView 的不足

盡管 RecyclerView 有着如此衆多的優點,但它也有一些使用起來較為麻煩的地方,其實我用了“不足”這個詞而不是“缺點”,因為 RecyclerView 具有高度的解耦性,在帶來更多功能的同時,勢必代碼量會加大一點。剛接觸 RecyclerView 時主要有兩個地方與 ListView 有所不同:

  1. Item 的點選事件需要自己實作
  2. Item 之間的分割線需要自己實作

一個一個來說,其實點選事件其實從另一方面了解倒不如說是好處,比如 Item 中如果有子控件需要響應點選事件,這點 ListView 中要實作還是要花不少功夫的,這裡不讨論其實作。可能由于上述場景越來越多,谷歌就幹脆把決策權讓給開發者,自己去實作點選事件吧。

另外一個比較麻煩的地方在于 Item 之間預設是沒有分割線的,對于網格布局形式或者瀑布流形式我們可以通過 margin 屬性實作 item 的分離,效果也不錯,但對于類似于 ListView 這樣的線性布局,還是有分割線會美觀一些。一般來說,實作分割線通常做法主要是自定義一個 ItemDecoration,本文後面會提出一個比較另類的寫法,也可以實作分割線的效果,而且寫法簡單,具體請參考後文。

實際上,如果隻是單純的有資料需要展示出來,不需要太好的視覺效果,用 ListView 也是一個不錯的選擇,因為寫法簡單。而通過結合 CardView 和 RecyclerView,我們可以做出更好的 Item 間隔效果,比起單純的一條線,使用 CardView 後的效果更加符合 MaterialDesign 的設計規範,具體如何使用請看我後續的部落格。本文主要是針對 RecyclerView 的基本用法并實作 ListView 效果展開。

2 RecyclerView的基本用法

RecyclerView 的基本用法與 ListView 相差無幾,大緻就是如下幾個步驟:

  • 與 ListView 不同的是,因為來自 v7 包,需引入 gradle 依賴
  • 布局檔案添加 RecyclerView 控件
  • 自定義 Adaper 及 ViewHolder
  • Activity 中為 RecyclerView 設定 Adapter

好的,說了半天,現在是代碼時間。

1,首先,我們要在 build.gradle 檔案引入 RecyclerView 的依賴庫:

2,接下來,去布局檔案中添加 RecyclerView 控件,xml 代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </android.support.v7.widget.RecyclerView>

</LinearLayout>
           

3,自定義 MyAdapter 繼承自 RecyclerView.Adapter<>,泛型是要實作的 ViewHolder,通常定義成 MyAdapter 内部類 MyViewHolder,這個類要繼承自 RecyclerView.ViewHolder,下面是 MyAdapter 的代碼:

class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

        private List<String> mData;

        // 通過構造方法傳入資料
        public MyAdapter(List<String> data) {
            this.mData = data;
        }

        @Override
        public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_recyclerview, parent, false);
            return new MyViewHolder(view);
        }

        @Override
        public void onBindViewHolder(MyAdapter.MyViewHolder holder, int position) {
            holder.tv.setText(mData.get(position));
        }

        @Override
        public int getItemCount() {
            return mData.size();
        }

        class MyViewHolder extends RecyclerView.ViewHolder {
            TextView tv;

            public MyViewHolder(View itemView) {
                super(itemView);
                tv = (TextView) itemView.findViewById(R.id.tv_content);
            }
        }
    }
           

RecyclerView.Adapter 是一個抽象類,我們必須實作其

onBindViewHolder()

getItemCount()

方法。

getItemCount()

方法傳回的是 Item 個數,這個與 ListView 類似。

onBindViewHolder()

方法傳回的則是一個 RecyclerView.ViewHolder 對象,我們這裡傳回自己的定義的 MyViewHolder。MyViewHolder 通過構造方法傳入每個 Item 的布局檔案,其單項 Item 局部檔案

item_recyclerview.xml

代碼如下:

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="10dp" />
</FrameLayout>
           

很簡單,因為我們的資料就是簡單的文本,是以采用 TextView。

4,好了,各項準備工作完成,我們隻需要在 MainActivity 調用如下幾行代碼即可:

List<String> data = new ArrayList<>();
for (int i = ; i < ; i++) {
    if (i < ) {
        data.add("這是第 " +  + (i + ) + " 條資料");
    } else {
        data.add("這是第 " + (i + ) + " 條資料");
    }
}

RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(new MyAdapter(data));
           

有幾點說明下,必須調用

setLayoutManager()

方法,否則是不會顯示資料的。這裡我們傳入一個 LinearLayoutManager,看名字就知道,它類似于 LinearLayout,可以設定布局方向為水準或垂直,預設的實作就是垂直布局,是以通過上面簡單的使用,我們已經簡單的實作了類似于 Listview  的效果。看下效果如下:

RecyclerView 基本用法及另類加分割線技巧

這樣就簡單實作了 Listview 的效果,如果你想實作網格布局或者瀑布流布局,隻需要将 LinearLayoutManager 換成 GridLayoutManager 或者 StaggeredGridLayoutManager 即可。

3 另類分割線實作

可以看到,與 Listview 唯一的差別在于 Item 之間并沒有分割線,前文說過,通常添加分割線是采用自定義 ItemDecoration 實作,這裡我們采用較為簡單的一種寫法。非常簡單隻需要修改

item_recyclerview.xml

如下:

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/darker_gray">

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="1dp"
        android:background="@android:color/background_light"
        android:gravity="center"
        android:padding="10dp"
        android:textColor="@android:color/black" />
</FrameLayout>
           

我們先來看下效果:

RecyclerView 基本用法及另類加分割線技巧

那麼這是怎麼實作的呢?其實很簡單,主要就是利用了兩個屬性,

background

layout_marginBottom

,總結起來按照以下步驟使用即可:

  • Item 最外層套一層布局 FrameLayout,設定其 background 屬性為你需要的分割線顔色
  • 将你自己的 Item 背景設定為需要的顔色
  • 根據需要設定 magin 屬性

這種方法有一定局限性,比如隻适合較為簡單的布局,像瀑布流這種複雜布局,這種方法就不适用了,但我個人覺得一般瀑布流布局也不需要什麼分割線了,畢竟都是不規則塊。如果要采用自定義 ItemDecoration 添加分割線的可以參照張鴻洋的部落格Android RecyclerView 使用完全解析 體驗藝術般的控件

4 小細節分析

這裡有一個細節,在 inflate xml 的時候這裡我們并沒有用

View.inflate();

而是用了

LayoutInflater.from().inflate();

如果采用前者,會使得 Item 的

match_parent

屬性失效。表現出來就是,Item 的子布局設定的某些屬性比如居中看上去都失效了,實際上是因為

match_parent

失效變成了

wrap_content

。具體可以用個例子來示範下,比如我們修改

item_recyclerview.xml

代碼如下:

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/holo_green_dark">

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="15dp"
        android:background="@android:color/holo_red_dark"
        android:gravity="center"
        android:padding="10dp"
        android:textColor="@android:color/black" />
</FrameLayout>
           

LayoutInflater.from().inflate();

方法也就是正常情況,如下左圖所示。用

View.inflate();

方法的情況如下右圖所示。

RecyclerView 基本用法及另類加分割線技巧
RecyclerView 基本用法及另類加分割線技巧

從上圖可以看出,确實是 FrameLayout 的

match_parent

屬性失效了,與 TextView 無關。這也是我剛開始學習 RecyclerView 時遇到的一個坑,感興趣的同學可以去試試。詳細的相關分析可以參考這篇部落格:RecyclerView中的item的match_parent屬性失效問題解決方案