Android сложная анимация пропускает кадры при изменении размера вида - PullRequest
1 голос
/ 02 марта 2020

Я пытаюсь создать несколько сложную анимацию, в которой участвуют несколько видов в одном макете. Представления изменят размер, непрозрачность, и есть переключатель изображения, который все должны воспроизводиться одновременно. Мне удалось выполнить необходимые анимации с помощью ValueAnimator, но анимация пропускает слишком много кадров (возможно, 25 кадров), которые необходимо оптимизировать, если это возможно.

Чтобы показать некоторые подробности о анимация, вот мой файл макета:

<?xml version="1.0" encoding="utf-8"?>
<com.example.android.views.DragLayer xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <FrameLayout
        android:id="@+id/containerView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <!-- Target width is 60dp for animation view. -->

        <com.airbnb.lottie.LottieAnimationView
            android:id="@+id/animationView"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_gravity="center"
            android:visibility="gone"
            app:lottie_rawRes="@raw/bubble_loop_animation"
            app:lottie_loop="true"
            app:lottie_autoPlay="false" />

        <!-- Target width is 50dp for progress view. -->

        <com.example.android.views.ProgressView
            android:id="@+id/progressView"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_gravity="center"
            android:visibility="gone"
            app:useDefaultProgressColors="false"
            app:strokeWidth="10dp"
            app:strokeGradientStart="#FFC928"
            app:strokeGradientEnd="#E53935" />

        <!-- Width transition: from 28dp to 45dp
             Height transition: from 28dp to 45dp
             Alpha transition: from 0.35 to 1 -->

        <View
            android:id="@+id/gamepadBackgroundView"
            android:layout_width="28dp"
            android:layout_height="28dp"
            android:background="@drawable/gamepad_background"
            android:layout_gravity="center"
            android:alpha="0.35" />

        <!-- Width transition: from 19dp to 29dp
             Height transition: from 14dp to 20dp -->

        <androidx.appcompat.widget.AppCompatImageView
            android:id="@+id/fireImageView"
            android:layout_width="19dp"
            android:layout_height="14dp"
            android:layout_gravity="center"
            app:srcCompat="@drawable/ic_fire" />

        <!-- Width transition: from 14dp to 24dp
             Height transition: from 9dp to 16dp
             Remove marginTop on scale up and add on scale down -->

        <ImageSwitcher
            android:id="@+id/gamepadImageSwitcher"
            android:layout_width="14.1dp"
            android:layout_height="9.3dp"
            android:layout_gravity="center"
            android:layout_marginTop="5dp" />

    </FrameLayout>

</com.burakgon.gamebooster3.views.DragLayer>

Информация о целевой анимации отображается в тексте макета. Чтобы уточнить еще больше:

  1. АнимацияView должна изменить размер с 0dp до 60dp (от 0x0 до 60x60),
  2. ProgressView должна изменить размер с 0dp до 50dp (от 0x0 до 50x50) ),
  3. GamepadBackgroundView необходимо изменить размер с 28dp до 45dp (от 28x28 до 45x45), а его альфа-канал необходимо изменить с 0,35f до 1f.
  4. gamepadImageSwitcher необходимо изменить размер с 14x9 до 24x16, а изображение внутри тоже нужно изменить с помощью setImageResource. Анимация - это затухание -> затухание.
  5. marginTop gamepadImageSwitcher необходимо удалить во время воспроизведения анимации, с 5dp до 0.
  6. В результате этих изменений также будут изменяться размеры контейнера и вида com.example.android.views.DragLayer.

Должна существовать обратная версия этих анимаций, которую я также успешно реализовал, однако это также пропускает кадры. Я говорю об огромных кусках, иногда я даже не вижу анимации. Я использую View.LAYER_TYPE_HARDWARE во время воспроизведения анимации, но они, похоже, тоже не помогают. Операции тяжелые со всеми изменениями размера вида, но я не уверен, как еще я могу это сделать.

Вот как я пытался реализовать анимацию.

// Required constants for animations.
static
{
    ANIM_VIEW_SIZE_SMALL = 0;
    ANIM_VIEW_SIZE_BIG = (int) dpToPx(60);
    PROGRESS_VIEW_SIZE_SMALL = 0;
    PROGRESS_VIEW_SIZE_BIG = (int) dpToPx(50);
    GAMEPAD_BACKGROUND_SIZE_SMALL = (int) dpToPx(28);
    GAMEPAD_BACKGROUND_SIZE_BIG = (int) dpToPx(45);
    FIRE_ICON_WIDTH = (int) dpToPx(18);
    FIRE_ICON_HEIGHT = (int) dpToPx(22);
    IMAGE_SWITCHER_WIDTH_SMALL = (int) dpToPx(14);
    IMAGE_SWITCHER_WIDTH_BIG = (int) dpToPx(24);
    IMAGE_SWITCHER_HEIGHT_SMALL = (int) dpToPx(9);
    IMAGE_SWITCHER_HEIGHT_BIG = (int) dpToPx(16);
    IMAGE_SWITCHER_MARGIN_TOP = (int) dpToPx(5);
}

private void startAnimation()
{
    ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
    animator.setDuration(ANIM_LONG);
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
    {
        @Override
        public void onAnimationUpdate(ValueAnimator animation)
        {
            float value = animation.getAnimatedValue() != null ? (float) animation.getAnimatedValue() : 0f;
            arrangeViewsToBiggerTransition(value);

            updateTranslationValues();
        }
    });
    animator.addListener(new AnimatorListenerAdapter()
    {
        @Override
        public void onAnimationStart(Animator animation)
        {
            super.onAnimationStart(animation);
            makeViewsVisible();
            setLayerTypeHardware(progressView, gamepadBackgroundView,
                    gamepadImageSwitcher, fireImageView);
            gamepadImageSwitcher.setImageResource(R.drawable.ic_game);
        }

        @Override
        public void onAnimationEnd(Animator animation)
        {
            super.onAnimationEnd(animation);
            resetLayerType(progressView, gamepadBackgroundView,
                    gamepadImageSwitcher, fireImageView);
            animationView.playAnimation();
        }
    });
    animator.addListener(getDeleteAnimatorListener());
    animator.start();
}

private void setLayerTypeHardware(View... views)
{
    for (View view : views)
        view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}

private void resetLayerType(View... views)
{
    for (View view : views)
        view.setLayerType(View.LAYER_TYPE_NONE, null);
}

/**
 * Arranges the views defined by percent. Changes from 0 to 1, not 0 to 100.
 *
 * @param percent is the value between 0 and 1.
 */
private void arrangeViewsToBiggerTransition(float percent)
{
    float gamepadBackgroundAlpha = getValueByPercent(GAMEPAD_BACKGROUND_ALPHA, 1f, percent);
    int animViewSize = (int) getValueByPercent(ANIM_VIEW_SIZE_SMALL, ANIM_VIEW_SIZE_BIG, percent);
    int progressViewSize = (int) getValueByPercent(PROGRESS_VIEW_SIZE_SMALL, PROGRESS_VIEW_SIZE_BIG, percent);
    int gamepadBackgroundViewSize = (int) getValueByPercent(GAMEPAD_BACKGROUND_SIZE_SMALL, GAMEPAD_BACKGROUND_SIZE_BIG, percent);
    int imageSwitcherWidth = (int) getValueByPercent(IMAGE_SWITCHER_WIDTH_SMALL, IMAGE_SWITCHER_WIDTH_BIG, percent);
    int imageSwitcherHeight = (int) getValueByPercent(IMAGE_SWITCHER_HEIGHT_SMALL, IMAGE_SWITCHER_HEIGHT_BIG, percent);
    int imageSwitcherMarginTop = (int) getValueByPercent(IMAGE_SWITCHER_MARGIN_TOP, 0f, percent);

    editViewSize(animationView, animViewSize, animViewSize);
    editViewSize(progressView, progressViewSize, progressViewSize);
    editViewSize(gamepadBackgroundView, gamepadBackgroundViewSize, gamepadBackgroundViewSize);
    editViewSize(gamepadImageSwitcher, imageSwitcherWidth, imageSwitcherHeight, false);
    editViewMarginTop(gamepadImageSwitcher, imageSwitcherMarginTop);

    gamepadBackgroundView.setAlpha(gamepadBackgroundAlpha);
    fireImageView.setAlpha(1f - percent);
}

/**
 * Edits the view size and requests layout.
 */
private void editViewSize(View view, int width, int height)
{
    editViewSize(view, width, height, true);
}

/**
 * Edits the view size and requests layout.
 */
private void editViewSize(View view, int width, int height, boolean requestLayout)
{
    if (view.getLayoutParams() != null)
    {
        view.getLayoutParams().width = width;
        view.getLayoutParams().height = height;
        if (requestLayout)
            view.requestLayout();
    }
}

/**
 * Edits the view's margin top attribute and requests layout.
 */
private void editViewMarginTop(View view, int marginTop)
{
    if (view.getLayoutParams() instanceof MarginLayoutParams)
    {
        ((MarginLayoutParams) view.getLayoutParams()).topMargin = marginTop;
        view.requestLayout();
    }
}

/**
 * Returns the calculated percentage value for start and end. Percent
 * should be between 0 and 1.
 *
 * @param start is the start value.
 * @param end is the end value.
 * @param percent is between 0 and 1.
 *
 * @return (end - start) / percent
 */
private float getValueByPercent(float start, float end, float percent)
{
    return start + ((end - start) * percent);
}

Любая оптимизация предложения? Альтернативы? Любая помощь приветствуется, большое спасибо.

1 Ответ

0 голосов
/ 02 марта 2020

Наличие фиксированной ширины и высоты в представлении DragLayer (он же root) решило большинство проблем: по-видимому, когда представление root оборачивает содержимое, изменение макета становится слишком экономичным. Поскольку я знал максимальную ширину и максимальную высоту для моих целевых анимаций, исправив root ширину и высоту вида, использование центрированного containerView в качестве wrap_content, похоже, решило проблемы.

...