Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to hide divider when delete animation happens in recycler view

RecyclerView by default, does come with a nice deletion animation, as long as you setHasStableIds(true) and provide correct implementation on getItemId.

Recently, I had added divider into RecyclerView via https://stackoverflow.com/a/27037230/72437

The outcome looks as following

https://www.youtube.com/watch?v=u-2kPZwF_0w

https://youtu.be/c81OsFAL3zY (To make the dividers more visible when delete animation played, I temporary change the RecyclerView background to red)

The dividers are still visible, when deletion animation being played.

However, if I look at GMail example, when deletion animation being played, divider lines are no longer visible. They are being covered a solid color area.

https://www.youtube.com/watch?v=cLs7paU-BIg

May I know, how can I achieve the same effect as GMail, by not showing divider lines, when deletion animation played?

like image 838
Cheok Yan Cheng Avatar asked Apr 12 '16 14:04

Cheok Yan Cheng


People also ask

How do I add a separator in RecyclerView?

We have to create a default Divider using addItemDecoration() method with the RecyclerView instance, we need to pass the ItemDecoration(in this case it is DividerItemDecoration()) instance and the orientation of the LayoutManager(in this case it is vertical) of the recycler view.

What is RecyclerView LayoutManager?

A RecyclerView.LayoutManager implementation that lays out items in a grid for leanback VerticalGridView and HorizontalGridView . LinearLayoutManager. A RecyclerView.LayoutManager implementation which provides similar functionality to ListView .


3 Answers

The solution is fairly easy. To animate a decoration, you can and should use view.getTranslation_() and view.getAlpha(). I wrote a blog post some time ago on this exact issue, you can read it here.

Translation and fading off

The default layout manager will fade views out (alpha) and translate them, when they get added or removed. You have to account for this in your decoration.

The idea is simple:

However you draw your decoration, apply the same alpha and translation to your drawing by using view.getAlpha() and view.getTranslationY().

Following your linked answer, it would have to be adapted like the following:

// translate int top = child.getBottom() + params.bottomMargin + view.getTranslationY(); int bottom = top + mDivider.getIntrinsicHeight();  // apply alpha mDivider.setAlpha((int) child.getAlpha() * 255f); mDivider.setBounds(left + view.getTranslationX(), top,         right + view.getTranslationX(), bottom); mDivider.draw(c); 

A complete sample

I like to draw things myself, since I think drawing a line is less overhead than layouting a drawable, this would look like the following:

public class SeparatorDecoration extends RecyclerView.ItemDecoration {      private final Paint mPaint;     private final int mAlpha;      public SeparatorDecoration(@ColorInt int color, float width) {         mPaint = new Paint();         mPaint.setColor(color);         mPaint.setStrokeWidth(width);         mAlpha = mPaint.getAlpha();     }      @Override     public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {         final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();          // we retrieve the position in the list         final int position = params.getViewAdapterPosition();          // add space for the separator to the bottom of every view but the last one         if (position < state.getItemCount()) {             outRect.set(0, 0, 0, (int) mPaint.getStrokeWidth()); // left, top, right, bottom         } else {             outRect.setEmpty(); // 0, 0, 0, 0         }     }      @Override     public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {         // a line will draw half its size to top and bottom,         // hence the offset to place it correctly         final int offset = (int) (mPaint.getStrokeWidth() / 2);          // this will iterate over every visible view         for (int i = 0; i < parent.getChildCount(); i++) {             final View view = parent.getChildAt(i);             final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();              // get the position             final int position = params.getViewAdapterPosition();              // and finally draw the separator             if (position < state.getItemCount()) {                 // apply alpha to support animations                 mPaint.setAlpha((int) (view.getAlpha() * mAlpha));                  float positionY = view.getBottom() + offset + view.getTranslationY();                 // do the drawing                 c.drawLine(view.getLeft() + view.getTranslationX(),                         positionY,                         view.getRight() + view.getTranslationX(),                         positionY,                         mPaint);             }         }     } } 
like image 57
David Medenjak Avatar answered Sep 30 '22 07:09

David Medenjak


Firstly, sorry for the massive answer size. However, I felt it necessary to include my entire test Activity so that you can see what I have done.

The issue

The issue that you have, is that the DividerItemDecoration has no idea of the state of your row. It does not know whether the item is being deleted.

For this reason, I made a POJO that we can use to contain an integer (that we use as both an itemId and a visual representation and a boolean indicating that this row is being deleted or not.

When you decide to delete entries (in this example adapter.notifyItemRangeRemoved(3, 8);), you must also set the associated Pojo to being deleted (in this example pojo.beingDeleted = true;).

The position of the divider when beingDeleted, is reset to the colour of the parent view. In order to cover up the divider.

I am not very fond of using the dataset itself to manage the state of its parent list. There is perhaps a better way.

The result visualized

Removing items and their dividers

The Activity:

public class MainActivity extends AppCompatActivity {     private static final int VERTICAL_ITEM_SPACE = 8;      private List<Pojo> mDataset = new ArrayList<Pojo>();      @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);          for(int i = 0; i < 30; i++) {             mDataset.add(new Pojo(i));         }          final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);         LinearLayoutManager layoutManager = new LinearLayoutManager(this);         recyclerView.setLayoutManager(layoutManager);          recyclerView.addItemDecoration(new VerticalSpaceItemDecoration(VERTICAL_ITEM_SPACE));         recyclerView.addItemDecoration(new DividerItemDecoration(this));          RecyclerView.ItemAnimator ia = recyclerView.getItemAnimator();         ia.setRemoveDuration(4000);          final Adapter adapter = new Adapter(mDataset);         recyclerView.setAdapter(adapter);          (new Handler(Looper.getMainLooper())).postDelayed(new Runnable() {             @Override             public void run() {                 int index = 0;                 Iterator<Pojo> it = mDataset.iterator();                 while(it.hasNext()) {                     Pojo pojo = it.next();                      if(index >= 3 && index <= 10) {                         pojo.beingDeleted = true;                         it.remove();                     }                      index++;                 }                  adapter.notifyItemRangeRemoved(3, 8);             }         }, 2000);     }      public class Adapter extends RecyclerView.Adapter<Holder> {         private List<Pojo> mDataset;          public Adapter(@NonNull final List<Pojo> dataset) {             setHasStableIds(true);             mDataset = dataset;         }          @Override         public Holder onCreateViewHolder(ViewGroup parent, int viewType) {             View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_cell, parent, false);             return new Holder(view);         }          @Override         public void onBindViewHolder(final Holder holder, final int position) {             final Pojo data = mDataset.get(position);              holder.itemView.setTag(data);             holder.textView.setText("Test "+data.dataItem);         }          @Override         public long getItemId(int position) {             return mDataset.get(position).dataItem;         }          @Override         public int getItemCount() {             return mDataset.size();         }     }      public class Holder extends RecyclerView.ViewHolder {         public TextView textView;          public Holder(View itemView) {             super(itemView);             textView = (TextView) itemView.findViewById(R.id.text);         }     }      public class Pojo {         public int dataItem;         public boolean beingDeleted = false;          public Pojo(int dataItem) {             this.dataItem = dataItem;         }     }      public class DividerItemDecoration extends RecyclerView.ItemDecoration {          private final int[] ATTRS = new int[]{android.R.attr.listDivider};          private Paint mOverwritePaint;         private Drawable mDivider;          /**          * Default divider will be used          */         public DividerItemDecoration(Context context) {             final TypedArray styledAttributes = context.obtainStyledAttributes(ATTRS);             mDivider = styledAttributes.getDrawable(0);             styledAttributes.recycle();             initializePaint();         }          /**          * Custom divider will be used          */         public DividerItemDecoration(Context context, int resId) {             mDivider = ContextCompat.getDrawable(context, resId);             initializePaint();         }          private void initializePaint() {             mOverwritePaint = new Paint();             mOverwritePaint.setColor(ContextCompat.getColor(MainActivity.this, android.R.color.background_light));         }          @Override         public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {             int left = parent.getPaddingLeft();             int right = parent.getWidth() - parent.getPaddingRight();              int childCount = parent.getChildCount();             for (int i = 0; i < childCount; i++) {                 View child = parent.getChildAt(i);                  RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();                  int top = child.getBottom() + params.bottomMargin;                 int bottom = top + mDivider.getIntrinsicHeight();                  Pojo item = (Pojo) child.getTag();                 if(item.beingDeleted) {                     c.drawRect(left, top, right, bottom, mOverwritePaint);                 } else {                     mDivider.setBounds(left, top, right, bottom);                     mDivider.draw(c);                 }              }         }     }      public class VerticalSpaceItemDecoration extends RecyclerView.ItemDecoration {          private final int mVerticalSpaceHeight;          public VerticalSpaceItemDecoration(int mVerticalSpaceHeight) {             this.mVerticalSpaceHeight = mVerticalSpaceHeight;         }          @Override         public void getItemOffsets(Rect outRect, View view, RecyclerView parent,                                    RecyclerView.State state) {             outRect.bottom = mVerticalSpaceHeight;         }     } } 

The Activity Layout

<?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"     android:paddingLeft="@dimen/activity_horizontal_margin"     android:paddingRight="@dimen/activity_horizontal_margin"     android:paddingTop="@dimen/activity_vertical_margin"     android:paddingBottom="@dimen/activity_vertical_margin"     android:background="@android:color/background_light"     tools:context="test.dae.myapplication.MainActivity">      <android.support.v7.widget.RecyclerView         android:id="@+id/recyclerView"         android:layout_width="match_parent"         android:layout_height="match_parent" /> </RelativeLayout> 

The RecyclerView "row" Layout

<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android"           android:layout_width="match_parent"           android:layout_height="wrap_content"           android:id="@+id/text"           android:padding="8dp">  </TextView> 
like image 31
Knossos Avatar answered Sep 30 '22 07:09

Knossos


I think the ItemDecorator you use to draw a divider after every row is messing things up when swipe to delete is performed.

Instead of Using ItemDecorator to draw a Divider in a recyclerview, add a view at the end of your RecyclerView child layout design.which will draw a divider line like ItemDecorator.

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    >
 <!-- child layout Design !-->

 <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@android:color/darker_gray"
        android:layout_gravity="bottom"
        />
 </Linearlayout>
like image 31
HourGlass Avatar answered Sep 30 '22 08:09

HourGlass