【问题标题】:Android complex animation skips frames on view size changesAndroid 复杂动画在视图大小更改时跳过帧
【发布时间】:2020-03-02 19:41:38
【问题描述】:

我正在尝试创建一个有点复杂的动画,其中涉及同一布局中的多个视图。视图将改变大小、不透明度,并且有一个需要同时播放的图像切换器。我已经设法通过使用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. animationView 需要将尺寸从 0dp 更改为 60dp(0x0 到 60x60),
  2. progressView 需要将尺寸从 0dp 更改为 50dp(0x0 到 50x50),
  3. gamepadBackgroundView 需要将大小从 28dp 更改为 45dp(28x28 到 45x45),并且其 alpha 需要从 0.35f 更改为 1f。
  4. gamepadImageSwitcher 需要将尺寸从 14x9 更改为 24x16,并且内部图像也需要更改为 setImageResource。它的动画是淡出 --> 淡入动画。
  5. gamepadImageSwitcher 的marginTop 需要在动画播放时移除,从 5dp 到 0。
  6. 由于这些更改,containerView 和 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);
}

有什么优化建议吗?备择方案?任何帮助表示赞赏,非常感谢。

【问题讨论】:

    标签: android android-animation


    【解决方案1】:

    在DragLayer(又名根视图)上具有固定的宽度和高度解决了大部分问题:显然,当根视图包装内容时,重新布局变得太划算。由于我知道目标动画的最大宽度和最大高度,固定根视图的宽度和高度,然后使用居中的containerView 作为wrap_content 似乎已经解决了问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-10-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多