LollipopのFragment Transitionがなかなか期待通りに動作せず、いろいろ試した結果をメモとして残します。
※自分用のworkaroundとしては使えていますが、とんでもない勘違いなどがあるかもしれませんのでご容赦ください。
Shared ElementがShared Elementにならない場合
Fragment間のトランジションでShared Elementを設定すると、Fragment全体のトランジションにオーバーレイするようなかたちで、特定のViewのみがFragmentをまたがって変化していくアニメーションを実装できるはずなのですが、条件が揃っていないとうまく動作しないようです。失敗している場合はViewがSharedElementになりません(前後のFragment内で個別にアニメーションが行われ、Fragment全体のトランジションの影響も受ける)。
原因
SharedElementに指定したViewは、トランジションの間のみ自動的にViewOverlayに描画される仕様のはずなのですが、そのように動作させるには次の条件を満たす必要があるようでした。
条件1: ChangeTransfromを使う
setSharedElementEnterTransitionで、ChangeTransfromを必ず適用します。
TransitionSet transitionSet = new TransitionSet();
ChangeBounds changeBounds = new ChangeBounds();
transitionSet.addTransition(changeBounds);
//不要でも追加
ChangeTransform changeTransform = new ChangeTransform();
transitionSet.addTransition(changeTransform);
fragment.setSharedElementEnterTransition(transitionSet);
なお、ViewOverlayへの描画をsetReparentWithOverlayで強制的にオフにすることで、正しい条件下でもSharedElementとして動作しなくなるのが確認できます(デフォルトはtrue)。
changeTransform.setReparentWithOverlay(false);
条件2: Viewの座標(View自身のマージンを含むグローバルな左上座標)が前後のFragmentで異なっているようにする
座標が変わらない場合はSharedElementにならないようです。前後のFragmentでViewの構造が似通っていて、SharedElementにしたいViewの位置がlayout_marginでしか差がついていないような場合は注意しないといけません。
これらの条件を満たしたうえで、その他の設定が正しく行われていればSharedElementとしてアニメーションが動作するはずです。
現在のFragmentがトランジション開始時に消えてしまう
FragmentTransactionのreplaceメソッドを使って画面遷移を行うと、現在のFragmentが実行した瞬間に消えてしまい、画面遷移らしいトランジションになりません。SharedElementに指定したViewは描画されますが、その他の要素が残らないのは期待とはちょっと違うような気がします。
トランジションの開始よりも先に、現在のFragmentのonDestroyViewが呼ばれてしまうのが理由のようですが、その回避方法は自分では見つけられませんでした。
対策としては、トランジション前のViewをキャプチャして、ビットマップを上位のコンテナの背景などに一時的に適用する方法などが考えられます。次のような感じです。
Transition fade = new Slide(Gravity.RIGHT);
prepareCaptureImage(targetViewGroup); //現在のFragmentをBitmap化して保存する
fade.addListener(new Transition.TransitionListener() {
@Override
public void onTransitionStart(Transition transition) {
setCaputreImageToContainer(upperViewGroup); //上位のViewGroupの背景などにBitmapを適用する
}
@Override
public void onTransitionEnd(Transition transition) {
upperViewGroup.setBackgroundResource(0); //背景解除
destroyCaptureImage(); //Bitmapを破棄
}
....