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?
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.
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 .
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.
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()
andview.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);
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); } } } }
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 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.
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; } } }
<?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>
<?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>
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With