web-dev-qa-db-ja.com

RecyclerView ItemTouchHelperスワイプ削除アニメーション

私はスワイプで削除して、(Inboxアプリのように)背景を描画します。これは、ItemTouchHelperによって実装されています-onChilDrawメソッドをオーバーライドして、提供されたキャンバスに長方形を描画します。

    ItemTouchHelper mIth = new ItemTouchHelper(
        new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {

            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                remove(viewHolder.getAdapterPosition());
            }

            public boolean onMove(RecyclerView recyclerview, RecyclerView.ViewHolder v, RecyclerView.ViewHolder target) {
                return false;
            }

            @Override
            public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {

                View itemView = viewHolder.itemView;

                Drawable d = ContextCompat.getDrawable(context, R.drawable.bg_swipe_item_right);
                d.setBounds(itemView.getLeft(), itemView.getTop(), (int) dX, itemView.getBottom());
                d.draw(c);

                super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
            }
        });

上で呼び出された削除メソッドは、アダプターにあります。

    public void remove(int position) {
       items.remove(position);
       notifyItemRemoved(position);
    }

背景はきれいに引き出されますが、notifyItemRemovedが呼び出されると(Debugger氏によると)、RecyclerViewは最初にかなり緑色の背景を削除し、次に2つの隣接する項目を一緒にプッシュします。

enter image description hereenter image description here

(受信トレイアプリと同じように)それが行われている間、背景をそこに保持したいと思います。それを行う方法はありますか?

15
RokG

同じ問題があり、それを修正するためだけに新しいlibを導入したくありませんでした。 RecyclerViewはかなり緑の背景を削除せず、それ自体を再描画するだけで、ItemTouchHelperはもう描画していません。実際には描画していますが、dXは0であり、itemView.getLeft()(0)からdX(0)に描画しているため、何も表示されません。描きすぎですが、後でまた見ます。

とにかく、行がアニメーションしている間に背景に戻ります:ItemTouchHelperonChildDraw内ではできませんでした。最後に、それを行うために別のアイテムデコレータを追加する必要がありました。それはこれらの線に沿っています:

public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    if (parent.getItemAnimator().isRunning()) {
        // find first child with translationY > 0
        // draw from it's top to translationY whatever you want

        int top = 0;
        int bottom = 0;

        int childCount = parent.getLayoutManager().getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getLayoutManager().getChildAt(i);
            if (child.getTranslationY() != 0) {
                top = child.getTop();
                bottom = top + (int) child.getTranslationY();                    
                break;
            }
        }

        // draw whatever you want

        super.onDraw(c, parent, state);
    }
}

このコードでは、アニメーション化する行のみが考慮されますが、行が下がることも考慮する必要があります。これは、最後の行をスワイプで削除すると発生し、上の行はそのスペースまでアニメーション化されます。

ItemTouchHelperの描画が多すぎると言ったとき:ItemTouchHelperは、復元する必要がある場合に備えて、削除された行のViewHoldersを保持しているようです。スワイプされているVHに加えて、それらのVHに対してonChildDrawを呼び出しています。この動作のメモリ管理への影響については不明ですが、「ファントム」行の描画を回避するために、onChildDrawの先頭に追加のチェックが必要でした。

if (viewHolder.getAdapterPosition() == -1) {
    return;
}

あなたの場合、それは左= 0から右= 0に描画しているので、何も見えませんがオーバーヘッドがあります。以前にスワイプされた行が背景を描画しているのを見始めた場合、それが理由です。

編集:私はこれで試してみました、これをご覧ください ブログ投稿 とこれ github repo

4

Wasabeefsのrecyclerview-animators ライブラリを使用して、なんとか動作させることができました。

私のViewHolderは、ライブラリの提供されるAnimateViewHolderを拡張します。

_    class MyViewHolder extends AnimateViewHolder {

    TextView textView;

    public MyViewHolder(View itemView) {
        super(itemView);
        this.textView = (TextView) itemView.findViewById(R.id.item_name);
    }

    @Override
    public void animateAddImpl(ViewPropertyAnimatorListener listener) {
        ViewCompat.animate(itemView)
                .translationY(0)
                .alpha(1)
                .setDuration(300)
                .setListener(listener)
                .start();
    }

    @Override
    public void preAnimateAddImpl() {
        ViewCompat.setTranslationY(itemView, -itemView.getHeight() * 0.3f);
        ViewCompat.setAlpha(itemView, 0);
    }

    @Override
    public void animateRemoveImpl(ViewPropertyAnimatorListener listener) {
        ViewCompat.animate(itemView)
                .translationY(0)
                .alpha(1)
                .setDuration(300)
                .setListener(listener)
                .start();
    }

}
_

オーバーライドされた関数の実装は、githubのrecyclerview-animatorsのreadmeと同じです。

また、ItemAnimatorをカスタムに変更し、removeDurationを0(または別の低い値-これはちらつきを防ぐため)に設定する必要があるようです。

_    recyclerView.setItemAnimator(new SlideInLeftAnimator());
    recyclerView.getItemAnimator().setRemoveDuration(0);
_

使用される通常の(スワイプしない)削除アニメーションはAnimateViewHolderのアニメーションであるため、これによって問題が発生することはありません。

他のすべてのコードは質問と同じに保たれました。私はまだこれの内部の仕組みを理解する時間がありませんでしたが、誰かがそうしたいと感じた場合は、この回答を自由に更新してください。

更新:recyclerView.getItemAnimator().setRemoveDuration(0);を設定すると、実際にはスワイプの「再バインド」アニメーションが中断されます。幸いなことに、その行を削除し、animateRemoveImplでより長い期間を設定すると(私にとって500が機能します)、ちらつきの問題も解決します。

更新2:ItemTouchHelper.SimpleCallbackがItemAnimatorのアニメーション期間を使用することが判明したため、上記のsetRemoveDuration(0)がスワイプアニメーションを中断するのはこのためです。メソッドgetAnimationDurationを次のようにオーバーライドするだけです。

_@Override
public long getAnimationDuration(RecyclerView recyclerView, int animationType, float animateDx, float animateDy) {
    return animationType == ItemTouchHelper.ANIMATION_TYPE_DRAG ? DEFAULT_DRAG_ANIMATION_DURATION
            : DEFAULT_SWIPE_ANIMATION_DURATION;
}
_

その問題を解決します。

1
RokG

アダプターの位置を更新して、アニメーションを削除するだけです

 @Override
        public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
            int position = viewHolder.getAdapterPosition();

            if (direction == ItemTouchHelper.LEFT) {
                remove(position);
            } else {
                mAdapter.notifyItemChanged(position);
            }

        }
0
Rafael M. G.