RecyclerView và ViewHolder trong Android Studio

Khóa học lập trình Android cơ bản

5.0 (2 đánh giá)
Tạo bởi Kteam Cập nhật lần cuối 16:42 14-08-2020 86.922 lượt xem 6 bình luận
Tác giả/Dịch giả: Kteam
Học nhanh

Danh sách bài học

RecyclerView và ViewHolder trong Android Studio

Dẫn nhập

Ở các bài học trước, chúng ta đã cùng nhau tìm hiểu về LISTVIEW TRONG ANDROID, và được biết nó là một View đặc biệt, có thể chứa danh sách các phần tử giống nhau, cũng như cách cài đặt khá là “dị” của nó để có thể show ra được những gì mình mong muốn.

Nhưng chưa hết! Ví dụ lần trước mình đưa ra khá là dở, dở ở đâu? Chúng ta sẽ cùng xem lại trong bài này và mình cũng sẽ hướng dẫn các bạn sử dụng một loại View khác cũng hỗ trợ hiển thị danh sách rất mạnh là RecyclerView.


Nội dung

Để đọc hiểu bài này tốt nhất các bạn nên có kiến thức cơ bản về các phần:

Trong bài học này, chúng ta sẽ cùng tìm hiểu các vấn đề:

  • ViewHolder.
  • RecyclerView và điểm khác của nó so với ListView.

Nào, chúng ta cùng vào vấn đề thứ nhất. Bài viết này được tấu bằng phong cách lãng khách nên sẽ hơi dị một chút, mọi người cùng thưởng thức!


ViewHolder

Tương truyền từ thời API 23, có một tay sát thủ khét tiếng đất Android. Tên hắn ai cũng biết, nhưng khuôn mặt của hắn muôn hình vạn trạng, biến đổi khôn lường.

Trong một quán rượu, mọi người xì xầm to nhỏ về chủ đề ListView, tay hảo hán trong giới View của Android, và được nhắc đến rất nhiều tại đại hội võ lâm Google I/O năm 2010.

Hẵng khoan, đã bao giờ các hạ tự hỏi rằng, liệu với những danh sách gồm rất nhiều phần tử, khoảng vài ngàn trở lên chẳng hạn, thì với mỗi item XML trong ListView chúng sẽ được xử lý ra sao không?

Quay lại ví dụ trước, chúng ta có hảo hán ListView với vũ khí là Adapter đại pháp:

package com.howkteam.listviewexample;

import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

public class DataAdapter extends BaseAdapter {

    private Activity activity;
    private String[] items;

    public DataAdapter(Activity activity, String[] items) {
        this.activity = activity;
        this.items = items;
    }

    @Override
    public int getCount() {
        return items.length;
    }

    @Override
    public Object getItem(int i) {
        return items[i];
    }

    @Override
    public long getItemId(int i) {
        return i;
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        // Gọi layoutInflater ra để bắt đầu ánh xạ view và data.
        LayoutInflater inflater = activity.getLayoutInflater();

        // Đổ dữ liệu vào biến View, view này chính là những gì nằm trong item_name.xml
        view = inflater.inflate(R.layout.item_name, null);

        // Đặt chữ cho từng view trong danh sách.
        TextView tvName = (TextView) view.findViewById(R.id.tv_name);
        tvName.setText(items[i]);

        // Trả về view kết quả.
        return view;
    }
}

  • “Dở, quá dở!” – Một bô lão của Android đại lục lên tiếng.

Trong quán rượu, mọi người xì xầm to nhỏ. Tên tiểu nhị lớn tiếng hỏi:

  • “Sao dở? Adapter quả thực rất mạnh. Một mình nó cũng đủ điều khiển hàng vạn tinh binh kia mà!”.

Người chê dở khi nãy lộ mặt. Hắn dùng cây thư pháp của mình quét lên bàn rượu như hạc lướt mặt nước:

  • “Mỗi lần tạo ra một phần tử để hiển thị, nó sẽ phải duyệt qua các cấp của View (trong item_name.xml) và mỗi lần như vậy, hàm findViewById() lại được triệu gọi, mặc dù view vẫn chỉ có item_name.xml.  Thử tưởng tượng với một danh sách dài khoảng vài ngàn phần tử, mỗi lần phải gọi như vậy thì lãng phí cỡ nào????

Thật ra thì việc này hoàn toàn có thể được bằng cách sử dụng cơ chế ViewHolder: Gán cứng view để nó inflate file xml, dữ liệu sau đó chỉ việc đổ vào mà không tạo ra các layout con mới, thêm class con này vào trong DataAdapter.java:


public static class ViewHolder {
    private CardView cardView;
    private TextView tvCategoryName;
    private TextView tvNumberOfPosts;
}

Tuy nhiên về bản chất thì ListView không ngăn cấm việc không sử dụng ViewHolder. Dù khá tự do nhưng về hiệu năng thì không ổn. Hơn nữa ListView chỉ có thể hiển thị danh sách dạng xếp dọc (Vertical Scrolling).

Từ phiên bản Android Support Library 23.2, Google đã bổ sung thêm RecyclerView vào thư viện hỗ trợ. Với một số đặc tính như:

  • Buộc sử dụng ViewHolder để tái sử dụng xml item cho mỗi phần tử, tăng tốc hiệu năng cho danh sách.
  • Có thể sử dụng nhiều item type, và đương nhiên là với ViewHolder thì ta cũng có thể tái sử dụng nó.
  • Không chỉ hỗ trợ dạng danh sách dọc, RecyclerView còn hỗ trợ dạng Grid, dọc, ngang đủ cả.

Hảo hán ListView tức giận, thách đấu với lão già dám ngang nhiên chê mình giữa thanh thiên bạch nhật. Lão già kia hiện nguyên hình là RecyclerView. Hai bên kẻ tám lạng người nửa cân thách đấu giao kèo: Mục tiêu là

Hiển thị một danh sách đơn giản, nhưng danh sách này gồm 2 loại phần tử từ cùng mộ model là Person, hiện thị màu theo giới tính (nam là đen, nữ là hồng)

Và trận thư hùng bắt đầu! 


RecyclerView và ListView

Hồi 1: Ban giám khảo tạo một project mới lấy tên RecyclerViewExample như thường lệ, và vẫn để Empty Activity:

RecyclerView và ViewHolder

Để có thể khai triển tuyệt kỹ, RecyclerView cần được thêm vào file build.gradle của app module như sau:

compile 'com.android.support:recyclerview-v7:24.2.1'

RecyclerView và ViewHolder

Hồi 2: Hai bên cùng tỉ thí trong activity_main.xml:

  • Activity_main.xml (của RecyclerViewExample):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.howkteam.recyclerviewexample.MainActivity">

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

</RelativeLayout>

So sánh với của ListViewExample:

  • Activity_main.xml (của ListViewExample):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.howkteam.listviewexample.MainActivity">

    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>

Vậy là cách khai báo của hai bên đều như nhau, không có gì khác! 

Tiếp theo, triển khai layout cho từng kiểu item:

  • Item_names.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="wrap_content"
              android:orientation="vertical"
              android:padding="16dp">

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</LinearLayout>

  • Item_name_female.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="wrap_content"
              android:orientation="vertical"
              android:padding="16dp">

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/colorAccent"/>

</LinearLayout>

Lưu ý: Thuộc tính textColor ở trên có thể thay bằng mã màu hexa với dấu # đằng trước, ví dụ #FFFFFF

Hồi 3: RecyclerView xuất chiêu, hắn có riêng cho mình một class Adapter, chỉ việc extends từ nó:

package com.howkteam.recyclerviewexample;
import android.support.v7.widget.RecyclerView;
public class RecyclerDataAdapter extends RecyclerView.Adapter {
        
}

Và chọn Code > Implement Methods để chèn những method cần thiết:

RecyclerView và ViewHolder

Và thiên hạ thấy được:

package com.howkteam.recyclerviewexample;

import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;

public class RecyclerDataAdapter extends RecyclerView.Adapter {
    
    @Override
    public int getItemCount() {
        return 0;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return null;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

    }
}

Nhưng khoan, cái gì kia?

RecyclerView và ViewHolder

Đúng! Tuyệt kỹ của RecyclerView luôn đi kèm một ViewHolder, dưới dạng một inner static class. Vì thế để thi triển tốt nhất, cần định nghĩa một class ViewHolder static trong RecyclerDataAdapter.

Hồi 4: Lúc này việc tạo model là cần thiết để có thể hiển thị nhiều loại dữ liệu. Tạm lấy một model là Person và có 2 thuộc tính là tên và giới tính:

  • Person.java
package com.howkteam.recyclerviewexample;

public class Person {
    private String name;
    private boolean isMale;

    public Person(String name, boolean isMale) {
        this.name = name;
        this.isMale = isMale;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isMale() {
        return isMale;
    }

    public void setMale(boolean male) {
        isMale = male;
    }
}

Hồi 5: Quay lại với RecyclerAdapter, để thi triển tuyệt chiêu nhiều kiểu item type, RecyclerView có sẵn phương thức getItemViewType để lấy ra kiểu View tương ứng. Nội dung đầy đủ như sau:

package com.howkteam.recyclerviewexample;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.List;

public class RecyclerDataAdapter extends RecyclerView.Adapter<RecyclerDataAdapter.DataViewHolder> {

    private List<Person> people;
    private Context context;

    public RecyclerDataAdapter(Context context, List<Person> people) {
        this.context = context;
        this.people = people;
    }

    @Override
    public int getItemCount() {
        return people == null ? 0 : people.size();
    }

    @Override
    public RecyclerDataAdapter.DataViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView;

        // Kiểm tra item view type, nếu là 1 thì inflate layout item_names.xml, 2 thì sử dụng item_names_female.
        switch (viewType) {
            case 1:
                itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_names, parent, false);
                break;
            case 2:
                itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_names_female, parent, false);
                break;
            default:
                itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_names, parent, false);
                break;
        }


        return new DataViewHolder(itemView);
    }

    @Override
    public int getItemViewType(int position) {
        // Cài đặt kiểu item view type cho từng phần tử, nếu có giới tính là nam thì trả về 1, nữ thì trả về 2.
        if (people.get(position).isMale()) {
            return 1;
        } else {
            return 2;
        }
    }

    @Override
    public void onBindViewHolder(RecyclerDataAdapter.DataViewHolder holder, int position) {
        String name = people.get(position).getName();
        holder.tvName.setText(name);
    }

    /**
     * Data ViewHolder class.
     */
    public static class DataViewHolder extends RecyclerView.ViewHolder {

        private TextView tvName;

        public DataViewHolder(View itemView) {
            super(itemView);

            tvName = (TextView) itemView.findViewById(R.id.tv_name);
        }
    }
}

Hồi 6: Quay lại với MainActivity, lúc này chỉ việc truyền vào bên trong Adapter đã khởi tạo, cùng với danh sách Person với đủ loại giới tính: Nam / nữ:

package com.howkteam.recyclerviewexample;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private RecyclerView rvItems;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        List<Person> people = new ArrayList<>();
        people.add(new Person("Long", true));
        people.add(new Person("My", false));
        people.add(new Person("Duong", true));
        people.add(new Person("Duyen", false));

        rvItems = (RecyclerView) findViewById(R.id.rv_items);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        rvItems.setLayoutManager(layoutManager);
        rvItems.setHasFixedSize(true);
        rvItems.setAdapter(new RecyclerDataAdapter(this, people));
    }
}


Toàn bộ những class và layout cần thiết được liệt kê như trong hình:

RecyclerView và ViewHolder

Và thế là danh sách cấu thành bằng RecyclerView đã hoàn tất:

RecyclerView và ViewHolder


Kết luận

Qua bài này chúng ta đã nắm được hàm là gì, cách sử dụng hàm, các từ khóa và các đặc tính đi kèm của hàm.

Bài sau chúng ta sẽ tìm hiểu về GIAO DIỆN TRƯỢT NGANG VỚI VIEWPAGER & TAB TRONG ANDROID CƠ BẢN.

Cảm ơn các bạn đã theo dõi bài viết. Hãy để lại bình luận hoặc góp ý của mình để phát triển bài viết tốt hơn. Đừng quên “Luyện tập – Thử thách – Không ngại khó”.


Tải xuống

Tài liệu

Nhằm phục vụ mục đích học tập Offline của cộng đồng, Kteam hỗ trợ tính năng lưu trữ nội dung bài học RecyclerView và ViewHolder trong Android Studio dưới dạng file PDF trong link bên dưới.

Ngoài ra, bạn cũng có thể tìm thấy các tài liệu được đóng góp từ cộng đồng ở mục TÀI LIỆU trên thư viện Howkteam.com

Đừng quên likeshare để ủng hộ Kteam và tác giả nhé!


Thảo luận

Nếu bạn có bất kỳ khó khăn hay thắc mắc gì về khóa học, đừng ngần ngại đặt câu hỏi trong phần bên dưới hoặc trong mục HỎI & ĐÁP trên thư viện Howkteam.com để nhận được sự hỗ trợ từ cộng đồng.

Nội dung bài viết

Tác giả/Dịch giả

Khóa học

Khóa học lập trình Android cơ bản

Serial tutorial hướng dẫn lập trình Android cơ bản

Đánh giá

JohnWick đã đánh giá 22:34 28-12-2020

hoangnhutsp đã đánh giá 19:38 01-03-2019

Bình luận

Để bình luận, bạn cần đăng nhập bằng tài khoản Howkteam.

Đăng nhập
thaitoan2210 đã bình luận 21:45 04-01-2019

Anh ơi, em bị vướn chỗ R đó anh, nó không tìm thấy R trong  hàm 

public RecyclerViewDataAdapter.DataViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
    View itemView;

    if(viewType==2)
        itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.female, viewGroup, false);
    else
        itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.male, viewGroup, false);

    return new DataViewHolder(itemView);
}

Rất mong anh chia sẻ cách xử lý. EM cảm ơn anh.

Tường Vy đã bình luận 17:32 19-03-2018

nghe như trong kiếm hiệp ý

beehomiez đã bình luận 23:01 25-12-2016
chỗ thêm dòng compile nên để là com.android.support:recyclerview-v7:+ để đúng với mọi phiên bản sdk hay s ấy a, khác phiên bản là báo lỗi k sync dc
Opiz đã bình luận 15:28 25-11-2016
Alt + Insert có getter setter mà anh :D
Không có video.