Tổng quan về RecyclerView trong Android
RecyclerView nó dùng để xây dựng UI gần giống với hoạt động của ListView, GridView. Nó biểu diễn
danh sách với nhiều cách trình bày khác nhau, theo chiều đứng, chiều ngang. Nó là thư viện hỗ trợ tốt hơn ListView rất nhiều nhất ra sử dụng trong
CoordinatorLayout
để tương tác với các thành phần UI khác.
RecyclerView cũng rất phù hợp khi dữ liệu hiện thị thu thập trong quá trình chạy ứng dụng, như căn cứ vào tương tác người dùng, vào dữ liệu tải về ...
Tích hợp vào build.gradle
thư viện như sau để sử dụng RecyclerView
implementation 'com.android.support:appcompat-v7:26.1.0' implementation 'com.android.support:recyclerview-v7:26.1.0' implementation 'com.android.support:design:26.1.0'
Khi dùng đến RecylerView thì bạn cũng cần làm việc với:
- RecyclerView.Adapter Quản lý dữ liệu và cập nhật dữ liệu cần hiện thị vào View (phần tử hiện thị trong RecyclerView)
- RecyclerView.LayoutManager Lớp mà để quy định cách mà vị trí các phần tử trong RecyclerView hiện thị, có thể sử dụng các lớp kế thừa LinearLayoutManager, GridLayoutManager
- RecyclerView.ItemAnimator Lớp để xây dựng cách thực hoạt hình (động) cho các sự kiện trên phần tử hiện thị, như hiệu ứng khi thêm phần tử vào, xóa phần tử khỏi RecyclerView
- RecyclerView.Viewholder lớp dùng để gán / cập nhật dữ liệu vào các phần tử.
Ví dụ đầu tiên sử dụng RecycleView hiện thị danh sách
Giả thiết cần hiện thị danh các học sinh một lớp. Thông tin hiện thị có Tên / Năm sinh, phần tử hiện thị có thêm nút bấm để xem chi tiết
Mô hình biểu diễn dữ liệu
Đầu tiên bạn cần xây dựng mô hình biểu diễn dữ liệu một học sinh (Mode), có thể xây dựng lớp đó như sau:
public class Student { private String mName; private int birthYear; public void setmName(String mName) { this.mName = mName; } public void setBirthYear(int birthYear) { this.birthYear = birthYear; } public Student(String mName, int birthYear) { this.mName = mName; this.birthYear = birthYear; } public String getmName() { return mName; } public int getBirthYear() { return birthYear; } }
RecyclerView trong Layout
Tiếp theo trong Layout XML (ví dụ activity_recycler_view_example_active.xml) thêm RecyclerView dạng sau:
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="false" <android.support.v7.widget.RecyclerView android:id="@+id/studentsList" android:layout_width="match_parent" android:layout_height="match_parent"> </android.support.v7.widget.RecyclerView> </android.support.design.widget.CoordinatorLayout>
Tạo ra layout biểu diễn một sinh viên trong danh sách
Ví dụ tạo ra layout có tên student_item.xml và cập nhật nội dung dạng sau:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="10dp" android:paddingBottom="10dp" > <ImageView android:layout_width="60dp" android:layout_height="match_parent" android:src="@android:drawable/ic_menu_gallery" /> <LinearLayout android:orientation="vertical" android:layout_width="0dp" android:paddingLeft="5dp" android:layout_weight="1" android:layout_height="match_parent"> <TextView android:id="@+id/studentname" android:layout_width="match_parent" android:layout_height="wrap_content" android:textStyle="bold" android:textSize="16sp" android:text="dsafdsfdsfasf" /> <TextView android:id="@+id/birthyear" android:layout_width="match_parent" android:layout_height="wrap_content" android:textStyle="italic" android:textSize="14sp" android:text="dsfadsfdsf" /> </LinearLayout> <Button android:id="@+id/detail_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="16dp" android:paddingRight="16dp" android:textSize="10sp" android:text="Chi tiết" /> </LinearLayout>
Viết Adapter và ViewHolder
Tạo ra một Adapter kế thừa từ RecyclerView.Adapter ví dụ đặt tên là StudentAdapter, chức năng của nó là để RecycleView tương tác với dữ liệu cần hiện thị. Cụ thể ta sẽ quá tải các phương thức trong Adapter
- getItemCount() : cho biết số phần tử của dữ liệu
- onCreateViewHolder : tạo ra đối tượng ViewHolder, trong nó chứa View hiện thị dữ liệu
- onBindViewHolder : chuyển dữ liệu phần tử vào ViewHolder
StudentAdapter cũng sử dụng một lớp tên ViewHolder kế thừa RecyclerView.ViewHolder, nó nắm giữ View hiện thị dữ liệu
StudentAdapter và ViewHolder trình bày với code như sau:
public class StudentAdapter extends RecyclerView.Adapter{ //Dữ liệu hiện thị là danh sách sinh viên private List mStutents; // Lưu Context để dễ dàng truy cập private Context mContext; public StudentAdapter(List _student, Context mContext) { this.mStutents = _student; this.mContext = mContext; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { Context context = parent.getContext(); LayoutInflater inflater = LayoutInflater.from(context); // Nạp layout cho View biểu diễn phần tử sinh viên View studentView = inflater.inflate(R.layout.student_item, parent, false); ViewHolder viewHolder = new ViewHolder(studentView); return viewHolder; } @Override public void onBindViewHolder(ViewHolder holder, int position) { Student student = mStutents.get(position); holder.studentname.setText(student.getmName()); holder.birthyear.setText(student.getBirthYear()+""); } @Override public int getItemCount() { return mStutents.size(); } /** * Lớp nắm giữ cấu trúc view */ public class ViewHolder extends RecyclerView.ViewHolder { private View itemview; public TextView studentname; public TextView birthyear; public Button detail_button; public ViewHolder(View itemView) { super(itemView); itemview = itemView; studentname = itemView.findViewById(R.id.studentname); birthyear = itemView.findViewById(R.id.birthyear); detail_button = itemView.findViewById(R.id.detail_button); //Xử lý khi nút Chi tiết được bấm detail_button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(view.getContext(), studentname.getText() +" | " + " Demo function", Toast.LENGTH_SHORT) .show(); } }); } } }
Code thiết lập RecyclerView - Thiết lập LayoutManager và Adapter
Giờ là lúc sử dụng các kết quả trên, trong onCreate của Activity có thể thêm mã như sau:
RecyclerView recyclerView; StudentAdapter adapter; ArrayList<Student> students; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_recycler_view_example_active); recyclerView = findViewById(R.id.studentsList); students = new ArrayList<Student>(); //Tự phát sinh 50 dữ liệu mẫu for (int i = 1; i <= 50; i++) { students.add(new Student("Student Name"+i , 1995 + (i % 2))); } adapter = new StudentAdapter(students, this); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); recyclerView.setAdapter(adapter); recyclerView.setLayoutManager(linearLayoutManager); }
Chạy xem kết quả ví dụ
Sử dụng Adapter thay đổi phần tử trong RecyclerView
Cách duy nhất để thêm bớt phần tử trong RecyclerView là phải thông qua Adapter của nó, bạn thay đổi dữ liệu do Adapter quản lý rồi thông báo lại cho RecylerView thay đổi đó.
Các phương thức Adapter thông báo đến RecylerView có thể sử dụng như:
Phương thức | Sử dụng |
---|---|
notifyItemChanged(int pos) |
Cho biết dự liệu phần tử vị trí pos thay đổi. |
notifyItemInserted(int pos) |
Thông báo Phần tử ở vị trí pos mới thêm vào |
notifyItemRemoved(int pos) |
Thông báo Phần tử ỏ vị trí pos bị loại bỏ |
notifyDataSetChanged() |
Thông báo toàn bộ dữ liệu thay đổi |
notifyDataSetChanged() |
Thông báo toàn bộ dữ liệu thay đổi |
Để mô tả hoạt động của các phương thức trên, có thể sửa ví dụ trên như sau:
Tạo một menu cho Activty
Trước tiên bạn có thể tạo ra XML menu\menu.xml
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/menu_modify_3" android:title="Thay đổi phần tử số 3" /> <item android:id="@+id/menu_insert_2" android:title="Chèn mới vị trí số 2" /> <item android:id="@+id/menu_remove_first" android:title="Xóa phần tử đầu tiên" /> <item android:id="@+id/menu_new_7" android:title="Danh sách 7 phần tử mới" /> </menu>
Trong Activity quá tải onCreateOptionsMenu, onOptionsItemSelected
@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_modify_3: //Sửa đội phần tử số 3 Student student = students.get(2); student.setmName("TÊN MỚI SỐ 3: " +Calendar.getInstance().getTimeInMillis()); student.setBirthYear(2000); adapter.notifyItemChanged(2); return true; case R.id.menu_insert_2: //Thêm một sinh viên mới vào vị trí số 2 Student newStudent = new Student("SINH VIÊN 2:" + Calendar.getInstance().getTimeInMillis(), 1990); students.add(1, newStudent); adapter.notifyItemInserted(1); return true; case R.id.menu_remove_first: //Xóa sinh viên ở vị trí đầu tiên students.remove(0); adapter.notifyItemRemoved(0); return true; case R.id.menu_new_7: //Danh sách 7 sinh viên mới students.clear();//Xóa bỏ danh sách cũ //Thêm 7 sinh viên mới for (int i = 1; i <=7; i++) students.add(new Student("SV Mới "+ i, 1990+i)); //Thông báo toàn bộ dữ liệu thay đổi adapter.notifyDataSetChanged(); return true; default:break; } return super.onOptionsItemSelected(item); }
Chạy thử xem
Lưu ý khi bạn thêm bớt một số phần tử vào, để hiệu suất của ứng dụng đảm bảo cần thiết mới gọi notifyDataSetChanged()
, mà hãy xem
các phương thức chuyên biệt khác như:
Phương thức | Sử dụng |
---|---|
notifyItemRangeChanged(int positionStart, int itemCount) |
Cho biết có số lượng phần tử itemCount tính từ phần tử vị trí positionStart thay đổi. |
notifyItemRangeInserted(int positionStart, int itemCount) |
Cho biết có itemCount phần tử được chèn vào, bắt đầu tự vị trí positionStart . |
notifyItemRangeRemoved(int positionStart, int itemCount) |
Cho biết có itemCount phần tử được loại bỏ, bắt đầu tự vị trí positionStart . |
Ví dụ thêm 7 phần tử vào cuối danh sách
int positionStart = students.size(); //Thêm 7 sinh viên mới for (int i = 1; i <=7; i++) students.add(new Student("SV Mới "+ i, 1990+i)); adapter.notifyItemRangeInserted(positionStart, students.size());
Dùng Adapter để tùy biến nhiều kiểu hiện thị cho phần tử RecyclerView
Phần này hướng dẫn làm sao để mỗi phần tử trong RecyclerView có thể tùy biến hiện thị (layout phần tử) khác nhau, ví dụ căn cứ vào dữ liệu của phần tử, căn cứ vào vị trị phần tử ...
Ở ví dụ trên, ta thấy các phần tử đều nạp res\layout\student_item.xml
để trình bày dòng dữ liệu, giờ giả sử
muốn các phần tử ở vị trí 0, 3, 6, 9 ... thì lại dụng layout khác thì làm thế nào
Đầu tiên tạo ra res\layout\student_item_2.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="10dp" android:paddingBottom="10dp" android:background="@android:color/holo_orange_light" > <Button android:id="@+id/detail_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="16dp" android:paddingRight="16dp" android:textSize="10sp" android:text="Chi tiết" /> <LinearLayout android:orientation="vertical" android:layout_width="0dp" android:paddingLeft="5dp" android:layout_weight="1" android:layout_height="match_parent"> <TextView android:id="@+id/studentname" android:layout_width="match_parent" android:layout_height="wrap_content" android:textStyle="bold" android:textSize="16sp" android:text="dsafdsfdsfasf" /> <TextView android:id="@+id/birthyear" android:layout_width="match_parent" android:layout_height="wrap_content" android:textStyle="italic" android:textSize="14sp" android:text="dsfadsfdsf" /> </LinearLayout> <ImageView android:layout_width="60dp" android:layout_height="match_parent" android:src="@drawable/flower" /> </LinearLayout>
Tiếp theo sửa đổi StudentAdapter
, chú ý có quá tải (overrided) thêm getItemViewType
, nội dung chi tiết như sau (xem giải thích
trong code), chú ý các chỗ đánh dấu thêm mới so với code ban đầu
public class StudentAdapter extends RecyclerView.Adapter{ //Dữ liệu hiện thị là danh sách sinh viên private List<Student> mStutents; // Lưu Context để dễ dàng truy cập private Context mContext; //Hằng số hai kiểu hiện thị phần tử public static final int TYPE1 = 0; public static final int TYPE2 = 1; public StudentAdapter(List<Student> _student, Context mContext) { this.mStutents = _student; this.mContext = mContext; } /** * Những phần tử chia hết cho 3 có kiểu 1, còn lại kiểu 0 */ @Override public int getItemViewType(int position) { if (position % 3 == 0) return TYPE2; else return TYPE1; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { Context context = parent.getContext(); LayoutInflater inflater = LayoutInflater.from(context); // Nạp layout cho View biểu diễn phần tử sinh viên //Tùy thuộc viewType của phần tử View studentView = null; switch (viewType) { case TYPE1: studentView = inflater.inflate(R.layout.student_item, parent, false); break; case TYPE2: studentView = inflater.inflate(R.layout.student_item_2, parent, false); break; } ViewHolder viewHolder = new ViewHolder(studentView); return viewHolder; } @Override public void onBindViewHolder(ViewHolder holder, int position) { Student student = mStutents.get(position); holder.studentname.setText(student.getmName()); holder.birthyear.setText(student.getBirthYear()+""); } @Override public int getItemCount() { return mStutents.size(); } /** * Lớp nắm giữ cấu trúc view */ public class ViewHolder extends RecyclerView.ViewHolder { private View itemview; public TextView studentname; public TextView birthyear; public Button detail_button; public ViewHolder(View itemView) { super(itemView); itemview = itemView; studentname = itemView.findViewById(R.id.studentname); birthyear = itemView.findViewById(R.id.birthyear); detail_button = itemView.findViewById(R.id.detail_button); //Xử lý khi nút Chi tiết được bấm detail_button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(view.getContext(), studentname.getText() +" | " + " Demo function", Toast.LENGTH_SHORT).show(); } }); } } }
Đó là tùy biến chỉ căn cứ vào vị trí phần tử, bạn hoàn toàn làm tương tự nhưng hiện thị phần tử căn cứ vào dữ liệu phần tử, vào các tác động khác nhau của người dùng.
Ngoài ra với mỗi phần tử khác nhau bạn cũng có thể tạo ra nhiều loại ViewHolder khác nhau, nghĩa là có thể định nghĩa nhiều loại lớp kế thừa từ RecyclerViewHoder
,
tùy vào phần tử mà dùng ViewHolder nào ...
Các danh sách lớn
Trong một số trường hợp bạn có danh sách phức lớn, mà thực hiện các thao tác phức tạp trên nó (như sắp xếp lại ...) không dễ gì
biết phần tử nào cần thông báo cho RecyclerView
biết mà cập nhật hiện thị cho đúng, lúc đó nếu bạn gọi notifyDataSetChanged()
để cho biết toàn bộ dữ liệu đã đổi thì hiệu suất hoạt động của ứng dụng rất tệ.
May mắn là có một lớp tiện ích tên là DiffUtil giúp bạn tính toán xem sự khác nhau giữa danh sách ban đầu và sau hiệu quả hơn. Lớp này tham khảo tại DiffUtil (android.support.v7.util.DiffUtil)
Để sử dụng, bạn cần kế thừa DiffUtil.Callback
nhận danh sách cũ, mới để so sánh và đưa ra kết quả
Ví dụ, xây dựng lớp tiện tích để chuyên tính toán sự thay đổi giữa hai danh sách StudentDiffCallback
public class StudentDiffCallback extends DiffUtil.Callback { private List<Student> oldList; private List<Student> newList; public StudentDiffCallback(List<Student> oldList, List<Student> newList) { this.oldList = oldList; this.newList = newList; } @Override public int getOldListSize() { return oldList.size(); } @Override public int getNewListSize() { return newList.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { /** * Nhớ thêm mỗi sinh viên có một mã số khác nhau, và mã số đó * lấy bằng hàm getStudentId() */ return oldList.get(oldItemPosition).getStudentId() == newList.get(newItemPosition).getStudentId(); } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { //Trả về true nếu dữ liệu giống nhau ở vị mới / cũ //Ở đây so sánh tên sinh viên return oldList.get(oldItemPosition).getmName() == newList.get(newItemPosition).getmName(); } }
Trong StudentAdapter thêm phương thức để cập nhật danh sách mới có sử dụng đến StudentDiffCallback tính toán và thông báo cho RecyclerView biết
public void UpdateListStudent(List<Student> listStudent) { //Tính toán sự thay đổi giữa cũ và mới final StudentDiffCallback diffCallback = new StudentDiffCallback(this.mStutents, listStudent); final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback); // Thay danh sách cũ bằng mới this.mStutents.clear(); this.mStutents.addAll(listStudent); //Thông báo cho RecyclerView biết phần tử nào cần cập nhật hiện thị diffResult.dispatchUpdatesTo(this); }
Bằng cách sử dụng code như trên, đối với cách danh sách lớn hàng ngàn phần tử thì hoạt động của RecyclerView sẽ mượt hơn so với gọi notifyDataSetChanged
Cuộn trong RecyclerView
Có một số phương thức của RecyclerView bạn có thể dùng tới, cách ứng xử của nó còn phụ thuộc thêm vào loại LayoutManager sử dụng, các phương thức có thể dùng đến như:
Phương thức | Áp dụng |
---|---|
scrollToPosition(int position) |
Cuộn lập tức đến phần tử position |
smoothScrollToPosition(int position) |
Cuộn đến phần tử position (trôi đến phần tử) |
scrollBy(int x, int y) , smoothScrollBy(int dx, int dy) |
Cuốn từ trạng thái hiện tại thêm một đoạn x, y theo phương dọc và ngang (lưu ý ảnh có tác động theo chiều X, Y không còn phụ thuộc loại LayoutManager) trình bày sau |
scrollTo(int x, int y) |
Cuộn đến tọa độ x, y |
Ví dụ sau chi cập nhật lại phần tử đầu tiên, bạn muốn cuộn đến nó:
adapter.notifyItemChanged(0); recyclerView.scrollToPosition(0);
Ví dụ sau chi chèn thêm một phần tử vào cuối, bạn muốn cuộn đến nó:
adapter.notifyItemChanged(students.size() -1); recyclerView.scrollToPosition(students.size() -1);
Nếu muốn trượt mềm mại dùng smoothScrollToPosition()
Tải thêm phần tử danh sách khi cuộn tới cuối
Để làm điều này, nguyên tắc thêm Listenr cho sự kiện cuộn trong RecyclerView (addOnScrollListener
), khi đó mỗi khi cuộn sẽ
kiểm tra xem nếu tới phần tử cuối cùng thì tiến hành cập nhật thêm dữ liệu (ví dụ tải thêm). Code có sẵn cho việc này,
bạn hãy tải EndlessRecyclerViewScrollListener.java, thêm vào dự án của bạn.
Code sử dụng sẽ có dạng
EndlessRecyclerViewScrollListener endlessRecyclerViewScrollListener
= new EndlessRecyclerViewScrollListener(linearLayoutManager) {
@Override
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Toast.makeText(view.getContext(), "Loading More ...",
Toast.LENGTH_SHORT).show();
List<Student> list = new ArrayList<Student>();
for (int i = 0; i <= 5; i++) {
list.add(new Student("Mới "+ i, 1988));
}
students.addAll(list);
adapter.notifyDataSetChanged();
}
}, 1500);
}
};
//Thêm Listener vào
recyclerView.addOnScrollListener(endlessRecyclerViewScrollListener);
Nhận biết vuốt mạnh lên, xuống để làm mới nội dung
Nguyên tắc là lắng nghe sự kiện vuốt vẩy trong RecyclerView bằng cách thiết lập listener cho sự kiện này với phương thức setOnFlingListener. Listener này xây dựng bằng lớp kế thừa RecyclerView.OnFlingListener. Ở đây có mẫu bạn có thể tải về dùng luôn RecyclerViewSwipeListener.java
Trong lớp tải về, bạn muốn bắt sự kiện nào thì chỉ việc quá tải phương thức tương ứng:
onSwipeRight
vuốt sang phảionSwipeLeft
vuốt tráionSwipeUp
vuốt lênonSwipeDown
vuốt xuống
Nhớ là các thao tác vuốt trên thuộc về nhận biết cử chỉ fling - nghĩ là sự kiện onSwipeRight chỉ xảy ra khi người dùng vuốt nhanh từ trái sang phải.
Ví dụ sử dùng RecyclerViewSwipeListener
để nhận biết vuốt lên, xuống
RecyclerViewSwipeListener recyclerViewSwipeListener = new RecyclerViewSwipeListener(true) { @Override public void onSwipeUp() { Toast.makeText(getApplicationContext(), "Vuốt lên", Toast.LENGTH_SHORT).show(); } @Override public void onSwipeDown() { Toast.makeText(getApplicationContext(), "Vuốt xuống - đang chèn thêm", Toast.LENGTH_SHORT).show(); students.add(0, new Student("Mới chèn vào", 1990)); adapter.notifyItemChanged(0); } }; recyclerView.setOnFlingListener(recyclerViewSwipeListener);
Bạn căn cứ vào mã nguồn của RecyclerViewSwipeListener
có thể phát triển để có được các trường hợp riêng của bạn.
Một số tùy biến với RecyclerView
Ở phần này là một số thiết lập, tùy biến chi tiết hơn áp dụng với RecyclerView
Thiết lập tối ưu danh sách cố định
Nếu danh sách bạn hiện thị không có nhu cầu thêm / bớt hãy thiết lập để cuộn mượt hơn nhiều
recyclerView.setHasFixedSize(true);
Sử dụng LinearLayoutManager
Ở phần trên bạn đã sử dụng LinearLayoutManager, ở đây nó chi tiết hơn một chút. LinearLayoutManager - cung cấp hai loại các phần từ xếp theo hàng thẳng đứng hoặc nằm ngang. Ví dụ, tạo LinearLayoutManager và áp dụng vào RecylerView
Context context = this; int orientation = LinearLayoutManager.HORIZONTAL; //Cuộn ngang // int orientation = LinearLayoutManager.VERTICAL; //cuốn đứng boolean reverse = false; //true thì bắt đầu từ phần tử cuối LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); layoutManager.scrollToPosition(0);//Thiết lập phần tử mặc định nếu muốn // Gắn vào RecylerView recyclerView.setLayoutManager(layoutManager);
Một số phương thức trong LinearLayoutManager
Phương thức | Áp dụng |
---|---|
findFirstCompletelyVisibleItemPosition() findLastCompletelyVisibleItemPosition() |
Trả về vị trí của phần tử thứ nhất/cuối cùng đang xuất hiện trọn vẹn trong View, RecyclerView.NO_POSITION nếu không thấy |
findFirstVisibleItemPosition() findLastVisibleItemPosition() |
Trả về vị trí của phần tử thứ nhất/cuối cùng đang xuất hiện trong View, RecyclerView.NO_POSITION nếu không thấy |
findViewByPosition(int position) |
Trả về View trình diễn phần tử thứ position của Adapter, trả về null nếu phần tử đó chưa hiện thị trong View |
scrollToPosition(int position) |
Cuộn tới phần tử thứ position trong Adapter |
Sử dụng GridLayoutManager/StaggeredGridLayoutManager
Thiết lập RecyclerView hiện thị các phần tử ở dạng lưới. Có thể chọn lưới vuốt đứng, với số cột hiện thị cố định hoặc lưới vuốt ngang với số dòng cố định. Ví dụ:
int spanCount = 2;//Số cột nếu thiết lập lưới đứng, số dòng nếu lưới ngang int orientation = GridLayoutManager.VERTICAL;//Lưới ngang //int orientation = GridLayoutManager.HORIZONTAL;//Lưới đứng GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 2); gridLayoutManager.setOrientation(GridLayoutManager.VERTICAL); recyclerView.setLayoutManager(gridLayoutManager);
Về StaggeredGridLayoutManager
thì nó cũng là dạng lưới, và cách code giống với GridLayoutManager
, nhưng có
một chút khác biệt ở hình dưới
Ví dụ
StaggeredGridLayoutManager gridLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); recyclerView.setLayoutManager(gridLayoutManager);
Trang trí thêm cho RecyclerView
Các phần tử được vẽ trong RecyclerView, có thể thêm các trang trí (vẽ thêm vào) ví dụ sau mỗi phần tử có đường kẻ ngang phía dưới, phía trên, hay khoảng cách giữa các phần tử .... Để làm được điều này bạn cần xây dựng các lớp kế thừa tử RecyclerView.ItemDecoration
Sử dụng DividerItemDecoration
Đây là lớp kế thừa RecyclerView.ItemDecoration
thư viện có sẵn để kẻ ngang hoặc đứng giữa các phần tử. Ví dụ:
//Chèn một kẻ ngang giữa các phần tử DividerItemDecoration dividerHorizontal = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL); recyclerView.addItemDecoration(dividerHorizontal); //Chèn một kẻ đứng giữa các phần tử DividerItemDecoration dividerVertical = new DividerItemDecoration(this, DividerItemDecoration.HORIZONTAL); recyclerView.addItemDecoration(dividerVertical);
DividerItemDecoration có phương thức setDrawable để bạn thiết lập ảnh riêng dùng để vẽ đường kẻ nếu muốn. Bạn có thể lấy ảnh làm Drawable hoặc tạo ra từ XML trình bày như XML Devider
Ví dụ, tạo ra một XML Drawable res\drawable\devider_red.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <size android:width="1000px" android:height="30px" /> <gradient android:startColor="#000000" android:centerColor="#beacac" android:endColor="#e63232" android:angle="0" /> </shape>
Sử dụng để vẽ đường ngang
//Chèn một kẻ ngang giữa các phần tử DividerItemDecoration dividerHorizontal = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL); dividerHorizontal. setDrawable(ContextCompat.getDrawable(this, R.drawable.devider_red)); recyclerView.addItemDecoration(dividerHorizontal);
Nên nhớ có thể gọi nhiều lần addItemDecoration
để thêm nhiều trang trí cho mỗi mục
Xây dựng ItemDecoration - thiết lập khoảng cách giữa các phần tử với nhau
public class SpacesItemDecoration extends RecyclerView.ItemDecoration { private final int mSpace; public SpacesItemDecoration(int space) { this.mSpace = space; } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { outRect.left = mSpace; outRect.right = mSpace; outRect.bottom = mSpace; // Add top margin only for the first item to avoid double space between items if (parent.getChildAdapterPosition(view) == 0) outRect.top = mSpace; } }
Ví dụ sử dụng:
recyclerView.addItemDecoration(new SpacesItemDecoration(10));
Các hiệu ứng hoạt họa cho phần tử
RecyclerView cung cấp các hiệu ứng trên phần tử khi phần tử đó trượt vào, di chuyển, bị xóa bằng cách sử dụng RecyclerView.ItemAnimator
,
hoặc SimpleItemAnimator
, trong đó bạn quá tải các phương thức và gán các hiệu ứng cần thiết vào ViewHolder
Xây dựng các hiệu ứng này không đơn giản, tuy nhiên một thư viện tốt có sẵn cho bạn dùng là recyclerview-animators
Làm theo hướng dẫn, bàn tích hợp thư viện vào dự án
dependencies { compile 'jp.wasabeef:recyclerview-animators:2.3.0' }
Sau đó trong sanh sách các loại hiệu ứng thích sử dụng cái nào thì thêm hiệu ứng đó.
Các hiệu ứng thực hiện với phần tử thêm vào bằng cách gọi recyclerView.setItemAnimator(): như LandingAnimator
, FadeInAnimator
...
Hiệu ứng xảy ra khi có các thao tác thêm/xóa phần tử.
Ví dụ:
recyclerView.setItemAnimator(new ScaleInAnimator());
Các hiệu ứng thực hiện trên Adapter như: AlphaInAnimationAdapter
, ScaleInAnimationAdapter
, SlideInBottomAnimationAdapter
...
thực hiện bằng code trước khi thiết lập Adapter cho RecycleView, ví dụ:
adapter = new StudentAdapter(students, this); recyclerView.setAdapter(new ScaleInAnimationAdapter(adapter));
Bắt sự kiện Touch trên phần tử của RecyclerView
Bạn có thể bắt sự kiện Touch trên phần tử với RecyclerView thông qua phương thức
public void addOnItemTouchListener(RecyclerView.OnItemTouchListener listener);
Trong đó listener
là đối tượng lớp triển khai từ giao diện (interface) RecyclerView.OnItemTouchListener.
Ví dụ:
RecyclerView.OnItemTouchListener listener = new RecyclerView.OnItemTouchListener() { @Override public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { //Phương thức này được gọi bất kỳ khi nào bắt đầu Touch //trên phần tử (TOUCH DOWN) //Nếu phương thức trả về true thì phương thức onTouchEvent //sẽ được gọi cho các sự kiện TOUCH tiếp theo - trả về FALSE //sẽ không nhận sự kiện trong luồng return false; } @Override public void onTouchEvent(RecyclerView rv, MotionEvent e) { //Nhận các sự kiện như //MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_MOVE ... //Với điều kiện onInterceptTouchEvent đã trả về TRUE } @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } }; recyclerView.addOnItemTouchListener(listener);
Với onInterceptTouchEvent
bạn có thể xây dựng để có được sự kiện onClick
, onLongClick
Ví dụ, xây dựng lớp RecyclerTouchListener
để bắt sự kiện CLick, LongClick trên phần tử của RecyclerView
RecyclerTouchListener.java
public class RecyclerTouchListener implements RecyclerView.OnItemTouchListener { /** * Xây dựng giao diện Listener */ public static interface ClickListener { public void onClick(View view,int position); public void onLongClick(View view, int position); } private ClickListener clicklistener; //Đối tượng để phát hiện ra onLongPress trên phần tử //clicklistener.onLongClick private GestureDetector gestureDetector; public RecyclerTouchListener(Context context, final RecyclerView recycleView, final ClickListener clicklistener){ this.clicklistener=clicklistener; gestureDetector = new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){ @Override public boolean onSingleTapUp(MotionEvent e) { return true; } @Override public void onLongPress(MotionEvent e) { View child=recycleView.findChildViewUnder(e.getX(),e.getY()); if(child!=null && clicklistener!=null){ clicklistener.onLongClick(child,recycleView.getChildAdapterPosition(child)); } } }); } @Override public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { //Khi bắt đầu Touch trên RecyclerView thì tìm View biểu diễn phàn tử //ở vị trí đó và xử lý Touch để biết Click, LongCLick View child=rv.findChildViewUnder(e.getX(),e.getY()); if(child!=null && clicklistener!=null && gestureDetector.onTouchEvent(e)){ clicklistener.onClick(child,rv.getChildAdapterPosition(child)); } return false; } @Override public void onTouchEvent(RecyclerView rv, MotionEvent e) { } @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } }
Áp dụng vào RecylerView
recyclerView.addOnItemTouchListener(new RecyclerTouchListener(this, recyclerView, new RecyclerTouchListener.ClickListener() { @Override public void onClick(View view, int position) { Toast.makeText(view.getContext(), "onClick phần tử " + position, Toast.LENGTH_SHORT).show(); } @Override public void onLongClick(View view, int position) { Toast.makeText(view.getContext(), "onLongClick phần tử " + position, Toast.LENGTH_SHORT).show(); } }));
Có hai onClick được gọi
Trong code trên, bạn thấy nó đã bắt onClick, onLongClick được trên phần tử. Thử code bạn cũng thấy, giả sử bấm vào phần tử RecyclerView, nhưng vị trí bấm đó đúng vào Button (Chi tiết) thì có hai sử kiện onClick diễn ra, một của code trên một của Button. Nếu muốn loại bỏ điều này, giả sử bấm đúng vào Button, thì onClick của code trên không hoạt động.
Bạn sử code onInterceptTouchEvent
như sau:
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
//Khi bắt đầu Touch trên RecyclerView thì tìm View biểu diễn phàn tử
//ở vị trí đó và xử lý Touch để biết Click, LongCLick
View child=rv.findChildViewUnder(e.getX(),e.getY());
//Phần thêm mới này kiểm tra xem nếu bấm đúng Button thì trả về false ngay
//Không bắt sự kiện trên phần tử RecylerView nữa
if (child instanceof ViewGroup)
{
ViewGroup viewGroup = (ViewGroup)child;
for(int _numChildren = viewGroup.getChildCount() - 1; _numChildren >=0; --_numChildren)
{
View _child = viewGroup.getChildAt(_numChildren);
Rect _bounds = new Rect();
_child.getHitRect(_bounds);
if (_bounds.contains((int)e.getX() - viewGroup.getLeft(), (int)e.getY() - viewGroup.getTop())) {
if (_child instanceof Button)
return false;
}
}
}
if(child!=null && clicklistener!=null && gestureDetector.onTouchEvent(e)){
clicklistener.onClick(child,rv.getChildAdapterPosition(child));
}
return false;
}
Căn lề các phần tử khi cuộn RecyclerView
Mặc định khi thao tác quận, vuốt dừng lại nó có thể dừng lại ở bất kỳ vị trí nào, tuy nhiên nếu muốn hỗ trợ việc cuộn
phần từ đầu của sanh sách dừng lại ở biên RecyclerView hoặc các hình thức khác thì dùng tới các lớp SnapHelper
như PagerSnapHelper
, LinearSnapHelper
để làm việc này
LinearSnapHelper
LinearSnapHelper
Sử dụng lớp này để các phần tử xuất hiện đầy đủ xuất hiện trong RecylerView
được căn lề để xuất hiện đều ở giữa (giữa trái - phải cho cuộn ngang,
trên - dưới cho cuộn đứng).
Để sử dụng chỉ cần thêm đoạn code đơn giản:
SnapHelper snapHelper = new LinearSnapHelper(); snapHelper.attachToRecyclerView(recyclerView);
Thư viện bên thứ 3: GravitySnapHelper cho phép căn lề phần tử theo cạnh bất kỳ của RecyclerView
Tích hợp thư viên
implementation 'com.github.rubensousa:gravitysnaphelper:1.5'
Ví dụ sử dụng:
SnapHelper snapHelperStart = new GravitySnapHelper(Gravity.START); snapHelperStart.attachToRecyclerView(recyclerView);