Trang chủHướng dẫnHướng dẫn RecyclerView và CardView trong Android
Android

Hướng dẫn RecyclerView và CardView trong Android

CyStack blog 6 phút để đọc
CyStack blog11/08/2025
Locker Avatar

Chris Pham

Technical Writer

Locker logo social
Reading Time: 6 minutes

Cùng với Material Design, RecyclerView và CardView được giới thiệu trong phiên bản Android Lollipop. Đối với những ai chưa biết, Material Design là một hướng dẫn toàn diện về giao diện người dùng với các thành phần widget mới được giới thiệu từ Android 5.0 nhằm cải thiện tính thẩm mỹ cho ứng dụng.

RecyclerView và CardView trong Android

Android RecyclerView

Android RecyclerView là phiên bản nâng cấp, mạnh mẽ và linh hoạt hơn của ListView. RecyclerView tương tự ListView, nhưng bắt buộc phải sử dụng lớp RecyclerView.ViewHolder để giữ dữ liệu cho từng phần tử, điều mà ListView không yêu cầu. Đúng như tên gọi, RecyclerView cho phép tái sử dụng các phần tử trong danh sách bằng cách tái chế chúng khi người dùng cuộn lên hoặc xuống.

Một cải tiến khác là RecyclerView cho phép thiết lập LayoutManager một cách linh hoạt tại thời điểm chạy, trong khi ListView chỉ hỗ trợ danh sách cuộn theo chiều dọc. RecyclerView hỗ trợ các loại layout sau:

  • LinearLayoutManager: hỗ trợ danh sách theo chiều dọc hoặc ngang
  • StaggeredLayoutManager: hỗ trợ hiển thị danh sách dạng lưới không đồng đều
  • GridLayoutManager: hỗ trợ hiển thị danh sách dạng lưới giống như GalleryView

Các lớp liên quan đến RecyclerView

  • RecyclerView.ItemAnimator cung cấp khả năng tạo hiệu ứng động cho các phần tử hiệu quả hơn so với ListView
  • RecyclerView.ItemDecorator hỗ trợ thêm viền hoặc đường phân cách tốt hơn, từ đó giúp lập trình viên kiểm soát chi tiết hơn

Do đó, RecyclerView linh hoạt và tùy biến tốt hơn nhiều so với ListView. Để sử dụng RecyclerView, cần thêm thư viện hỗ trợ trong tập tin build.gradle như sau:

dependencies {
    compile 'com.android.support:recyclerview-v7:21.0.0-rc1'
}

Android CardView

Android CardView UI là một thành phần cho phép hiển thị thông tin bên trong các thẻ. Thường được sử dụng để trình bày thông tin liên hệ. Thành phần này cũng nằm trong một thư viện hỗ trợ riêng, nên cần thêm phụ thuộc như sau:

dependencies {
    compile 'com.android.support:cardview-v7:21.0.0-rc1'
    compile 'com.android.support:recyclerview-v7:21.0.0-rc1'
}

CardView cho phép thiết lập các thuộc tính như màu nền, bóng đổ, bán kính góc, độ nâng cao… Để sử dụng các thuộc tính tùy chỉnh trong XML, bạn cần khai báo namespace sau trong parent layout. Dưới đây là phần khai báo namespace kèm theo một số thuộc tính từ dự án của chúng tôi.:

<android.support.v7.widget.CardView
    android:id="@+id/card_view"
    xmlns:card_view="<https://schemas.android.com/apk/res-auto>"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    card_view:cardBackgroundColor="@color/grey_300"
    card_view:cardCornerRadius="10dp"
    card_view:cardElevation="5dp"
    card_view:cardUseCompatPadding="true">

Các thuộc tính quan trọng:

  • card_view:cardCornerRadius: thiết lập độ cong của các góc
  • card_view:cardBackgroundColor: thiết lập màu nền cho thẻ

Trong ví dụ minh họa, RecyclerView được sử dụng để hiển thị danh sách các CardView chứa tên và phiên bản Android cùng với một logo minh họa. Onclick CardView được lập trình để xóa thẻ đó khỏi danh sách. Chúng tôi đã thêm một tùy chọn menu trong ActionBar để thêm lại các thẻ đã bị xóa theo đúng thứ tự ban đầu. Lưu ý: các hình ảnh logo được lấy ngẫu nhiên từ Google, nên kích thước có thể không đồng nhất.

>> Bài viết liên quan: Cách triển khai NavigationView trong Android

Ví dụ Android RecyclerView và CardView

android-recyclerview-cardview

Dự án gồm một MainActivity hiển thị RecyclerView. Mỗi CardView được thêm vào RecyclerView thông qua lớp CustomAdapter. Lớp DataModel dùng để truy xuất dữ liệu cho từng CardView thông qua các phương thức getter. Lớp MyData chứa các mảng dữ liệu văn bản và hình ảnh cùng với các ID tương ứng.

Ví dụ mã nguồn Android sử dụng RecyclerView và CardView

Tệp activity_main.xml chứa RecyclerView bên trong một RelativeLayout, được trình bày như sau:

Tập tin activity_main.xml

<RelativeLayout xmlns:android="<https://schemas.android.com/apk/res/android>"
    xmlns:tools="<https://schemas.android.com/tools>"
    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=".MainActivity"
    android:background="@color/grey_300">

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

</RelativeLayout>

Bố cục CardView trong Android được định nghĩa như sau:

Mã nguồn cards_layout.xml:

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

    <android.support.v7.widget.CardView
        android:id="@+id/card_view"
        xmlns:card_view="<https://schemas.android.com/apk/res-auto>"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        card_view:cardBackgroundColor="@color/color_white"
        card_view:cardCornerRadius="10dp"
        card_view:cardElevation="5dp"
        card_view:cardUseCompatPadding="true">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <ImageView
                android:id="@+id/imageView"
                android:tag="image_tag"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_margin="5dp"
                android:layout_weight="1"
                android:src="@drawable/ic_launcher" />

            <LinearLayout
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginTop="12dp"
                android:layout_weight="2"
                android:orientation="vertical">

                <TextView
                    android:id="@+id/textViewName"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center_horizontal"
                    android:layout_marginTop="10dp"
                    android:text="Android Name"
                    android:textAppearance="?android:attr/textAppearanceLarge" />

                <TextView
                    android:id="@+id/textViewVersion"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center_horizontal"
                    android:layout_marginTop="10dp"
                    android:text="Android Version"
                    android:textAppearance="?android:attr/textAppearanceMedium" />

            </LinearLayout>
        </LinearLayout>

    </android.support.v7.widget.CardView>

</LinearLayout>

CardView chứa một ImageView và hai TextView được đặt trong một LinearLayout lồng nhau. Tập tin menu_main.xml chứa một tùy chọn để thêm lại các thẻ đã bị xóa:

<menu xmlns:android="<https://schemas.android.com/apk/res/android>"
    xmlns:app="<https://schemas.android.com/apk/res-auto>"
    xmlns:tools="<https://schemas.android.com/tools>"
    tools:context=".MainActivity">

    <item android:id="@+id/add_item"
        android:title="Add"
        android:orderInCategory="100"
        app:showAsAction="always" />
</menu>

Lớp MainActivity.java

package com.journaldev.recyclerviewcardview;

import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    private static RecyclerView.Adapter adapter;
    private RecyclerView.LayoutManager layoutManager;
    private static RecyclerView recyclerView;
    private static ArrayList<DataModel> data;
    static View.OnClickListener myOnClickListener;
    private static ArrayList<Integer> removedItems;

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

        myOnClickListener = new MyOnClickListener(this);

        recyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
        recyclerView.setHasFixedSize(true);

        layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        recyclerView.setItemAnimator(new DefaultItemAnimator());

        data = new ArrayList<DataModel>();
        for (int i = 0; i < MyData.nameArray.length; i++) {
            data.add(new DataModel(
                    MyData.nameArray[i],
                    MyData.versionArray[i],
                    MyData.id_[i],
                    MyData.drawableArray[i]
            ));
        }

        removedItems = new ArrayList<Integer>();

        adapter = new CustomAdapter(data);
        recyclerView.setAdapter(adapter);
    }

    private static class MyOnClickListener implements View.OnClickListener {

        private final Context context;

        private MyOnClickListener(Context context) {
            this.context = context;
        }

        @Override
        public void onClick(View v) {
            removeItem(v);
        }

        private void removeItem(View v) {
            int selectedItemPosition = recyclerView.getChildPosition(v);
            RecyclerView.ViewHolder viewHolder
                    = recyclerView.findViewHolderForPosition(selectedItemPosition);
            TextView textViewName
                    = (TextView) viewHolder.itemView.findViewById(R.id.textViewName);
            String selectedName = (String) textViewName.getText();
            int selectedItemId = -1;
            for (int i = 0; i < MyData.nameArray.length; i++) {
                if (selectedName.equals(MyData.nameArray[i])) {
                    selectedItemId = MyData.id_[i];
                }
            }
            removedItems.add(selectedItemId);
            data.remove(selectedItemPosition);
            adapter.notifyItemRemoved(selectedItemPosition);
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        super.onOptionsItemSelected(item);
        if (item.getItemId() == R.id.add_item) {
           //check if any items to add
            if (removedItems.size() != 0) {
                addRemovedItemToList();
            } else {
                Toast.makeText(this, "Nothing to add", Toast.LENGTH_SHORT).show();
            }
        }
        return true;
    }

    private void addRemovedItemToList() {
        int addItemAtListPosition = 3;
        data.add(addItemAtListPosition, new DataModel(
                MyData.nameArray[removedItems.get(0)],
                MyData.versionArray[removedItems.get(0)],
                MyData.id_[removedItems.get(0)],
                MyData.drawableArray[removedItems.get(0)]
        ));
        adapter.notifyItemInserted(addItemAtListPosition);
        removedItems.remove(0);
    }
}

Phương thức removeItems() được gọi từ hàm lắng nghe sự kiện (listener) để xóa CardView đã được nhấn. ID tương ứng của thẻ đó được lưu trong một mảng nhằm phục vụ cho việc khôi phục sau này.

Để thêm lại thẻ đã xóa, chúng tôi triển khai thêm một phương thức khác có tên là addRemovedItemToList(). Trong phương thức này, thẻ được thêm view đó tại một vị trí xác định trước trong danh sách và ID của nó sẽ được loại bỏ khỏi mảng removedItems. Lớp CustomAdapter đều được thông báo cập nhật trong cả hai trường hợp.

Lớp CustomAdapter.java được định nghĩa như sau:

package com.journaldev.recyclerviewcardview;

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

import java.util.ArrayList;

public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.MyViewHolder> {

    private ArrayList<DataModel> dataSet;

    public static class MyViewHolder extends RecyclerView.ViewHolder {

        TextView textViewName;
        TextView textViewVersion;
        ImageView imageViewIcon;

        public MyViewHolder(View itemView) {
            super(itemView);
            this.textViewName = (TextView) itemView.findViewById(R.id.textViewName);
            this.textViewVersion = (TextView) itemView.findViewById(R.id.textViewVersion);
            this.imageViewIcon = (ImageView) itemView.findViewById(R.id.imageView);
        }
    }

    public CustomAdapter(ArrayList<DataModel> data) {
        this.dataSet = data;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.cards_layout, parent, false);

        view.setOnClickListener(MainActivity.myOnClickListener);

        MyViewHolder myViewHolder = new MyViewHolder(view);
        return myViewHolder;
    }

    @Override
    public void onBindViewHolder(final MyViewHolder holder, final int listPosition) {
        TextView textViewName = holder.textViewName;
        TextView textViewVersion = holder.textViewVersion;
        ImageView imageView = holder.imageViewIcon;

        textViewName.setText(dataSet.get(listPosition).getName());
        textViewVersion.setText(dataSet.get(listPosition).getVersion());
        imageView.setImageResource(dataSet.get(listPosition).getImage());
    }

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

Trong đoạn mã trên, chúng tôi đã triển khai lớp ViewHolder riêng bằng cách kế thừa RecyclerView.ViewHolder. Giao diện được nạp từ tệp cards_layout.xml đã được định nghĩa trước trong thư mục layouts. Trình lắng nghe sự kiện onClickListener trong MainActivity được gán vào view như đoạn sau:

view.setOnClickListener(MainActivity.myOnClickListener);

Một đối tượng ArrayList lưu trữ toàn bộ dữ liệu dưới dạng các đối tượng của lớp DataModel và thêm chúng vào các thẻ tương ứng trong danh sách. Dưới đây là hai lớp DataModel.javaMyData.java, nơi chứa dữ liệu cụ thể của ứng dụng:

package com.journaldev.recyclerviewcardview;

public class DataModel {

    String name;
    String version;
    int id_;
    int image;

    public DataModel(String name, String version, int id_, int image) {
        this.name = name;
        this.version = version;
        this.id_ = id_;
        this.image = image;
    }

    public String getName() {
        return name;
    }

    public String getVersion() {
        return version;
    }

    public int getImage() {
        return image;
    }

    public int getId() {
        return id_;
    }
}

package com.journaldev.recyclerviewcardview;

public class MyData {

    static String[] nameArray = {
        "Cupcake", "Donut", "Eclair", "Froyo", "Gingerbread", "Honeycomb",
        "Ice Cream Sandwich", "JellyBean", "Kitkat", "Lollipop", "Marshmallow"
    };

    static String[] versionArray = {
        "1.5", "1.6", "2.0-2.1", "2.2-2.2.3", "2.3-2.3.7", "3.0-3.2.6",
        "4.0-4.0.4", "4.1-4.3.1", "4.4-4.4.4", "5.0-5.1.1", "6.0-6.0.1"
    };

    static Integer[] drawableArray = {
        R.drawable.cupcake, R.drawable.donut, R.drawable.eclair,
        R.drawable.froyo, R.drawable.gingerbread, R.drawable.honeycomb,
        R.drawable.ics, R.drawable.jellybean, R.drawable.kitkat,
        R.drawable.lollipop, R.drawable.marsh
    };

    static Integer[] id_ = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
}

Dưới đây là kết quả đầu ra của ứng dụng mẫu sử dụng RecyclerViewCardView trên Android:

android-recyclerview-cardview

Như bạn có thể thấy, mục bị xóa luôn được thêm lại tại vị trí thứ ba trong danh sách (vị trí thứ tư tính theo thứ tự người dùng nhìn thấy).

Điều này cũng đánh dấu phần kết thúc của hướng dẫn về RecyclerViewCardView trong Android. Bạn có thể tải toàn bộ Dự án Mẫu Android RecyclerView CardView về từ liên kết bên dưới và thử nghiệm ngay!

Tải xuống Dự án Mẫu Android RecyclerView CardView

0 Bình luận

Đăng nhập để thảo luận

Chuyên mục Hướng dẫn

Tổng hợp các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.

Đăng ký nhận Newsletter

Nhận các nội dung hữu ích mới nhất