1. 程式人生 > >Android 控制元件之 RecyclerView(一)—— 載入檢視和佈局選擇

Android 控制元件之 RecyclerView(一)—— 載入檢視和佈局選擇

本文目錄

一、概述

RecyclerView 是在 Android5.0 之後,Google 推出的一個新控制元件,是作為 ListView、GridView 的替代者出現的。它具有比上述兩者更高的靈活性,在功能上也更加強大。Google 官方對它的定義是:

A flexible view for providing a limited window into a large data set.
向一個有限的視窗(window)提供大量資料集的靈活檢視(view)。

先來看看 RecyclerView 能實現哪些效果:
圖1 圖2
圖3 圖4
由於 RecylerView 定義在了 support 庫中,如果我們想要使用 RecyclerView 控制元件,需要向專案中的 build.gradle新增程式碼 implementation ‘com.android.support:recyclerview-v7:27.1.1’ ,如下所示:

dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation 'com.android.support:appcompat-v7:27.1.1' implementation 'com.android.support.constraint:constraint-layout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.android.support:recyclerview-v7:27.1.1' }

新增完之後一定要記得點一下右上角的 Sync Now 進行同步,否則可能會出現錯誤。

二、列表檢視的處理

1. item 的佈局檔案

首先,在 activity_main 中新增 RecyclerView 控制元件,程式碼如下:

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

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

</LinearLayout>

其次,在 layout 資料夾下新建一個 Layout Resource file,命名為 item_recycler_view,它是 RecyclerView 中 item(即子項)的佈局檔案,我們需要的 item 佈局為圖片(ImageView)和文字(TextView)的組合,所以程式碼如下所示:

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

    <ImageView
        android:id="@+id/iv_item"
        android:layout_width="80dp"
        android:layout_height="80dp" />

    <TextView
        android:id="@+id/tv_item"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginStart="20dp"
        android:textSize="15sp" />

</LinearLayout>

2. 構造 Adapter 類

既然展示的是關於食物的圖片和文字的檢視,那麼我們就新建一個 Food 類來存取我們每個食物(Food)的例項,程式碼如下:

public class Food {
    // 食物的圖片id
    private int imageId;
    // 食物的名字
    private String name;
    // 建構函式
    public Food(String name, int imageId){
        this.imageId = imageId;
        this.name = name;
    }
    // 獲取食物的圖片id
    public int getImageId() {
        return imageId;
    }
    // 獲取食物的名字
    public String getName() {
        return name;
    }
}

接著,為我們的 RecyclerView 定義一個 Adapter 類。和 ListView 一樣,RecyclerView 也需要使用 Adapter 介面卡,現在我們新建一個 MyAdapter 類,繼承自 RecyclerView.Adapter,其中泛型引數 VH 表示的就是 ViewHolder ,這裡我們將泛型型別指定為 MyAdapter.ViewHolder,它是 MyAdapter 中我們自己定義的一個 的內部類,繼承自 Recycler.ViewHolder

我們還需要在 MyAdapter 類中覆寫三個方法 :onCreateViewHolder()onBindViewHolder()getItemCount(),新建好的程式碼如下:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    // 儲存 Food 型別資料的資料來源 
    private List<Food> mFoodList;
    
    // 構造方法用於傳入資料
    public MyAdapter(List<Food> list){
		mFoodList = list;
    }
    
	// 建立 ViewHolder的例項,在這裡載入 item_recycler_view 
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
    	return null;
    }
    
    // 繫結 ViewHolder,用於對每個 item 進行賦值操作
    @Override
    public void onBindViewHolder(ViewHolder holder, int position){
    }
    
    // 獲取 item 的數量
    @Override
    public int getItemCount(){
    	return 0;
    }
    
    // 自定義的 ViewHolder 類,繼承自 RecyclerView.ViewHolder
    static class ViewHolder extends RecyclerView.ViewHolder{
        View view;
        ViewHolder(View view){
            super(view);
            this.view = view;
        }
    }
}

分析一下這段程式碼,mFoodList 是用於儲存 Food 型別資料的資料來源,它通過構造方法獲取資料。接下來看第一個需要覆寫的方法 onCreateViewHolder(),從字面上理解,顧名思義,就是需要我們在這個方法中建立 ViewHolder 例項。在這個方法中,我們將之前寫好的佈局檔案 item_recycler_view 載入進來,並建立一個 ViewHolder 例項返回。程式碼如下:

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
	// 傳入子項(item)的佈局檔案
    View view = LayoutInflater.from(parent.getContext()).inflate(
            R.layout.item_recycler_view, parent, false);
    // 建立 ViewHolder 例項
    ViewHolder holder = new ViewHolder(view);
    return holder;
}

接下來是第二個覆寫方法 onBindViewHolder(),該方法用於對 RecyclerView 子項的資料進行賦值,我們可以通過第二個引數 position 來獲取當前項的 Food 例項,然後把 Food 例項中的資料傳入 holder 的 ImageViewTextView 中。程式碼如下:

@Override
public void onBindViewHolder(ViewHolder holder, int position){
	// 獲取當前項的 Food 例項
    Food food = mFoodList.get(position);
    TextView textView = holder.view.findViewById(R.id.tv_item);
    ImageView imageView = holder.view.findViewById(R.id.iv_item);
    textView.setText(food.getName());
    imageView.setImageResource(food.getImageId());
}

最後一個覆寫方法 getItemCount() 就很簡單了,因為是獲取 item 的個數,所以直接返回 mFoodList 的長度即可。

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

完整的 MyAdapter 類程式碼如下所示:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    // 儲存 Food 型別資料的資料來源 
    private List<Food> mFoodList;
    
    // 構造方法用於傳入資料
    public MyAdapter(List<Food> list){
        mFoodList = list;
    }

	// 建立 ViewHolder的例項,在這裡載入 item_recycler_view 
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
    	// 傳入子項(item)的佈局檔案
        View view = LayoutInflater.from(parent.getContext()).inflate(
                R.layout.item_recycler_view, parent, false);
        // 建立 ViewHolder 例項
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

	// 繫結 ViewHolder,用於對每個 item 進行賦值操作
    @Override
    public void onBindViewHolder(ViewHolder holder, int position){
    	// 獲取當前項的 Food 例項
        Food food = mFoodList.get(position);
        TextView textView = holder.view.findViewById(R.id.tv_item);
        ImageView imageView = holder.view.findViewById(R.id.iv_item);
        textView.setText(food.getName());
        imageView.setImageResource(food.getImageId());
    }

	// 獲取 item 的數量
    @Override
    public int getItemCount(){
        return mFoodList.size();
    }

	// 自定義的 ViewHolder 類,繼承自 RecyclerView.ViewHolder
    static class ViewHolder extends RecyclerView.ViewHolder{
        View view;
        ViewHolder(View view){
            super(view);
            this.view = view;
        }
    }
}

3. 佈局管理器

在寫好 MyAdapter 類之後,就是在 MainActivity 中使用 RecyclerView 了。我們到目前為止還剩下一個問題沒解決,那就是既然提到了 RecyclerView 是作為 ListView 和 GridView 的替代者出現的,那麼它是如何管理它的佈局的呢?

答案就是呼叫 RecyclerView 例項的 setLayoutManager() 方法。setLayoutManager()方法接受一個 LayoutManager 型別的引數,通過這個引數來實現 RecyclerView 的各種各樣的佈局,前面所展示的佈局都是在這個方法中進行設定的。下面就來介紹幾個佈局管理器型別:

1)LinearLayoutManager

如果我們要實現像 ListView 那樣的垂直線性佈局,就得使用 LinearLayoutManager 。程式碼如下所示:

// 獲取 RecyclerView 的例項
mRecyclerView = (RecyclerView)findViewById(R.id.recycler_view);
// 線性佈局管理器
LinearLayoutManager manager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(manager);

先構造 LinearLayoutManager 的例項 manager,它的預設 orientationVERTICAL,與我們的需求正好一致,所以構造好之後直接將 manager 傳入 setLayoutManager() 即可。最終效果如前面垂直線性佈局所示。

而當我們需要實現橫向線性佈局時,只需要在第二句和第三句之間新增setOrientation()方法即可:

// 線性佈局管理器
LinearLayoutManager manager = new LinearLayoutManager(MainActivity.this);
manager.setOrientation(LinearLayoutManager.HORIZONTAL); 
mRecyclerView.setLayoutManager(manager);

效果如前面水平線性佈局所示。

2)GridLayoutManager

如果我們要實現像 GridView 那樣的網格佈局,就得使用 GridLayoutManager 。程式碼如下所示:

// 網格佈局管理器
GridLayoutManager manager = new GridLayoutManager(MainActivity.this, 3);
mRecyclerView.setLayoutManager(manager);

GridLayoutManager 的建構函式的第二個引數型別是 int,表示列數,這裡我們指定為 3,即顯示三列的資料。效果如前面網格佈局所示。

3)StaggeredGridLayoutManager

如果我們想實現瀑布流佈局的效果的話,就得使用 StaggeredGridLayoutManager,程式碼如下所示:

// 瀑布流佈局管理器
StaggeredGridLayoutManager manager = new StaggeredGridLayoutManager(3,
        StaggeredGridLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(manager);

StaggeredGridLayoutManager 的構造方法第一個引數型別為int,表示列數,這裡我們同樣指定為3。第二個引數表示佈局的方向,這裡我們設定為垂直方向。實現效果如前面瀑布流佈局所示。

4. 完整的 MainActivity 程式碼

這裡我們以垂直線性佈局為例,直接上程式碼:

public class MainActivity extends AppCompatActivity {

    private RecyclerView mRecyclerView;
    private List<Food> foodList;
    private MyAdapter adapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 初始化食物的資料
        initData();
        mRecyclerView = (RecyclerView)findViewById(R.id.recycler_view);
        // 垂直線性佈局
        LinearLayoutManager manager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(manager);
        // 將 foodList 傳入 adapter 中
        adapter = new MyAdapter(MainActivity.this, foodList);
        mRecyclerView.setAdapter(adapter);
    }

    // 這裡為了方便展示隨便初始化了 20 個食物的資料
    private void initData(){
        foodList = new ArrayList<>();
        for(int i = 0; i < 5; i++) {
            Food food1 = new Food("Cake", R.drawable.cake);
            Food food2 = new Food("Biscuit", R.drawable.biscuit);
            Food food3 = new Food("Bread", R.drawable.bread);
            Food food4 = new Food("Hamburger", R.drawable.hamburger);
            foodList.add(food1);
            foodList.add(food2);
            foodList.add(food3);
            foodList.add(food4);
        }
    }
}

到這裡相信很多人都會看的比較明白了,在 MainActivity 中我們首先初始化了我們要展示的食物的資料,然後將佈局設定為線性(預設為垂直佈局)。然後我們會初始化adpater,它是我們自定義的 MyAdapter 類的例項,我們將初始好後的 foodList 作為引數傳給它。最後,它會被作為引數傳給 setAdapter() 方法。

至此,我們的程式碼就全部完成了!執行一下,效果如下圖所示:
圖5
如果我們需要設定簡單的分割線的話,可以新增一句程式碼:

mRecyclerView.addItemDecoration(new DividerItemDecoration(MainActivity.this,
		 DividerItemDecoration.VERTICAL));

效果如下圖所示:
圖6
為了節省篇幅,其他的佈局程式碼這裡就不貼出來了,方法也很簡單,無非是在setLayoutManager() 方法中傳入不同的佈局器引數,然後對 item_recycler_view 或資料進行簡單的修改即可。

三、總結

對於 RecyclerView 的使用,可分為 4 步:
Step 1:向 build.gradle 中新增相應的依賴
Step 2:為 item 新增一個佈局檔案
Step 3:新建一個繼承自 RecyclerView.Adapter 的 Adapter 類,並覆寫 onCreateViewHolder()、onBindViewHolder() 和 getItemCount() 這三個方法。
Step 4:在 MainActivity 中,獲取 RecyclerView 的例項,然後通過 setLayoutManager() 進行佈局設定,通過 setAdapter() 設定介面卡,然後就大功告成了!

最後,本篇部落格是我寫的第一篇部落格,如果有寫的不清楚或者不清晰的地方,望見諒!如果對於我的部落格有任何想法建議的話,歡迎評論或私信!