androidandroid-recyclerviewrippledrawablestatelistdrawable

Why does a list and state list selector ripple animation not draw completely on API24?


In this case the question is pertaining to a RecyclerView, where I'd like the ripple effect to animate (roughly if not exactly) from where the finger touched with a custom colour to the full extent of a view, much like a listview row in terms of the view's layout, and finally end its animation with the row selected in another colour.

I have

items_row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/rowLayout"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:orientation="horizontal"
  android:paddingBottom="5dp"
  android:paddingTop="5dp"
  android:background="@drawable/items_ripple_state_selector"
  >

  <RelativeLayout
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1"
    android:gravity="center">

    <TextView
      android:id="@+id/item_unit"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_below="@+id/item_amount"
      android:layout_centerHorizontal="true"
      android:gravity="center"
      android:text="unit" />

    <TextView
      android:id="@+id/item_amount"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentTop="true"
      android:layout_centerHorizontal="true"
      android:gravity="center"
      android:text="amount" />
  </RelativeLayout>

  <RelativeLayout
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="4.6"
    android:gravity="center_vertical">

    <TextView
      android:id="@+id/item_title"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="title"
      android:textAppearance="?android:attr/textAppearanceLarge" />

    <TextView
      android:id="@+id/item_description"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignLeft="@+id/item_title"
      android:layout_alignWithParentIfMissing="true"
      android:layout_below="@+id/item_title"
      android:text="description" />
  </RelativeLayout>
</LinearLayout>

and

items_ripple_state_selector.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
  <item>
    <selector>
      <item android:state_pressed="true" android:drawable="@color/state_activated" />
      <item android:state_selected="true" android:drawable="@color/state_selected" />
      <item android:drawable="@android:color/transparent" />
    </selector>
  </item>
  <item>
    <ripple android:color="@color/primary">
      <item android:id="@android:id/mask">
        <color android:color="@android:color/white" />
      </item>
    </ripple>
  </item>
</layer-list>

Tests that came out bad

I have done tests on the following devices, in which the ripple effect animation stops way too early, resulting more in a "blip"/"ping" rather than a full expansion to the view's edges.

Tests that came out good

I have done tests on the following devices, in which the ripple effect animation animates beautifully in what I would expect is its full length, to the view's edges:

I can't for the life of me figure out what's going on with this. It looks pretty horrible on the N6 and N5X as the animation stops mid-way, with no distinct pattern as to when the animation stops.

I've read quite a few SO questions as well as RecyclerView, StateListDrawable and RippleDrawable documentation, but there's just nothing that gives any explanation to the behaviour I see.

The closest I have come, which is what I'm currently using, and what I've shared in this post in terms of code, came from this answer https://stackoverflow.com/a/31335539/975641

Does anyone have an idea as to why this happens and how to fix it?


Solution

  • I don't know the specific reason for why it works in Marshmallow, but not Nougat. However, most commonly when you see this issue, it's because the RecyclerView is refreshing and/or re-binding way too much.

    Basically what would happen is

    1. User clicks
    2. Animation starts
    3. Something called for a rebinding on that item
    4. RecyclerView calls rebind on the item
    5. Animation is canceled because the view resets.

    Most commonly this occurs when calling either RecyclerAdapter#notifyItemChanged(int) method or RecyclerAdapter#notifyDatasetChanged() too many times.

    It would be best to use the RecyclerView.Adapter#notifyItem____ methods if possible. notifyDatasetChanged() will cause a rebind on all the items in the list. Using the notifyItem_____ methods will make sure that only individual items are updated when they need to be. Otherwise, you need to ensure to only call nofityDatasetChanged() when there are actual changed in the dataset.

    Another common issue would be if the app is calling invalidate in either the RecyclerView or any parent of the RecyclerView. This would cause a refresh of the entire View tree which would require a rebind for all the items in a RecyclerView.