web-dev-qa-db-ja.com

Android 5での共有要素アクティビティの遷移

あるアクティビティから別のアクティビティに移動するときに、共有要素の遷移を設定したかったのです。

最初のアクティビティには、アイテムを含むRecyclerViewがあります。アイテムがクリックされると、そのアイテムは新しいアクティビティにアニメーション化されます。

そのため、リサイクラービューのアイテムビューと同様に、両方の最終アクティビティビューにAndroid:transitionName = "item"を設定しました。

次のアクティビティに進むときにも、このコードを使用しています。

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のバグのようです

36
TjerkW

同じ問題が発生し、戻ったときに元の共有要素が前の画面に表示されなくなった場合にクラッシュが発生することに気付きました(おそらく、縦向きの画面の最後の要素ですが、横向きに切り替えると表示されなくなります) 、したがって、遷移には共有要素を戻す場所がありません。

私の回避策は、戻る前に画面が回転している場合、(2番目のアクティビティで)リターントランジションを削除することですが、これを処理するためのより良い方法があるはずです:

@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();
    }
}
8
hidro

最終アクティビティのビューにある可能性のあるマージxmlタグを削除してみてください。遷移要素がマージタグの直接の子であるマージタグを含むビューに遷移すると、このエラーが発生することに気付きましたが、マージタグをCardViewなどの別のコンテナに置き換える必要があります。アニメーション正常に動作します。また、ビューのtransitionNames間に1:1の関係があることを確認してください。

更新:アクティビティの移行を実行し、[戻る]ボタンをクリックして最初のアクティビティに戻り、移行を再試行すると、この問題がもう一度発生しました。私は、findViewById()呼び出しを使用して、idで「遷移コンポーネント」の直接の親(ArelativeLayout)にアクセスし、次にremoveAllViews()を呼び出していました。親よりも大きい祖先で「removeAllViews()」を呼び出すようにコードを変更し、ページの読み込み後に「遷移コンポーネント」の代わりになるタグも要素から削除しました。これは私の問題を軽減しました。

4
DanielGaffey

Proguardを使用している場合は、これをルールファイルに追加してみてください。私は同じ問題を抱えていました、そしてそれはうまくいくように見えますか?

-keep public class Android.app.ActivityTransitionCoordinator

3
Sababado

2番目のアクティビティで移行するビューがルートレイアウトではないことを確認してください。透明なwindowBackgroundを使用してFrameLayoutでラップするだけです。

3
Tinashe

これと同じ問題が発生しました。最初の終了遷移後/遷移中にrecyclerviewが更新を実行したことが原因でした。その後、共有要素ビューがリサイクルされることがあったと思います。つまり、トランジションアニメーションで使用できなくなり、クラッシュします(通常はリターントランジションで、場合によってはエグジットトランジションで)。アクティビティが一時停止している場合(isRunningフラグを使用)に更新をブロックすることで解決しました-一時停止していましたが、バックグラウンドでまだ表示されていたため停止していませんでした。さらに、移行が実行されている場合は、更新プロセスをブロックしました。私はこのコールバックを聞くのに十分だと思いました:

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

最後の対策として、これが違いを生んだかどうかはわかりませんが、onTransitionStart/onTransitionEndrecyclerView.setLayoutFrozen(true)/recyclerView.setLayoutFrozen(false)も実行しました。

3
hmac

同じ問題に直面しました。実際にfirebaseを使用し、情報のリストがあります。ユーザーがタップすると、このアクティビティでsharedAnimationでdetailActivityが呼び出されます。firebaseを使用して更新していたので、firebaseイベントでリストアイテムが更新されます。リサイクラーがその画面を表示するため、この問題が発生している場合layoutが影響を受けていました。

渡したトランジションIDがなくなったため、例外が発生するため、このメソッドを使用してこの問題を解決します。

onPause()レイアウトをフリーズし、onResume()でfalseに設定しました;

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

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

そして、それは機能しています。

2
Abdul Rizwan

トランジションで渡す「itemView」がクリックされたビューであることを確認してください(onClick()コールバックで受信)

2
Fabio Rocha

@ Fabio Rocha が言ったように、itemViewViewHolderから取得されていることを確認してください。

ViewHolderは、次の方法で位置ごとに取得できます。

mRecyclerView.findViewHolderForAdapterPosition(position);
1
Chaos

これと同じエラーが発生しました。hidroの回答の背後にある同じ理由が原因でしたが、トランジションが戻る共有要素をキーボードが非表示にしたことが原因でした。

私の回避策は、アクティビティを終了する直前にプログラムでキーボードを閉じて、前のアクティビティの共有要素が隠されないようにすることでした。

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

私が思いついたのは、RecyclerViewを使用してアクティビティに戻ることや、他の何かを使用して遷移を元に戻すことを回避することです。

すべてのリターントランジションを無効にします。

@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());
    }
}
1

この理由は実際には非常に単純です。親のアクティビティまたはフラグメントに戻ると、ビューはまだありません(多くの理由が考えられます)。
それで、あなたがしたいことはビューが利用可能になるまでEnterトランジションを延期することです。

私の回避策は、フラグメントのonCreate()で次の関数を呼び出すことです(ただし、アクティビティでも機能します)。

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;
        }
    });
}
0
Andy Strife

同じ問題が発生し、バックグラウンドでのリサイクラービューの更新が原因で、notifyItemChanged(int index)時にリサイクラービューがビューを再作成するため、共有ビューがリサイクルされ、戻ったときにクラッシュしました。

私の解決策はrecyclerView.setItemAnimator(null);を呼び出すことです。これにより、リサイクラービューがビューを再作成できなくなります。

0
Glorin