Настраиваемый круговой вывод результатов перехода в "java.lang.UnsupportedOperationException", когда пауза?

Я создал собственный циклический переход для раскрытия, который будет использоваться как часть перехода ввода в Деятельности (в частности, я устанавливаю переход как переход ввода в окне, вызывая Window#setEnterTransition()):

public class CircularRevealTransition extends Visibility {
    private final Rect mStartBounds = new Rect();

    /**
     * Use the view's location as the circular reveal's starting position.
     */
    public CircularRevealTransition(View v) {
        int[] loc = new int[2];
        v.getLocationInWindow(loc);
        mStartBounds.set(loc[0], loc[1], loc[0] + v.getWidth(), loc[1] + v.getHeight());
    }

    @Override
    public Animator onAppear(ViewGroup sceneRoot, final View v, TransitionValues startValues, TransitionValues endValues) {
        if (endValues == null) {
            return null;
        }
        int halfWidth = v.getWidth() / 2;
        int halfHeight = v.getHeight() / 2;
        float startX = mStartBounds.left + mStartBounds.width() / 2 - halfWidth;
        float startY = mStartBounds.top + mStartBounds.height() / 2 - halfHeight;
        float endX = v.getTranslationX();
        float endY = v.getTranslationY();
        v.setTranslationX(startX);
        v.setTranslationY(startY);

        // Create a circular reveal animator to play behind a shared
        // element during the Activity Transition.
        Animator revealAnimator = ViewAnimationUtils.createCircularReveal(v, halfWidth, halfHeight, 0f,
                FloatMath.sqrt(halfWidth * halfHeight + halfHeight * halfHeight));
        revealAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                // Set the view's visibility to VISIBLE to prevent the
                // reveal from "blinking" at the end of the animation.
                v.setVisibility(View.VISIBLE);
            }
        });

        // Translate the circular reveal into place as it animates.
        PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("translationX", startX, endX);
        PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("translationY", startY, endY);
        Animator translationAnimator = ObjectAnimator.ofPropertyValuesHolder(v, pvhX, pvhY);

        AnimatorSet anim = new AnimatorSet();
        anim.setInterpolator(getInterpolator());
        anim.playTogether(revealAnimator, translationAnimator);
        return anim;
    }
}

Это нормально работает нормально. Однако, когда я нажимаю кнопку "Назад" в середине перехода, я получаю следующее исключение:

Process: com.adp.activity.transitions, PID: 13800
java.lang.UnsupportedOperationException
        at android.view.RenderNodeAnimator.pause(RenderNodeAnimator.java:251)
        at android.animation.AnimatorSet.pause(AnimatorSet.java:472)
        at android.transition.Transition.pause(Transition.java:1671)
        at android.transition.TransitionSet.pause(TransitionSet.java:483)
        at android.app.ActivityTransitionState.startExitBackTransition(ActivityTransitionState.java:269)
        at android.app.Activity.finishAfterTransition(Activity.java:4672)
        at com.adp.activity.transitions.DetailsActivity.finishAfterTransition(DetailsActivity.java:167)
        at android.app.Activity.onBackPressed(Activity.java:2480)

Есть ли какая-то конкретная причина, почему я получаю эту ошибку? Как этого следует избегать?

3 ответа

Решение

Вам нужно будет создать подкласс Animator который игнорирует призывы к pause() а также resume() во избежание этого исключения.

Для более подробной информации, я только что закончил пост на эту тему ниже:

Есть ли какая-то конкретная причина, почему я получаю эту ошибку?

ViewAnimationUtils.createCircularReveal это ярлык для создания нового RevealAnimator, который является подклассом RenderNodeAnimator, По умолчанию, RenderNodeAnimator.pause бросает UnsupportedOperationException, Вы видите, что это происходит здесь в вашей трассировке стека:

java.lang.UnsupportedOperationException
        at android.view.RenderNodeAnimator.pause(RenderNodeAnimator.java:251)

когда Activity.onBackPressed вызывается в Lollipop, он делает новый вызов Activity.finishAfterTransition, который в конечном итоге делает обратный звонок Animator.pause в Transition.pause(android.view.View), когда ваш UnsupportedOperationException наконец брошен

Причина, по которой она не выбрасывается при использовании кнопки "назад" после завершения перехода, связана с тем, что EnterTransitionCoordinator обрабатывает входящие Transition как только это будет завершено.

Как этого следует избегать?

Я полагаю, у вас есть несколько вариантов, но ни один из них не идеален:

Опция 1

Прикрепить TransitionListener когда ты звонишь Window.setEnterTransition так что вы можете отслеживать, когда вызывать кнопку "назад". Итак, что-то вроде:

public class YourActivity extends Activity {

    /** True if the current window transition is animating, false otherwise */
    private boolean mIsAnimating = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Get the Window and enable Activity transitions
        final Window window = getWindow();
        window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
        // Call through to super
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_child);

        // Set the window transition and attach our listener
        final Transition circularReveal = new CircularRevealTransition(yourView);
        window.setEnterTransition(circularReveal.addListener(new TransitionListenerAdapter() {

            @Override
            public void onTransitionEnd(Transition transition) {
                super.onTransitionEnd(transition);
                mIsAnimating = false;
            }

        }));

        // Restore the transition state if available
        if (savedInstanceState != null) {
            mIsAnimating = savedInstanceState.getBoolean("key");
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        // Save the current transition state
        outState.putBoolean("key", mIsAnimating);
    }

    @Override
    public void onBackPressed() {
        if (!mIsAnimating) {
            super.onBackPressed();
        }
    }

}

Вариант 2

Используйте отражение, чтобы позвонить ActivityTransitionState.clear, который остановит Transition.pause(android.view.View) от вызова в ActivityTransitionState.startExitBackTransition,

@Override
public void onBackPressed() {
    if (!mIsAnimating) {
        super.onBackPressed();
    } else {
        clearTransitionState();
        super.onBackPressed();
    }
}

private void clearTransitionState() {
    try {
        // Get the ActivityTransitionState Field
        final Field atsf = Activity.class.getDeclaredField("mActivityTransitionState");
        atsf.setAccessible(true);
        // Get the ActivityTransitionState
        final Object ats = atsf.get(this);
        // Invoke the ActivityTransitionState.clear Method
        final Method clear = ats.getClass().getDeclaredMethod("clear", (Class[]) null);
        clear.invoke(ats);
    } catch (final Exception ignored) {
        // Nothing to do
    }
}

Очевидно, у каждого есть недостатки. Вариант 1 в основном отключает кнопку "назад", пока переход не будет завершен. Вариант 2 позволяет прерывать с помощью кнопки "назад", но очищает переходное состояние и использует отражение.

Вот пример результатов. Вы можете видеть, как он полностью переходит от "А" к "М" и обратно, затем кнопка "назад" прерывает переход и возвращается к "А". Это будет иметь больше смысла, если вы посмотрите это.

В любом случае, я надеюсь, что это поможет вам.

Вы можете добавить слушателя, чтобы ввести переход, который устанавливает флаг transitionInProgress в методах onTransitionStart() / onTransitionEnd(), Затем вы можете переопределить метод finishAfterTransition() а затем проверьте transitionInProgress флаг и вызов super только если переход закончен. В противном случае вы можете просто finish() ваш Activity или ничего не делать.

override fun finishAfterTransition() {
    if (!transitionInProgress){
        super.finishAfterTransition()
    } else {
        finish()
    }
}
Другие вопросы по тегам