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ề ...

recyleview

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ả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);

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);

Đăng ký nhận bài viết mới