Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animation for change and remove do not work with RecyclerView

App sometimes takes updated dataset from server and needs to show animations of changes. I try to use RecyclerView and ItemAnimator, but it doesn't work: only animateAdd method of ItemAnimator is called, animateChange and animateRemove are not.

Is there any ideas what wrong?

I've made simple app with RecyclerView and dataset changes emulator.

package com.tseglevskiy.recycleviewdevmo;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

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

public class MainActivity extends Activity {

    RecyclerView rv;

    List<DummyData> data;

    Handler handler;

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

        handler = new Handler();

        rv = (RecyclerView)findViewById(R.id.list);
        rv.setItemAnimator(new MyAnimator());

        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        layoutManager.scrollToPosition(0);
        rv.setLayoutManager(layoutManager);

        data = new ArrayList<>();
        data.add(new DummyData(1, "foo"));
        data.add(new DummyData(2, "bar"));

        MyAdapter adapter = new MyAdapter(copycopy());
        adapter.setHasStableIds(true);
        rv.setAdapter(adapter);

        loop();
    }

    void loop() {
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                getNewDataset();
                loop();
            }
        }, 3000);
    }

    List<DummyData> copycopy() {
        List<DummyData> copy = new ArrayList<>();

        for (DummyData f: data) {
            copy.add(new DummyData(f.id, f.what));
        }

        return copy;
    }

    void getNewDataset() {
        long now = System.currentTimeMillis();
        Log.d("foo", "getNewDataset " + now);

        // sometimes remove first
        if (randInt(1, 10) > 7) {
            data.remove(0);
        }
        // remove random
        if (data.size() > 5) {
            data.remove(randInt(data.size()/2, data.size()-1));
        }
        // add a few
        int dummyId = (int)(now % Integer.MAX_VALUE);
        data.add(new DummyData(dummyId, "A" + randInt(0,1000000)));
        data.add(new DummyData(dummyId + 1, "B" + randInt(0, 1000000)));
        data.set(randInt(data.size()/2, data.size()-1),
                new DummyData(dummyId + 2, "C" + randInt(0, 1000000)));

        MyAdapter adapter = new MyAdapter(copycopy());
        adapter.setHasStableIds(true);

        rv.swapAdapter(adapter, false);
    }

    public static int randInt(int min, int max) {
        Random rand = new Random();
        int randomNum = rand.nextInt((max - min) + 1) + min;
        return randomNum;
    }

    public static class MyAdapter extends RecyclerView.Adapter<MyHolder> {
        private List<DummyData> items;

        public MyAdapter(List<DummyData> items) {
            this.items = items;
        }

        @Override
        public MyHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
            View v = LayoutInflater.from(viewGroup.getContext())
                    .inflate(android.R.layout.simple_expandable_list_item_1, viewGroup, false);
            return new MyHolder(v);

        }

        @Override
        public void onBindViewHolder(MyHolder myHolder, int position) {
            DummyData model = items.get(position);
            myHolder.text1.setText(model.what);
        }

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

    public static class MyHolder extends RecyclerView.ViewHolder {
        TextView text1;

        public MyHolder(View itemView) {
            super(itemView);
            text1 = (TextView)itemView.findViewById(android.R.id.text1);
        }
    }

    public static class DummyData {
        public DummyData(int id, String what) {
            this.id = id;
            this.what = what;
        }

        public int id;
        public String what;

        @Override
        public String toString() {
            return what;
        }
    }

    public static class MyAnimator extends DefaultItemAnimator {
        @Override
        public boolean animateAdd(RecyclerView.ViewHolder holder) {
            Log.d("foo", "animateAdd");
            return super.animateAdd(holder);
        }

        @Override
        public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromX, int fromY, int toX, int toY) {
            Log.d("foo", "animateChange");
            return super.animateChange(oldHolder, newHolder, fromX, fromY, toX, toY);
        }

        @Override
        public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
            Log.d("foo", "animateMove");
            return super.animateMove(holder, fromX, fromY, toX, toY);
        }

        @Override
        public boolean animateRemove(RecyclerView.ViewHolder holder) {
            Log.d("foo", "animateRemove");
            return super.animateRemove(holder);
        }
    }
}
like image 715
tse Avatar asked Nov 23 '25 00:11

tse


1 Answers

Whenever you use setHasStableIds(true), you need to override your adapter's getItemId(position) method.

It should return a truly unique value, that is not direct function of position. In your example it should be something like:

@Override
public long getItemId(int position)
{
    return items.get(position).id;
}

Of course you also need to call adapter.notifyDataSetChanged() when your data is updated

like image 86
tochkov Avatar answered Nov 24 '25 14:11

tochkov