2016年11月18日金曜日

SharedElement をフェードインさせたい

やりたいことは次の動画を見てもらうのが早いです。



言葉にすると、
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; } } これで一番上に載せた動画のような動作になりました!


0 件のコメント:

コメントを投稿