2016年11月18日金曜日

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

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



言葉にすると、
1. Activity1 から Activity2 に遷移するときに、
2. ある View を sharedElement としてアニメーション(移動)させたい
3. Activity2 では sharedElement が移動している間に表示している内容を変更したい

Activity1 の方のレイアウトは ImageView 一つだけで、これが sharedElement の対象。
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <FrameLayout  
  3.     xmlns:android="http://schemas.android.com/apk/res/android"  
  4.     xmlns:tools="http://schemas.android.com/tools"  
  5.     android:layout_width="match_parent"  
  6.     android:layout_height="match_parent">  
  7.   
  8.     <ImageView  
  9.         android:id="@+id/image"  
  10.         android:layout_width="128dp"  
  11.         android:layout_height="128dp"  
  12.         android:layout_gravity="bottom"  
  13.         android:src="@drawable/sample_image1"  
  14.         android:transitionName="image"  
  15.         tools:ignore="ContentDescription"/>  
  16.   
  17. </FrameLayout>  
Activity2 の方は ImageView が2つ重なっていて、一つ目の ImageView には Activity1 と同じ画像、二つ目の ImageView には Transition 後に表示したい画像がセットされている。二つ目の ImageView は非表示(INVISIBLE)。二つの ImageView の container である FrameLayout が sharedElement の対象。
  1. <!--xml version="1.0" encoding="utf-8"?-->  
  2. <?xml version="1.0" encoding="utf-8"?>  
  3. <LinearLayout  
  4.     xmlns:android="http://schemas.android.com/apk/res/android"  
  5.     xmlns:tools="http://schemas.android.com/tools"  
  6.     android:layout_width="match_parent"  
  7.     android:layout_height="match_parent"  
  8.     android:orientation="vertical">  
  9.   
  10.     <net.yanzm.sample.SquareFrameLayout  
  11.         android:layout_width="match_parent"  
  12.         android:layout_height="wrap_content"  
  13.         android:transitionName="image">  
  14.   
  15.         <ImageView  
  16.             android:id="@+id/image"  
  17.             android:layout_width="match_parent"  
  18.             android:layout_height="match_parent"  
  19.             android:src="@drawable/sample_image1"  
  20.             tools:ignore="ContentDescription"/>  
  21.   
  22.         <ImageView  
  23.             android:id="@+id/image2"  
  24.             android:layout_width="match_parent"  
  25.             android:layout_height="match_parent"  
  26.             android:src="@drawable/sample_image2"  
  27.             android:visibility="invisible"  
  28.             tools:ignore="ContentDescription"/>  
  29.   
  30.     </net.yanzm.sample.SquareFrameLayout>  
  31.   
  32. </LinearLayout>  
Activity1側で ActivityOptionsCompat.makeSceneTransitionAnimation() を使って Activity2 を呼び出すと、画面遷移時に SharedElement が移動します。 当たり前ですがこの段階では Activity2 側の二つ目の ImageView は出てきません(INVISIBLEなので)。
  1. public class TransitionActivity extends AppCompatActivity {  
  2.   
  3.     @Override  
  4.     protected void onCreate(@Nullable Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_transition);  
  7.   
  8.         findViewById(R.id.image).setOnClickListener(new View.OnClickListener() {  
  9.             @Override  
  10.             public void onClick(View view) {  
  11.                 move(view);  
  12.             }  
  13.         });  
  14.     }  
  15.   
  16.     private void move(View view) {  
  17.         Intent intent = new Intent(this, TransitionActivity2.class);  
  18.         final Bundle options = ActivityOptionsCompat  
  19.                 .makeSceneTransitionAnimation(this, view, "image")  
  20.                 .toBundle();  
  21.         startActivity(intent, options);  
  22.     }  
  23. }  




そこで、独自の SharedElement 用 TransitionSet を作ります。

デフォルトはプラットフォームの @transition/move が指定されており、中身は次のようになっています。
  1. <transitionSet xmlns:android="http://schemas.android.com/apk/res/android">  
  2.     <changeBounds/>  
  3.     <changeTransform/>  
  4.     <changeClipBounds/>  
  5.     <changeImageTransform/>  
  6. </transitionSet>  
そこで、以下のようなクラスを用意しました。
  1. public class CustomTransitionSet extends TransitionSet {  
  2.   
  3.     public CustomTransitionSet() {  
  4.         addTransition(new ChangeBounds());  
  5.         addTransition(new ChangeTransform());  
  6.         addTransition(new ChangeClipBounds());  
  7.         addTransition(new ChangeImageTransform());  
  8.         addTransition(new CustomTransition().addTarget(R.id.image2));  
  9.     }  
  10. }  
デフォルトの設定 + CustomTransition を追加しています。 CustomTransition は Activity2 の二つ目の ImageView だけを対象にしたいので、addTarget で対象を絞っています。
この CustomTransitionSet を Activity2 で SharedElement 用の Transition としてセットします。
  1. public class TransitionActivity2 extends AppCompatActivity {  
  2.   
  3.     @Override  
  4.     protected void onCreate(@Nullable Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.   
  7.         getWindow().setSharedElementEnterTransition(new CustomTransitionSet());  
  8.   
  9.         setContentView(R.layout.activity_transition2);  
  10.     }  
  11. }  
CustomTransition では Transition 開始時の view の Visibility を持っておいて、それが VISIBLE 以外だったらフェードアウト、VISIBLE だったらフェードインのアニメーションをするようにしました。
  1. public class CustomTransition extends Transition {  
  2.   
  3.     // TransitionValues に追加するときのキーは パッケージ名:クラス名:プロパティ名  
  4.     private static final String PROP_NAME_VISIBILITY = "net.yanzm.sample:CustomTransition:visibility";  
  5.   
  6.     @Override  
  7.     public void captureStartValues(TransitionValues transitionValues) {  
  8.         // visibility の値を持っておく  
  9.         final View view = transitionValues.view;  
  10.         transitionValues.values.put(PROP_NAME_VISIBILITY, view.getVisibility());  
  11.     }  
  12.   
  13.     @Override  
  14.     public void captureEndValues(TransitionValues transitionValues) {  
  15.         // end の値は使わないので何もしない  
  16.     }  
  17.   
  18.     @Override  
  19.     public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {  
  20.         if (startValues == null || startValues.view == null) {  
  21.             return null;  
  22.         }  
  23.   
  24.         final View view = startValues.view;  
  25.         final int visibility = (int) startValues.values.get(PROP_NAME_VISIBILITY);  
  26.         final boolean isEnter = visibility != View.VISIBLE;  
  27.   
  28.         view.setVisibility(View.VISIBLE);  
  29.         view.setAlpha(isEnter ? 0f : 1f);  
  30.   
  31.         final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "alpha", isEnter ? 1f : 0f);  
  32.         anim.addListener(new AnimatorListenerAdapter() {  
  33.             @Override  
  34.             public void onAnimationEnd(Animator animation) {  
  35.                 view.setAlpha(1f);  
  36.                 view.setVisibility(isEnter ? View.VISIBLE : View.INVISIBLE);  
  37.                 super.onAnimationEnd(animation);  
  38.             }  
  39.         });  
  40.         return anim;  
  41.     }  
  42. }  
これで一番上に載せた動画のような動作になりました!


0 件のコメント:

コメントを投稿