やりたいことは次の動画を見てもらうのが早いです。
言葉にすると、
1. Activity1 から Activity2 に遷移するときに、
2. ある View を sharedElement としてアニメーション(移動)させたい
3. Activity2 では sharedElement が移動している間に表示している内容を変更したい
Activity1 の方のレイアウトは ImageView 一つだけで、これが sharedElement の対象。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image"
android:layout_width="128dp"
android:layout_height="128dp"
android:layout_gravity="bottom"
android:src="@drawable/sample_image1"
android:transitionName="image"
tools:ignore="ContentDescription"/>
</FrameLayout>
Activity2 の方は ImageView が2つ重なっていて、一つ目の ImageView には Activity1 と同じ画像、二つ目の ImageView には Transition 後に表示したい画像がセットされている。二つ目の ImageView は非表示(INVISIBLE)。二つの ImageView の container である FrameLayout が sharedElement の対象。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<net.yanzm.sample.SquareFrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:transitionName="image">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/sample_image1"
tools:ignore="ContentDescription"/>
<ImageView
android:id="@+id/image2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/sample_image2"
android:visibility="invisible"
tools:ignore="ContentDescription"/>
</net.yanzm.sample.SquareFrameLayout>
</LinearLayout>
Activity1側で ActivityOptionsCompat.makeSceneTransitionAnimation() を使って Activity2 を呼び出すと、画面遷移時に SharedElement が移動します。
当たり前ですがこの段階では Activity2 側の二つ目の ImageView は出てきません(INVISIBLEなので)。
public class TransitionActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_transition);
findViewById(R.id.image).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
move(view);
}
});
}
private void move(View view) {
Intent intent = new Intent(this, TransitionActivity2.class);
final Bundle options = ActivityOptionsCompat
.makeSceneTransitionAnimation(this, view, "image")
.toBundle();
startActivity(intent, options);
}
}
そこで、独自の SharedElement 用 TransitionSet を作ります。
デフォルトはプラットフォームの @transition/move が指定されており、中身は次のようになっています。
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<changeBounds/>
<changeTransform/>
<changeClipBounds/>
<changeImageTransform/>
</transitionSet>
そこで、以下のようなクラスを用意しました。
public class CustomTransitionSet extends TransitionSet {
public CustomTransitionSet() {
addTransition(new ChangeBounds());
addTransition(new ChangeTransform());
addTransition(new ChangeClipBounds());
addTransition(new ChangeImageTransform());
addTransition(new CustomTransition().addTarget(R.id.image2));
}
}
デフォルトの設定 + CustomTransition を追加しています。 CustomTransition は Activity2 の二つ目の ImageView だけを対象にしたいので、addTarget で対象を絞っています。
この CustomTransitionSet を Activity2 で SharedElement 用の Transition としてセットします。
public class TransitionActivity2 extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setSharedElementEnterTransition(new CustomTransitionSet());
setContentView(R.layout.activity_transition2);
}
}
CustomTransition では Transition 開始時の view の Visibility を持っておいて、それが VISIBLE 以外だったらフェードアウト、VISIBLE だったらフェードインのアニメーションをするようにしました。
public class CustomTransition extends Transition {
// TransitionValues に追加するときのキーは パッケージ名:クラス名:プロパティ名
private static final String PROP_NAME_VISIBILITY = "net.yanzm.sample:CustomTransition:visibility";
@Override
public void captureStartValues(TransitionValues transitionValues) {
// visibility の値を持っておく
final View view = transitionValues.view;
transitionValues.values.put(PROP_NAME_VISIBILITY, view.getVisibility());
}
@Override
public void captureEndValues(TransitionValues transitionValues) {
// end の値は使わないので何もしない
}
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
if (startValues == null || startValues.view == null) {
return null;
}
final View view = startValues.view;
final int visibility = (int) startValues.values.get(PROP_NAME_VISIBILITY);
final boolean isEnter = visibility != View.VISIBLE;
view.setVisibility(View.VISIBLE);
view.setAlpha(isEnter ? 0f : 1f);
final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "alpha", isEnter ? 1f : 0f);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setAlpha(1f);
view.setVisibility(isEnter ? View.VISIBLE : View.INVISIBLE);
super.onAnimationEnd(animation);
}
});
return anim;
}
}
これで一番上に載せた動画のような動作になりました!