android 5上的共享元素活动转换

时间:2014-10-25 19:29:21

标签: android android-animation android-recyclerview shared-element-transition activity-transition

我想在从一个Activity转到另一个Activity时设置共享元素转换。

第一个Activity有一个带物品的RecyclerView。单击某个项目时,该项目应为新活动设置动画。

所以我设定了一个         最终活动视图上的android:transitionName =“item”,以及recycler-view项目视图。

我在进行下一个活动时也使用此代码:

this.startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this, itemView, "boomrang_item").toBundle());

单击某个项目时,它会正确转换并显示新视图。真的很好。 但是当我点击后退按钮时。有时它工作正常,但大多数时候我的活动与以下堆栈跟踪崩溃:

   java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.ViewGroup.transformMatrixToGlobal(android.graphics.Matrix)' on a null object reference
            at android.view.GhostView.calculateMatrix(GhostView.java:95)
            at android.app.ActivityTransitionCoordinator$GhostViewListeners.onPreDraw(ActivityTransitionCoordinator.java:845)
            at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:847)
            at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1956)
            at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1054)
            at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5779)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:767)
            at android.view.Choreographer.doCallbacks(Choreographer.java:580)
            at android.view.Choreographer.doFrame(Choreographer.java:550)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:753)
            at android.os.Handler.handleCallback(Handler.java:739)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:135)
            at android.app.ActivityThread.main(ActivityThread.java:5221)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

我做错了什么? 它看起来像android 5中的一个错误

12 个答案:

答案 0 :(得分:8)

我遇到了同样的问题,并注意到当你回去时原来的共享元素在前一个屏幕上不再可见时会发生崩溃(可能它是画面中屏幕上的最后一个元素,但是一旦切换到横向,它就不会更长时间可见),因此过渡无处可回放共享元素。

我的解决方法是删除返回转换(在第二个活动中),如果屏幕在返回之前已经旋转,但我确信必须有更好的方法来处理这个:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    mOrientationChanged = !mOrientationChanged;
}

@Override
public void supportFinishAfterTransition() {
    if (mOrientationChanged) {
        /**
         * if orientation changed, finishing activity with shared element
         * transition may cause NPE if the original element is not visible in the returned
         * activity due to new orientation, we just finish without transition here
         */
        finish();
    } else {
        super.supportFinishAfterTransition();
    }
}

答案 1 :(得分:4)

尝试删除最终活动视图中可能包含的任何合并xml标记。我注意到转换到包含合并标记的视图,其中转换元素是合并标记的直接子元素,将导致此错误,但是我应该将合并标记替换为不同的容器,如CardView,动画工作得很好。还要确保视图中的transitionNames之间存在1:1的关系。

更新: 我在进行活动转换时再次遇到此问题,单击后退按钮返回初始活动,然后再次尝试转换。我通过id访问'transition component'的直接父级( A RelativeLayout ),使用findViewById()调用,然后调用removeAllViews()。我最后更改代码以在比父级更大的祖先上调用'removeAllViews()',还从页面加载后取代'transition component'的元素中删除了一个标记。这缓解了我的问题。

答案 2 :(得分:3)

如果您正在使用Proguard,请尝试将其添加到规则文件中。我有同样的问题,似乎有用吗?

-keep public class android.app.ActivityTransitionCoordinator

答案 3 :(得分:3)

确保第二个活动中的转换视图不是根布局。 您可以使用透明的windowBackground将其包装在FrameLayout中。

答案 4 :(得分:3)

我有同样的问题,对我而言,这是由于Recyclerview在第一次退出转换之后/期间执行更新。我认为共享元素视图有时会被回收,这意味着它将不再可用于过渡动画,因此崩溃(通常在返回过渡时,但有时在退出过渡时)。我通过阻止更新来解决它,如果活动暂停(使用isRunning标志) - 注意它是暂停但不停止,因为它仍然在后台可见。此外,如果转换正在运行,我阻止了更新过程。我发现它足以听取这个回调:

Transition sharedElementExitTransition = getWindow().getSharedElementExitTransition();
        if (sharedElementExitTransition != null) {
            sharedElementExitTransition.addListener(.....);
        }

作为最后一项措施,虽然我不确定这是否有所不同,但我在recyclerView.setLayoutFrozen(true) / recyclerView.setLayoutFrozen(false)中也onTransitionStart / onTransitionEnd

答案 5 :(得分:2)

确保" itemView"你在转换中传递的是点击的视图(在你的onClick()回调中收到)

答案 6 :(得分:1)

我想到的是避免使用RecyclerView转换回Activity,或者用其他东西改回转换。

禁用所有返回转换:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void finishAfterTransition() {
    finish();
}

或者,如果您只想禁用共享元素返回转换,并且能够设置自己的返回转换:

// Track if finishAfterTransition() was called
private boolean mFinishingAfterTransition;

@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mFinishingAfterTransition = false;
}

public boolean isFinishingAfterTransition() {
    return mFinishingAfterTransition;
}

@Override
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void finishAfterTransition() {
    mFinishingAfterTransition = true;
    super.finishAfterTransition();
}

public void clearSharedElementsOnReturn() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        TransitionUtilsLollipop.clearSharedElementsOnReturn(this);
    }
}

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static final class TransitionUtilsLollipop {

    private TransitionUtilsLollipop() {
        throw new UnsupportedOperationException();
    }

    static void clearSharedElementsOnReturn(@NonNull final BaseActivity activity) {
        activity.setEnterSharedElementCallback(new SharedElementCallback() {

            @Override
            public void onMapSharedElements(final List<String> names,
                    final Map<String, View> sharedElements) {
                super.onMapSharedElements(names, sharedElements);
                if (activity.isFinishingAfterTransition()) {
                    names.clear();
                    sharedElements.clear();
                }
            }
        });
    }

通过在基本活动中实现,您可以在onCreate()

中轻松使用它
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    clearSharedElementsOnReturn(this);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        // set your own transition
        getWindow().setReturnTransition(new VerticalGateTransition());
    }
}

答案 7 :(得分:1)

我有同样的错误,我的理由是hidro的回答背后的原因,但是由于键盘隐藏了过渡回归的共享元素。

我的解决方法是在完成活动之前以编程方式关闭键盘,这样前一个活动的共享元素就不会被遮挡。

View view = this.getCurrentFocus();
if (view != null) {  
    InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
supportFinishAfterTransition();

答案 8 :(得分:1)

正如@Fabio Rocha所述,请确保从itemView检索ViewHolder

您可以通过

按位置获取ViewHolder
mRecyclerView.findViewHolderForAdapterPosition(position);

答案 9 :(得分:1)

我遇到了同样的问题,实际上我使用了firebase并且我有信息列表,当用户点击它时会在此活动中使用sharedAnimation调用detailActivity我正在使用firebase更新它,因此firebase事件更新了列表项,在这种情况下,这个问题正在调用,因为回收者视图屏幕布局正在生效。

并且它调用了一个异常,因为我们已经传递了它的转换ID不再存在,所以我使用这个方法来解决这个问题。

onPause()我冻结了布局,onResume()将其设置为false;

 @Override
public void onPause() {
    super.onPause();
    mRecycler.setLayoutFrozen(true);
}

@Override
public void onResume() {
    super.onResume();
    mRecycler.setLayoutFrozen(false);
}

它正在发挥作用。

答案 10 :(得分:0)

原因其实很简单: 当您导航回父活动或片段时,视图还没有(可能有很多原因)。
因此,您要做的是推迟Enter转换,直到视图可用。

我的工作是在我的片段中的 onCreate()中调用以下函数(但也适用于Activity):

private void checkBeforeTransition() {
    // Postpone the transition until the window's decor view has
    // finished its layout.
    getActivity().supportPostponeEnterTransition();

    final View decor = getActivity().getWindow().getDecorView();
    decor.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            decor.getViewTreeObserver().removeOnPreDrawListener(this);
            getActivity().supportStartPostponedEnterTransition();
            return true;
        }
    });
}

答案 11 :(得分:0)

有同样的问题,它是由后台回收器视图更新引起的,当notifyItemChanged(int index)时,回收器视图将重新创建视图,因此共享视图被回收并且回来时崩溃。

我的解决方案称为recyclerView.setItemAnimator(null);,它将阻止回收者视图重新创建视图。