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 (согласно Mr. Debugger), RecyclerView сначала удаляет мой довольно зеленый фон, а затем сжимает два соседних элемента вместе.

enter image description hereenter image description here

Мне бы хотелось, чтобы он сохранял фон там, пока он это делает (как и приложение Inbox). Есть ли способ сделать это?

Ответы

Ответ 1

У меня была такая же проблема, и я не хотел вводить новую библиотеку, чтобы исправить ее. RecyclerView не удаляет ваш довольно зеленый фон, он просто перерисовывает себя, а ваш ItemTouchHelper больше не рисуется. На самом деле это рисунок, но dX равен 0 и рисуется от itemView.getLeft() (от 0) до dX (что равно 0), поэтому вы ничего не видите. И это слишком много, но я вернусь к нему позже.

Во всяком случае, вернуться к фону во время анимации строк: я не мог сделать это в пределах ItemTouchHelper и onChildDraw. В конце концов мне пришлось добавить еще один декоратор предметов, чтобы сделать это. Он идет по этим строкам:

public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    if (parent.getItemAnimator().isRunning()) {
        // find first child with translationY > 0
        // draw from it 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 хранит ViewHolder удаленных строк в случае, если их нужно восстановить. Он также называет onChildDraw для этих VH в дополнение к VH, который выкачивается. Не уверен относительно последствий управления памятью этого поведения, но мне нужна дополнительная проверка в начале onChildDraw, чтобы избежать рисования для строк "fantom".

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

В вашем случае это рисование слева = 0 вправо = 0, поэтому вы ничего не видите, кроме накладных расходов. Если вы начинаете видеть ранее прочищенные строки, рисующие их фоны, это и есть причина.

EDIT: я пошел на это, см. это сообщение в блоге и github repo.

Ответ 2

Мне удалось заставить его работать с помощью библиотеки Wasaceefs recyclerview-animators.

Теперь My 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();
    }

}

Реализации переопределенных функций идентичны тем, что находится в readme-reciler-аниматорах на github.

Также представляется необходимым изменить ItemAnimator на пользовательский и установить для параметра removeDuration значение 0 (или другое низкое значение - это предотвращение некоторых мерцаний):

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

Это не вызывает никаких проблем, поскольку обычная (не-swiping) удаление анимации используется в 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;
}

решает эту проблему.