Как использовать GridLayoutAnimation в RecyclerView?

Я пытаюсь заменить свой GridView новым RecyclerView (используя GridLayoutManager), но кажется, что он плохо справляется с gridLayoutAnimation (ClassCastException: LayoutAnimationController$AnimationParameters cannot be cast to GridLayoutAnimationController$AnimationParameters). Он работает с обычной анимацией макета, но, поскольку это сетка, на планшетах это занимает слишком много времени.

То, что я пытаюсь сделать, похоже на иерархическую синхронизацию. Если вы посмотрите на пример видео, оно показывает, что анимация макета перемещается по диагонали от левого верхнего угла к правому нижнему углу. Обычная анимация макета будет выполнять анимацию строка за строкой, поэтому на больших сетках (например, на планшетах) потребуется слишком много времени. Я также попытался исследовать ItemAnimator, но при этом анимация запускалась бы одновременно на всех ячейках, как в примере "Не".

Есть ли способ выполнить эту анимацию макета сетки в RecyclerView?

Это gridview_layout_animation.xml:

<!-- replace gridLayoutAnimation with layoutAnimation and -->
<!-- replace column- and rowDelay with delay for RecyclerView -->

<gridLayoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:columnDelay="15%"
    android:rowDelay="15%"
    android:animation="@anim/grow_in"
    android:animationOrder="normal"
    android:direction="top_to_bottom|left_to_right"
    android:interpolator="@android:interpolator/linear"
/>

А это анимация grow_in.xml:

<set android:shareInterpolator="false"
 xmlns:android="http://schemas.android.com/apk/res/android">
    <scale
        android:interpolator="@android:interpolator/decelerate_quint"
        android:fromXScale="0.0"
        android:toXScale="1.0"
        android:fromYScale="0.0"
        android:toYScale="1.0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:fillAfter="true"
        android:duration="400"
        android:startOffset="200"
    />
</set>

РЕДАКТИРОВАТЬ: Основываясь на ответе Galaxas0, вот решение, которое требует только от вас использовать пользовательское представление, которое расширяет RecyclerView, В основном только переопределение attachLayoutAnimationParameters() метод. С этим <gridLayoutAnimation> работает как это было с GridView.

public class GridRecyclerView extends RecyclerView {

    public GridRecyclerView(Context context) {
        super(context);
    }

    public GridRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public GridRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public void setLayoutManager(LayoutManager layout) {
        if (layout instanceof GridLayoutManager){
            super.setLayoutManager(layout);
        } else {
            throw new ClassCastException("You should only use a GridLayoutManager with GridRecyclerView.");
            }
        }

    @Override
    protected void attachLayoutAnimationParameters(View child, ViewGroup.LayoutParams params, int index, int count) {

        if (getAdapter() != null && getLayoutManager() instanceof GridLayoutManager){

            GridLayoutAnimationController.AnimationParameters animationParams =
                (GridLayoutAnimationController.AnimationParameters) params.layoutAnimationParameters;

            if (animationParams == null) {
                animationParams = new GridLayoutAnimationController.AnimationParameters();
                params.layoutAnimationParameters = animationParams;
            }

            int columns = ((GridLayoutManager) getLayoutManager()).getSpanCount();

            animationParams.count = count;
            animationParams.index = index;
            animationParams.columnsCount = columns;
            animationParams.rowsCount = count / columns;

            final int invertedIndex = count - 1 - index;
            animationParams.column = columns - 1 - (invertedIndex % columns);
            animationParams.row = animationParams.rowsCount - 1 - invertedIndex / columns;

        } else {
            super.attachLayoutAnimationParameters(child, params, index, count);
        }
    }
}

3 ответа

Решение

LayoutAnimationController связан в ViewGroup и оба ListView а также GridView расширить метод ниже, чтобы обеспечить ребенка animationParams, Проблема в том, что GridLayoutAnimationController требует своего AnimationParameters это не может быть приведено в класс.

    /**
     * Subclasses should override this method to set layout animation
     * parameters on the supplied child.
     *
     * @param child the child to associate with animation parameters
     * @param params the child's layout parameters which hold the animation
     *        parameters
     * @param index the index of the child in the view group
     * @param count the number of children in the view group
     */
    protected void attachLayoutAnimationParameters(View child,
            LayoutParams params, int index, int count) {
        LayoutAnimationController.AnimationParameters animationParams =
                    params.layoutAnimationParameters;
        if (animationParams == null) {
            animationParams = new LayoutAnimationController.AnimationParameters();
            params.layoutAnimationParameters = animationParams;
        }

        animationParams.count = count;
        animationParams.index = index;
    }

Поскольку этот метод по умолчанию добавляет LayoutAnimationController.AnimationParameters вместо GridLayoutAnimationController.AnimationParametersИсправление должно состоять в том, чтобы создать и прикрепить один заранее. Что нам нужно реализовать, это то, что GridView уже делает:

@Override
protected void attachLayoutAnimationParameters(View child,
        ViewGroup.LayoutParams params, int index, int count) {

    GridLayoutAnimationController.AnimationParameters animationParams =
            (GridLayoutAnimationController.AnimationParameters) params.layoutAnimationParameters;

    if (animationParams == null) {
        animationParams = new GridLayoutAnimationController.AnimationParameters();
        params.layoutAnimationParameters = animationParams;
    }

    animationParams.count = count;
    animationParams.index = index;
    animationParams.columnsCount = mNumColumns;
    animationParams.rowsCount = count / mNumColumns;

    if (!mStackFromBottom) {
        animationParams.column = index % mNumColumns;
        animationParams.row = index / mNumColumns;
    } else {
        final int invertedIndex = count - 1 - index;

        animationParams.column = mNumColumns - 1 - (invertedIndex % mNumColumns);
        animationParams.row = animationParams.rowsCount - 1 - invertedIndex / mNumColumns;
    }
}

Тиражировать GridViewсамое близкое, что мы можем сделать - это вставить в onBindViewHolder() что позволяет им бегать раньше dispatchDrawвызов, который вызывает анимацию.

ViewGroup.LayoutParams params = holder.itemView.getLayoutParams();
        GridLayoutAnimationController.AnimationParameters animationParams = new GridLayoutAnimationController.AnimationParameters();
        params.layoutAnimationParameters = animationParams;

        animationParams.count = 9;
        animationParams.columnsCount = 3;
        animationParams.rowsCount = 3;
        animationParams.index = position;
        animationParams.column = position / animationParams.columnsCount;
        animationParams.row = position % animationParams.columnsCount;

При использовании RecyclerViewновый GridLayoutManagerпопробуйте получить параметры из этого. Приведенный выше пример является подтверждением концепции, демонстрирующей, что она работает. У меня есть жестко закодированные значения, которые не совсем подходят и для моего приложения.

Поскольку это API, существующий со времен API 1 без реальной документации или примеров, я настоятельно рекомендую не использовать его, учитывая, что существует множество способов тиражировать его функциональность.

Более простое решение

TransitionManager.beginDelayedTransition(moviesGridRecycler);
gridLayoutManager.setSpanCount(gridColumns);
adapter.notifyDataSetChanged();

Но не забудьте сделать свой RecyclerAdapter setHasStableIds(true); и реализовать getItemID()

@Override
public long getItemId(int position) {
   return yourItemSpecificLongID;
}

Цитирование @Musenkishi на https://gist.github.com/Musenkishi/8df1ab549857756098ba

Понятия не имею. Ты звонишь recyclerView.scheduleLayoutAnimation(); после настройки адаптера? И вы установили android:layoutAnimation="@anim/your_layout_animation" на ваш <GridRecyclerView> в макете?

Это решило мою проблему.

Другие вопросы по тегам