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.
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óccard_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
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.java
và MyData.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 RecyclerView
và CardView
trên Android:
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ề RecyclerView
và CardView
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