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.
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.
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ị trongTextView
.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ạngdrawable
hoặcraw
.
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()
và 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.
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