How to hide divider when delete animation happens in recycler view

二次信任 提交于 2020-01-12 04:11:28

问题


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?


回答1:


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);
            }
        }
    }
}



回答2:


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

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>



回答3:


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>


来源:https://stackoverflow.com/questions/36577042/how-to-hide-divider-when-delete-animation-happens-in-recycler-view

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!