问题 自定义循环显示转换在暂停时导致“java.lang.UnsupportedOperationException”?


我创建了一个自定义循环显示转换,用作Activity的enter转换的一部分(具体来说,我通过调用将窗口的转换设置为转换 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)

我有什么特别的原因得到这个错误吗?应该如何避免?


7876
2017-11-05 03:36


起源

我给了你关于G +上这种越野行为的观点解释! - Pavlos


答案:


您需要创建一个子类 Animator 无视调用 pause() 和 resume() 为了避免这种例外。

有关详细信息,我刚刚完成了有关此主题的帖子:


10
2017-11-07 01:35



非常感谢您的帮助!这似乎解决了我的问题。我编辑了你的答案,为链接提供了更多的上下文。 - Alex Lockwood
4年后,感谢这个金块。 - Hazam


我有什么特别的原因得到这个错误吗?

ViewAnimationUtils.createCircularReveal 是创建新的快捷方式 RevealAnimator,这是一个子类 RenderNodeAnimator。默认, RenderNodeAnimator.pause 抛出一个 UnsupportedOperationException。您会在堆栈跟踪中看到这种情况:

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

什么时候 Activity.onBackPressed 在棒棒糖中被召唤,它给了一个新的召唤 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允许您使用“后退”按钮中断,但清除过渡状态并使用反射。

这是一个很好的结果。 您可以看到它如何从“A”完全转换为“M”并再次返回,然后“后退”按钮中断转换并返回“A”。如果你看它会更有意义。

无论如何,我希望能帮助你解决一些问题。


3
2017-11-05 12:37



这两种方法都肯定会让人觉得惹神如果我不得不在两者之间做出选择,我可能更喜欢第一次,因为反射看起来有些可怕。谁知道清理转型国家的副作用可能是...... - Alex Lockwood
也许还有另一种方式......如果你想做的就是防止 Animator#pause() 从被称为,也许你可以简单地包装圆形揭示动画师 Animator 包装器子类,它转发所有方法调用 除  pause() 圆形揭示动画师。看起来比使用反射更安全,至少......但似乎仍然不是理想的解决方案。 - Alex Lockwood
@AlexLockwood是的,这似乎更好,但仍然不理想。无论哪种方式,很确定你是扔了 Exception 因为 Animator 最终回归 RenderNodeAnimator.pause。 - adneal


您可以添加侦听器以输入设置标志的转换 transitionInProgress 在方法中 onTransitionStart() / onTransitionEnd()。然后,您可以覆盖方法 finishAfterTransition() 然后检查 transitionInProgress 旗帜,并打电话 super 只有过渡结束。否则你可以 finish() 你的 Activity 或什么也不做。

override fun finishAfterTransition() {
    if (!transitionInProgress){
        super.finishAfterTransition()
    } else {
        finish()
    }
}

0
2017-10-10 09:21