Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Applying stateListAnimator in RecylerView's item will cause flickering effect when calling notifyDataSetChanged

Tags:

android

I realize, if I apply android:stateListAnimator on RecylerView's item, calling adapter.notifyDataSetChanged will cause undesired flickering effect on certain RecylerView's items (Not all items, strangely)

Here's my RecylerView's item

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    android:stateListAnimator="@anim/lift_up"
    android:background="@drawable/white" >

    ...
</LinearLayout>

@anim/lift_up is defined as

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:state_enabled="true"
        android:state_pressed="true">
        <objectAnimator
            android:duration="@android:integer/config_shortAnimTime"
            android:propertyName="translationZ"
            android:valueTo="8dip"
            android:valueType="floatType" />
    </item>
    <item>
        <objectAnimator
            android:duration="@android:integer/config_shortAnimTime"
            android:propertyName="translationZ"
            android:valueTo="4dip"
            android:valueType="floatType" />
    </item>
</selector>

and @drawable/white is defined as

<drawable name="white">#ffffffff</drawable>

When I call adapter.notifyDataSetChanged, the following strange flickering effect happens at the last 5 items of RecylerView. (There are total 10 visible items on screen)

https://youtu.be/yB4UP2wEFk0

This problem only happen at API 21 and above, because only API 21 supports android:stateListAnimator

Is this a bug, or I had missed out something?

The complete minimal workable code can be downloaded from https://github.com/yccheok/RecyclerViewTutorial/tree/4763879598864233a8e6544fe240c3fb34a15b73

like image 581
Cheok Yan Cheng Avatar asked Nov 22 '15 03:11

Cheok Yan Cheng


2 Answers

Not all items, strangely

This is by design (I believe).

Internally, all recycling ViewGroups (that I have dealt with) maintain a View pool. Its expensive to create a View from scratch. Some of this cost is dissipated by using a View pool, at the expense of resource usage. Size of this pool represents this tradeoff. A basic implementation can be looked at here: ViewPool from DeckView.

RecyclerView does the same with RecycledViewPool. Note the default max size:

public static class RecycledViewPool {
    ....
    private static final int DEFAULT_MAX_SCRAP = 5;
    ....
}

I believe the first 5 views in your case don't flicker because they come from the pool - they're not created when notifyDataSetChanged() call is made. This might be the reason that the StateListAnimator does not kick in. For the rest 5 rows/items, new Views are created.

From the source code:

View getViewForPosition(int position, boolean dryRun) {
    ....
    // 0) If there is a changed scrap, try to find from there
    ....
    // 1) Find from scrap by position
    ....
    // 2) Find from scrap via stable ids, if exists
    ....
    // fallback to recycler
        ....
        // getRecycledViewPool() returns an instance of RecycledViewPool
        holder = getRecycledViewPool().getRecycledView(type);
        ....
    // if holder is still 'null' after checking the pool, create a new one
        ....
        if (holder == null) {
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
        }
        ....
}

As you can tell, recycling is a serious business. What I cannot explain is why options 0, 1, and 2 fail - or tell if they even do. To check my hypothesis, you can change the pool's max size and note any difference(s) (in the # of views that flicker):

mRecyclerView
    .getRecycledViewPool()
        .setMaxRecycledViews(RecyclerView.INVALID_TYPE, 10);
like image 162
Vikram Avatar answered Nov 15 '22 17:11

Vikram


If you check documentation of recyclerview method notifyDataSetChanged in http://androidxref.com/6.0.0_r1/xref/frameworks/support/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java for method notifyDatasetChanged, it mentions following "

RecyclerView will attempt to synthesize visible structural change events for adapters that report that they have {@link #hasStableIds() stable IDs} when this method is used. This can help for the purposes of animation and visual object persistence but individual item views will still need to be rebound and relaid out.

Similar idea is sketched out also in https://www.youtube.com/watch?v=8MIfSxgsHIs where you would do stableIds true if you had to do animations for list view, also more examples in the above mentioned dev bytes series assume stable ids for animation persistence in list items.

like image 42
jay shah Avatar answered Nov 15 '22 19:11

jay shah