Trang chủHướng dẫnThực hành RecyclerView trong Android với nhiều kiểu View
Android

Thực hành RecyclerView trong Android với nhiều kiểu View

CyStack blog 5 phút để đọc
CyStack blog17/08/2025
Locker Avatar

Chris Pham

Technical Writer

Locker logo social
Reading Time: 5 minutes

Cho đến thời điểm hiện tại, chúng ta mới chỉ hiển thị cùng một loại View trong một RecyclerView. Trong hướng dẫn này, chúng ta sẽ triển khai các layout khác loại bên trong RecyclerView trong Android.

Thực hành Android RecyclerView

RecyclerView trong Android

Việc sử dụng RecyclerView với các layout không đồng nhất thường được áp dụng để hiển thị các tiêu đề mục và nội dung chi tiết (hai loại này yêu cầu layout khác nhau, do đó cần loại View khác nhau). Bên cạnh đó, mô hình này còn được sử dụng trong các ứng dụng dạng Newsfeed (như Facebook, Instagram), ở đây mỗi loại dữ liệu được hiển thị với một View khác nhau. Ví dụ: văn bản, hình ảnh, gif, video…

Mỗi loại yêu cầu một layout riêng bên trong RecyclerView. Ngoài ra, nó còn được sử dụng trong NavigationDrawer để tách phần Header ra khỏi phần còn lại của mục điều hướng. Để không mất thời gian thêm, chúng ta sẽ tiến hành triển khai ngay trong ứng dụng.

Cấu trúc dự án Android RecyclerView sử dụng nhiều ViewType

Chúng ta sẽ triển khai ba loại View (văn bản, hình ảnh, âm thanh), mỗi loại được inflate với một layout riêng biệt. Mỗi layout này sẽ được xử lý riêng trong lớp Adapter.

android-recycler-view-multiple-view-type-project

Mã nguồn

Tệp activity_main.xml chứa một CoordinatorLayout là phần tử gốc, và RecyclerView được đặt làm phần tử con.

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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>"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.journaldev.recyclerviewmultipleviewtype.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

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

</android.support.design.widget.CoordinatorLayout>

Lưu ý dòng app:layout_behavior="@string/appbar_scrolling_view_behavior" trong phần tử RecyclerView. Nếu bỏ dòng này, RecyclerView sẽ cuộn qua toàn bộ màn hình, dẫn đến việc nó bị đè lên AppBarLayout.

Lớp Model.java chứa dữ liệu được sử dụng để hiển thị trong Adapter như sau:

public class Model {

    public static final int TEXT_TYPE = 0;
    public static final int IMAGE_TYPE = 1;
    public static final int AUDIO_TYPE = 2;

    public int type;
    public int data;
    public String text;

    public Model(int type, String text, int data) {
        this.type = type;
        this.data = data;
        this.text = text;
    }
}

Lớp này gồm ba loại dữ liệu:

  • int type chứa hằng số biểu thị loại View.
  • String text chứa chuỗi sẽ được hiển thị trong TextView.
  • int data là biến dùng để lưu dữ liệu tương ứng sẽ được hiển thị. Trong trường hợp này, nó lưu tài nguyên dạng drawable hoặc raw.

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

package com.journaldev.recyclerviewmultipleviewtype;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.OrientationHelper;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        ArrayList<Model> list = new ArrayList<>();
        list.add(new Model(Model.TEXT_TYPE, "Hello. This is the Text-only View Type. Nice to meet you", 0));
        list.add(new Model(Model.IMAGE_TYPE, "Hi. I display a cool image too besides the omnipresent TextView.", R.drawable.wtc));
        list.add(new Model(Model.AUDIO_TYPE, "Hey. Pressing the FAB button will playback an audio file on loop.", R.raw.sound));
        list.add(new Model(Model.IMAGE_TYPE, "Hi again. Another cool image here. Which one is better?", R.drawable.snow));

        MultiViewTypeAdapter adapter = new MultiViewTypeAdapter(list, this);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, OrientationHelper.VERTICAL, false);

        RecyclerView mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        mRecyclerView.setLayoutManager(linearLayoutManager);
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
        mRecyclerView.setAdapter(adapter);
    }
}

Tài nguyên R.raw.sound là tệp sound.mp3 sẽ được phát trong loại View âm thanh.

Lớp Adapter dành cho RecyclerView sẽ ghi đè ba phương thức chính:

  • getItemViewType()
  • onCreateViewHolder()
  • onBindViewHolder()

Trong phương thức getItemViewType(), chúng ta sẽ sử dụng cấu trúc switch để trả về giá trị viewType tương ứng. Biến viewType này là biến nội bộ của lớp Adapter, được sử dụng trong các phương thức onCreateViewHolder()onBindViewHolder() để inflate và gán dữ liệu cho các layout đã ánh xạ trước đó.

Trước khi đi vào triển khai lớp Adapter, chúng ta sẽ xem xét các loại layout đã được định nghĩa tương ứng với từng loại View. (text_type.xml…)

text_type.xml

<android.support.v7.widget.CardView ... >
    <TextView
        android:id="@+id/type"
        ... />
</android.support.v7.widget.CardView>

image_type.xml

<android.support.v7.widget.CardView ... >
    <LinearLayout ... >
        <TextView
            android:id="@+id/type"
            ... />
        <ImageView
            android:id="@+id/background"
            android:src="@drawable/snow"
            ... />
    </LinearLayout>
</android.support.v7.widget.CardView>

audio_type.xml

<android.support.v7.widget.CardView ... >
    <RelativeLayout ... >
        <TextView
            android:id="@+id/type"
            ... />
        <android.support.design.widget.FloatingActionButton
            android:id="@+id/fab"
            android:src="@drawable/volume"
            ... />
    </RelativeLayout>
</android.support.v7.widget.CardView>

Lưu ý: Thêm phụ thuộc sau cho CardView vào tệp build.gradle:

compile 'com.android.support:cardview-v7:24.2.0'

Đảm bảo rằng phiên bản của dependency appcompat phải khớp với cardview. (Ở đây tôi sử dụng phiên bản 24.2.0, bạn có thể đang dùng phiên bản khác.) Chúng ta sẽ tạo ba lớp ViewHolder riêng biệt cho từng layout như trình bày trong lớp MultiViewTypeAdapter.java dưới đây.

package com.journaldev.recyclerviewmultipleviewtype;

import android.content.Context;
import android.media.MediaPlayer;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.widget.CardView;
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;

/**
 * Created by anupamchugh on 09/02/16.
 */
public class MultiViewTypeAdapter extends RecyclerView.Adapter {

    private ArrayList<Model> dataSet;
    Context mContext;
    int total_types;
    MediaPlayer mPlayer;
    private boolean fabStateVolume = false;

    public static class TextTypeViewHolder extends RecyclerView.ViewHolder {

        TextView txtType;
        CardView cardView;

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

            this.txtType = (TextView) itemView.findViewById(R.id.type);
            this.cardView = (CardView) itemView.findViewById(R.id.card_view);
        }
    }

    public static class ImageTypeViewHolder extends RecyclerView.ViewHolder {

        TextView txtType;
        ImageView image;

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

            this.txtType = (TextView) itemView.findViewById(R.id.type);
            this.image = (ImageView) itemView.findViewById(R.id.background);
        }
    }

    public static class AudioTypeViewHolder extends RecyclerView.ViewHolder {

        TextView txtType;
        FloatingActionButton fab;

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

            this.txtType = (TextView) itemView.findViewById(R.id.type);
            this.fab = (FloatingActionButton) itemView.findViewById(R.id.fab);
        }
    }

    public MultiViewTypeAdapter(ArrayList<Model> data, Context context) {
        this.dataSet = data;
        this.mContext = context;
        total_types = dataSet.size();
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View view;
        switch (viewType) {
            case Model.TEXT_TYPE:
                view = LayoutInflater.from(parent.getContext()).inflate(R.layout.text_type, parent, false);
                return new TextTypeViewHolder(view);
            case Model.IMAGE_TYPE:
                view = LayoutInflater.from(parent.getContext()).inflate(R.layout.image_type, parent, false);
                return new ImageTypeViewHolder(view);
            case Model.AUDIO_TYPE:
                view = LayoutInflater.from(parent.getContext()).inflate(R.layout.audio_type, parent, false);
                return new AudioTypeViewHolder(view);
        }
        return null;
    }

    @Override
    public int getItemViewType(int position) {

        switch (dataSet.get(position).type) {
            case 0:
                return Model.TEXT_TYPE;
            case 1:
                return Model.IMAGE_TYPE;
            case 2:
                return Model.AUDIO_TYPE;
            default:
                return -1;
        }
    }

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

        Model object = dataSet.get(listPosition);
        if (object != null) {
            switch (object.type) {
                case Model.TEXT_TYPE:
                    ((TextTypeViewHolder) holder).txtType.setText(object.text);
                    break;
                case Model.IMAGE_TYPE:
                    ((ImageTypeViewHolder) holder).txtType.setText(object.text);
                    ((ImageTypeViewHolder) holder).image.setImageResource(object.data);
                    break;
                case Model.AUDIO_TYPE:
                    ((AudioTypeViewHolder) holder).txtType.setText(object.text);

                    ((AudioTypeViewHolder) holder).fab.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {

                            if (fabStateVolume) {
                                if (mPlayer.isPlaying()) {
                                    mPlayer.stop();
                                }
                                ((AudioTypeViewHolder) holder).fab.setImageResource(R.drawable.volume);
                                fabStateVolume = false;

                            } else {
                                mPlayer = MediaPlayer.create(mContext, R.raw.sound);
                                mPlayer.setLooping(true);
                                mPlayer.start();
                                ((AudioTypeViewHolder) holder).fab.setImageResource(R.drawable.mute);
                                fabStateVolume = true;
                            }
                        }
                    });
                    break;
            }
        }
    }

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

Trong đoạn mã trên, chúng ta sử dụng một biến boolean toàn cục để lưu trạng thái của nút âm lượng, giá trị này được chuyển đổi mỗi khi người dùng nhấn nút (đồng thời thay đổi hình ảnh biểu tượng của FloatingActionButton). Kết quả đầu ra của ứng dụng được minh họa bên dưới.

android recyclerview type project

Phần này cũng kết thúc hướng dẫn. Bạn có thể tải toàn bộ dự án Android RecyclerViewMultipleViewType về theo liên kết dưới đây.

Download Android RecyclerView Multiple ViewType Project

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