Xử Lý Animation Trong RecyclerView

Tổng quan về ItemAnimator

ItemAnimator: Là thành phần hỗ trợ animation khi chúng ta add hay remove một item ra khỏi RecyclerView. Có các class chính sau:
ItemAnimator: Là class đại diện, khung sườn của animation trong RecyclerView.
SimpleItemAnimator: class wrapper lại ItemAnimator.
DefaultItemAnimtor: class xử lý animtion mặc định sử dụng trong RecyclerView.
Chúng ta có thể viết lại class xử lý animation bằng cách extends lại class SimpleItemAnimator.
Để set ItemAnimator cho ReyclerView đơn giản là chúng ta sử dụng phương thức

setItemAnimator(ItemAnimator animator)
Tuy nhiên chỉ set như vậy thì chưa đủ mà chúng ta phải tạo custom Adapter và gọi notify đúng trường hợp ứng với các thao tác add và remove item. Vấn đề này sẽ được bàn luận ở dưới đây

Notify Adapter xử lý Animation

Không giống như ListView khi chúng ta có thay đổi về mặt data thì phải gọi notifyDataSetChanged để render lại ListView. Thì ReyclerView có một số điểm khác như sau:
Gọi notify đối với tại vị trí thay đổi bằng các phương thức:

notifyItemChanged(int): Notify item tại vị trí position nếu có thay đổi. Phương thức này cũng sử dụng nếu bạn muốn thực hiện animation khi item changed.

notifyItemInserted(int):  Notify nếu bạn insert một item tại vị trí position. Phương thức này cũng được sử dụng nếu bạn muốn thực hiện animation khi add một item vào RecyclerView.

notifyItemRemoved(int): Notify khi bạn remove một item tại vị trí position. Phương thức này cũng được sử dụng nếu bạn muốn thực hiện animation khi remove một item ra khỏi RecyclerView.

notifyItemRangeChanged(int, int):  Notify những item thay đổi trên một miền từ fromPosition. Phương thức này cũng được sử dụng nếu bạn muốn xử lý animation những item changed.

notifyItemRangeInserted(int, int): Notify các item được insert vào từ fromPosition. Phương thức này cũng được sử dụng nếu bạn muốn xử lý animation những item được insert.

notifyItemRangeRemoved(int, int): Notify các item remove ra khỏi RecyclerView từ fromPosition. Phương thức này cũng được sử dụng nếu bạn muốn xử lý animation những item được removed .

Ngoài ra RecyclerView cũng có phương thức notifyDataSetChanged giống như ListView. Tuy nhiên phương thức này sẽ không xảy ra animation.
Ví dụ như sau:
Khi bạn add 1 item vào Adapter

public void addItem(String item) {
        mDatas.add(item);
        //notify tại vị trí mà bạn add item.
        notifyItemInserted(mDatas.size() - 1);
    }

Hay

 public void addItem(int position, String item) {
        mDatas.add(position, item);
        notifyItemInserted(position);
    }

Remove một item

 public void removeItem(int position) {
        mDatas.remove(position);
        notifyItemRemoved(position);
    }

Hay

public void removeItem(String item) {
        int index = mDatas.indexOf(item);
        if (index < 0)
            return;
        mDatas.remove(index);
        notifyItemRemoved(index);
    }

Change một item

 public void replaceItem(int postion, String item) {
        mDatas.remove(postion);
        mDatas.add(postion, item);
        notifyItemChanged(postion);
    }


Việc sử dụng ItemAnimator đơn giản là chúng ta:
+ set ItemAnimator cho RecyclerView thông qua phương thức setItemAnimator
+ Viết custom Adapter sử dụng những phương thức notify đã giới thiệu ở trên.
Bây giờ chúng ta cùng đi vào phần thực hành:
Tổng qua về ứng dụng như sau:
  • Một button dùng để thực hiện thêm item vào adapter để thực hiện animation add.
  • Khi click và item thì sẽ thực hiện animation change trên item đó.
  • Khi giữ item sẽ thực hiện animation remove trên item đó.
File main_acitivity.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context="com.eitguide.nguyennghia.animationreyclerview.MainActivity">

    <Button
        android:id="@+id/btn_add_item"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Add Item" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_items"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/btn_add_item" />
</RelativeLayout>

 File MainActivity.java
 package com.eitguide.nguyennghia.animationreyclerview;

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.View;
import android.widget.Button;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    ;
    private Button btnAddItem;
    private RecyclerView rvItems;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnAddItem = (Button) findViewById(R.id.btn_add_item);
        rvItems = (RecyclerView) findViewById(R.id.rv_items);

        List<String> data = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            data.add("item " + i);
        }

        final CustomAdapter adapter = new CustomAdapter(data);
        rvItems.setAdapter(adapter);
        rvItems.setLayoutManager(new LinearLayoutManager(this));
     
        //set ItemAnimator for RecyclerView
        rvItems.setItemAnimator(new DefaultItemAnimator());

        btnAddItem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                adapter.addItem("new item");
                rvItems.scrollToPosition(adapter.getItemCount() - 1);
            }
        });

    }
}

  • Button btnAddItem sẽ add một item có nội dung là “new item” và Adapter.
  • Phương thức scrollToPosition sẽ di chuyển đến vị trí mà chúng ta vừa add item
  • Nhớ là set ItemAnimator cho RecyclerView.
Lớp CustomAdapter.java
package com.eitguide.nguyennghia.animationreyclerview;

import android.support.v7.widget.RecyclerView;
import android.text.Layout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import java.util.List;

/**
 * Created by nguyennghia on 8/27/16.
 */
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> {
    private List<String> mDatas;

    public CustomAdapter(List<String> data) {
        mDatas = data;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater li = LayoutInflater.from(parent.getContext());
        View itemView = li.inflate(R.layout.item_row, parent, false);
        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        String item = mDatas.get(position);
        holder.tvItem.setText(item);
    }

    @Override
    public int getItemCount() {
        return mDatas.size();
    }

    public void addItem(String item) {
        mDatas.add(item);
        notifyItemInserted(mDatas.size() - 1);
    }

    public void addItem(int position, String item) {
        mDatas.add(position, item);
        notifyItemInserted(position);
    }

    public void removeItem(int position) {
        mDatas.remove(position);
        notifyItemRemoved(position);
    }

    public void removeItem(String item) {
        int index = mDatas.indexOf(item);
        if (index < 0)
            return;
        mDatas.remove(index);
        notifyItemRemoved(index);
    }

    public void replaceItem(int postion, String item) {
        mDatas.remove(postion);
        mDatas.add(postion, item);
        notifyItemChanged(postion);
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        private TextView tvItem;

        public ViewHolder(final View itemView) {
            super(itemView);
            tvItem = (TextView) itemView.findViewById(R.id.tv_item);

            itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View view) {
                    removeItem(getAdapterPosition());
                    Toast.makeText(itemView.getContext(), "Removed Animation", Toast.LENGTH_SHORT).show();
                    return false;
                }
            });

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    replaceItem(getAdapterPosition(), "item changed");
                    Toast.makeText(itemView.getContext(), "Changed Animation", Toast.LENGTH_SHORT).show();
                }
            });
        }
    }
}

Với row của 1 item (item_row.xml) có nội dung

 <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="3dp"
    android:background="#ecf0f1">

    <TextView
        android:id="@+id/tv_item"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="8dp"
        android:textColor="#2c3e50"
        android:textSize="16dp" />

</FrameLayout>

Kết quả chúng ta sẽ thấy như video Demo dưới đây:

https://www.youtube.com/watch?time_continue=61&v=pxHpwFpT4Lc&feature=emb_logo

Vì chúng ta sử dụng DefaultAnimator nên Animation sẽ như sau:
  • Animation Added: Thực hiện thay đổi Alpha của itemView từ 0 đến 1:
  • Animation Removed: Thực hiện thay đổi giá trị Alpha của itemView từ 1 về 0.
  • Animation Changed: Thực hiện Animation từ 1 về 0 và sau đó set Animation từ 0 đến 1 để thay đổi item.
Đó là cách mà DefaulItemAnimator xử lý. Còn nếu bạn muốn Animation theo ý bạn thì phải viết lại Animation bằng cách extends từ SimpleItemAnimator. Tuy nhiên tôi muốn giới thiệu cho bạn một thư viện cung cấp cho các bạn nhiều Animation hơn. Source code của thư viện này được public ở trên github: https://github.com/wasabeef/recyclerview-animators
Nếu bạn nào có hứng thú muốn tìm hiểu cách viết ItemAnimator có thể liên hệ mình để có thể trao đổi chi tiết hơn.
Source code ItemAnimatorReyclerView

Kết luận

Qua những bài viết về ListView và ReyclerView tôi hy vọng những bài viết sẽ giúp các bạn một phần nào đó trong học tập cũng như công việc.


Comments