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ề ...
Để sử dụng RecyclerView làm theo các bước sau: cũng tương tự như ListView thôi à!
Đầu tiên, vào XML lấy Recycler ra.
Tạo một layout để hiển thị.
Tạo một class chứa dữ liệu cho layout.
Tạo một adapter để đổ dữ liệu vào RecyclerView.
Layout thì đơn giản rồi, các bạn tùy ý thiết kế theo ý mình.
Trong class thì các bạn tạo các biến cần thiết rồi add vào Contructor và Getter&Setter. ví dụ:
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;
}
}
Còn với Adapter thì như sau:
public class StudentAdapter extends RecyclerView.Adapter {
//Dữ liệu hiện thị là danh sách sinh viên
private ArrayList<tên Class> mStutents;
// Lưu Context để dễ dàng truy cập
private Context mContext;
public StudentAdapter(List _student, Context mContext) { // constructor
this.mStutents = _student;
this.mContext = mContext;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
// Nạp layout cho View biểu diễn phần tử sinh viên
View itemView =
inflater.inflate(R.layout.tênlayout, parent, false);
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.tvstudentname.setText(mstudent.get(position).getmName());
holder.tvbirthyear.setText(mstudent.get(position).getBirthYear()+"");
}
@Override
public int getItemCount() {
return mStutents.size();
}
/**
* Lớp nắm giữ cấu trúc view
*/
public class ViewHolder extends RecyclerView.ViewHolder {
TextView tvstudentname;
TextView tvbirthyear;
Button detail_button;
public ViewHolder(View itemView) {
super(itemView);
tvstudentname = itemView.findViewById(R.id.studentname);
tvbirthyear = 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();
}
});
}
}
}
Cuối cùng là đổ dữ liệu vào RecyclerView là xong!
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, LinearLayoutManager.VERTICAL, false); //hoặc là this thôi cũng được.
recyclerView.setAdapter(adapter);
recyclerView.setHasFixedSize(true); // Tối ưu kích thước cho layout
recyclerView.setLayoutManager(linearLayoutManager);
}
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
Thêm đường gạch ngang sau mỗi layout
DividerItemDecoration divider = new DividerItemDecoration(context, linearLayoutManager.getOrientation());
recyclerView.addItemDecoration(divider);
//Chèn một kẻ ngang giữa các phần tử theo hình tùy chọn
DividerItemDecoration dividerHorizontal =
new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
dividerHorizontal.
setDrawable(ContextCompat.getDrawable(this, R.drawable.devider_red));
recyclerView.addItemDecoration(dividerHorizontal);
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
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());
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
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();
}
});
}
}
}
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()
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ải
onSwipeLeft vuốt trái
onSwipeUp vuốt lên
onSwipeDown 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);
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);
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));
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));
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) {
}
}
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();
}
}));
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;
}
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
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);
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);