Fragment API には、モーション エフェクトと変換を使用して、ナビゲーション中にフラグメントを視覚的に接続する方法が 2 つあります。その 1 つがアニメーション フレームワークで、Animation
と Animator
の両方を使用します。もう 1 つは、共有要素の遷移を含む遷移フレームワークです。
フラグメントの開始と終了、フラグメント間の共有要素の遷移には、カスタム エフェクトを指定できます。
- enter エフェクトは、フラグメントがどのように画面に入るかを決定します。たとえば、画面に移動してきたときに画面の端からフラグメントをスライドインさせるためのエフェクトを作成できます。
- exit エフェクトは、フラグメントがどのように画面から出るかを決定します。たとえば、画面から離れていくときにフラグメントをフェードアウトするためのエフェクトを作成できます。
- 共有要素の遷移は、2 つのフラグメント間で共有されているビューが、それらのフラグメント間でどのように移動するかを決定します。たとえば、フラグメント A の
ImageView
に表示されている画像は、フラグメント B が表示されると B に遷移します。
アニメーションを設定する
まず、新しいフラグメントに移動したときに実行される enter エフェクトと exit エフェクトのためのアニメーションを作成する必要があります。アニメーションは、トゥイーン アニメーション リソースとして定義できます。これらのリソースを使用して、アニメーション中のフラグメントの回転、拡大、フェード、移動の方法を定義できます。たとえば、現在のフラグメントをフェードアウトして、新しいフラグメントを画面の右端からスライドインさせることができます。図 1 をご覧ください。
これらのアニメーションは、res/anim
ディレクトリで定義できます。
<!-- res/anim/fade_out.xml -->
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromAlpha="1"
android:toAlpha="0" />
<!-- res/anim/slide_in.xml -->
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromXDelta="100%"
android:toXDelta="0%" />
また、バックスタックをポップすると実行される enter エフェクトと exit エフェクトのアニメーションを指定することもできます。このアニメーションは、ユーザーが [上へ] ボタンまたは [戻る] ボタンをタップしたときに表示させることができます。これらを popEnter
アニメーションと popExit
アニメーションと呼びます。たとえば、ユーザーが前の画面に戻るときに、現在のフラグメントを画面の右端からスライドアウトさせて前のフラグメントをフェードインさせることができます。
これらのアニメーションは次のように定義できます。
<!-- res/anim/slide_out.xml -->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromXDelta="0%"
android:toXDelta="100%" />
<!-- res/anim/fade_in.xml -->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:interpolator="@android:anim/decelerate_interpolator"
android:fromAlpha="0"
android:toAlpha="1" />
定義したアニメーションを FragmentTransaction.setCustomAnimations()
を呼び出して使用し、そのリソース ID でアニメーション リソースに渡します。次の例をご覧ください。
Kotlin
supportFragmentManager.commit { setCustomAnimations( R.anim.slide_in, // enter R.anim.fade_out, // exit R.anim.fade_in, // popEnter R.anim.slide_out // popExit ) replace(R.id.fragment_container, fragment) addToBackStack(null) }
Java
Fragment fragment = new FragmentB(); getSupportFragmentManager().beginTransaction() .setCustomAnimations( R.anim.slide_in, // enter R.anim.fade_out, // exit R.anim.fade_in, // popEnter R.anim.slide_out // popExit ) .replace(R.id.fragment_container, fragment) .addToBackStack(null) .commit();
遷移を設定する
遷移を使用して enter エフェクトと exit エフェクトを定義することもできます。これらの遷移は、XML リソース ファイルで定義できます。たとえば、現在のフラグメントをフェードアウトし、新しいフラグメントを画面の右端からスライドインさせるとします。これらの遷移は次のように定義できます。
<!-- res/transition/fade.xml -->
<fade xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"/>
<!-- res/transition/slide_right.xml -->
<slide xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_shortAnimTime"
android:slideEdge="right" />
遷移を定義したら、開始フラグメントで setEnterTransition()
を、終了フラグメントで setExitTransition()
を呼び出して適用し、そのリソース ID でインフレートされた遷移リソースを渡します。次の例をご覧ください。
Kotlin
class FragmentA : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val inflater = TransitionInflater.from(requireContext()) exitTransition = inflater.inflateTransition(R.transition.fade) } } class FragmentB : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val inflater = TransitionInflater.from(requireContext()) enterTransition = inflater.inflateTransition(R.transition.slide_right) } }
Java
public class FragmentA extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TransitionInflater inflater = TransitionInflater.from(requireContext()); setExitTransition(inflater.inflateTransition(R.transition.fade)); } } public class FragmentB extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TransitionInflater inflater = TransitionInflater.from(requireContext()); setEnterTransition(inflater.inflateTransition(R.transition.slide_right)); } }
フラグメントは AndroidX の遷移をサポートしています。 フラグメントはフレームワークの遷移もサポートしていますが、AndroidX の遷移を使用することを強くおすすめします。AndroidX の遷移は API レベル 14 以上でサポートされており、古いバージョンのフレームワークの遷移にはないバグ修正を含んでいるためです。
共有要素の遷移を使用する
遷移フレームワークの一部である共有要素の遷移は、フラグメントの遷移中に対応するビューが 2 つのフラグメント間をどのように移動するかを決定します。たとえば、図 3 に示すように、フラグメント A の ImageView
に表示される画像を、フラグメント B が表示されたときに B に遷移させることができます。
共有要素を使用してフラグメントの遷移を行う手順の概要は次のとおりです。
- 各共有要素ビューに一意の遷移名を割り当てます。
FragmentTransaction
に共有要素ビューと遷移名を追加します。- 共有要素の遷移アニメーションを設定します。
まず、各共有要素ビューに一意の遷移名を割り当て、ビューをあるフラグメントから次のフラグメントにマッピングできるようにする必要があります。ViewCompat.setTransitionName()
を使用して、各フラグメント レイアウトの共有要素の遷移名を設定します。これにより、API レベル 14 以上で互換性が確保されます。たとえば、フラグメント A と B の ImageView
の遷移名を次のように割り当てることができます。
Kotlin
class FragmentA : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { ... val itemImageView = view.findViewById<ImageView>(R.id.item_image) ViewCompat.setTransitionName(itemImageView, “item_image”) } } class FragmentB : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { ... val heroImageView = view.findViewById<ImageView>(R.id.hero_image) ViewCompat.setTransitionName(heroImageView, “hero_image”) } }
Java
public class FragmentA extends Fragment { @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { ... ImageView itemImageView = view.findViewById(R.id.item_image); ViewCompat.setTransitionName(itemImageView, “item_image”); } } public class FragmentB extends Fragment { @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { ... ImageView heroImageView = view.findViewById(R.id.hero_image); ViewCompat.setTransitionName(heroImageView, “hero_image”); } }
フラグメントの遷移に共有要素を含めるには、FragmentTransaction
でそれぞれの共有要素ビューがフラグメント間でどのようにマッピングされるかを把握する必要があります。FragmentTransaction.addSharedElement()
を呼び出して、ビューと対応するビューの遷移名を次のフラグメントに渡すことで、各共有要素を FragmentTransaction
に追加します。次の例をご覧ください。
Kotlin
val fragment = FragmentB() supportFragmentManager.commit { setCustomAnimations(...) addSharedElement(itemImageView, “hero_image”) replace(R.id.fragment_container, fragment) addToBackStack(null) }
Java
Fragment fragment = new FragmentB(); getSupportFragmentManager().beginTransaction() .setCustomAnimations(...) .addSharedElement(itemImageView, “hero_image”) .replace(R.id.fragment_container, fragment) .addToBackStack(null) .commit();
共有要素がフラグメント間を遷移する方法を指定するには、移動先のフラグメントに enter 遷移を設定する必要があります。フラグメントの onCreate()
メソッドで Fragment.setSharedElementEnterTransition()
を呼び出します。次の例をご覧ください。
Kotlin
class FragmentB : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) sharedElementEnterTransition = TransitionInflater.from(requireContext()) .inflateTransition(R.transition.shared_image) } }
Java
public class FragmentB extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Transition transition = TransitionInflater.from(requireContext()) .inflateTransition(R.transition.shared_image); setSharedElementEnterTransition(transition); } }
shared_image
遷移は次のように定義されます。
<!-- res/transition/shared_image.xml -->
<transitionSet>
<changeImageTransform />
</transitionSet>
共有要素の遷移として、Transition
のすべてのサブクラスがサポートされています。カスタム Transition
を作成する場合は、カスタムの遷移アニメーションの作成をご覧ください。先の例で使用した changeImageTransform
は、利用可能な事前ビルド済み変換の 1 つです。Transition
クラスの API リファレンスには、その他の Transition
サブクラスもあります。
デフォルトでは、共有要素の enter 遷移は、共有要素の return 遷移としても使用されます。return 遷移は、フラグメント トランザクションがバックスタックからポップされたときに、共有要素がどのように前のフラグメントに戻るかを決定します。別の return 遷移を指定するには、フラグメントの onCreate()
メソッドで Fragment.setSharedElementReturnTransition()
を使用します。
予測型「戻る」互換性
予測型「戻る」は多くのクロス フラグメント アニメーションで使用できます(すべてではありません)。 予測型「戻る」を実装する場合は、次の点に留意してください。
Transitions 1.5.0
以降とFragments 1.7.0
以降をインポートします。Animator
クラス、サブクラス、AndroidX Transition ライブラリは次のとおりです。 サポートされません。Animation
クラスとフレームワークTransition
ライブラリはサポートされていません。- 予測フラグメント アニメーションは、Android 14 以降を搭載したデバイスでのみ機能します。 高くなります。
setCustomAnimations
さん、setEnterTransition
さん、setExitTransition
さん、setReenterTransition
、setReturnTransition
、setSharedElementEnterTransition
、setSharedElementReturnTransition
は 予測型「戻る」に対応しています
詳しくは以下をご覧ください。 予測型「戻る」アニメーションのサポートを追加します。
遷移を延期する
場合によっては、フラグメントの遷移を短時間延期する必要が生じることもあります。たとえば、Android が遷移の開始状態と終了状態を正確に取得できるよう、開始フラグメント内のすべてのビューが測定、配置されるまで待機しなければならない、といった場合です。
また、必要なデータが読み込まれるまで遷移を延期しなければならない、ということもあります。たとえば、共有要素の画像が読み込まれるまで待機が必要になることがあります。待機しないと、遷移中や遷移後に画像の読み込みが完了したときに遷移がゆがむ可能性があります。
遷移を延期するには、まずフラグメント トランザクションでフラグメントの状態の変化を並べ替えられるようにする必要があります。フラグメントの状態の変化を並べ替えられるようにするには、FragmentTransaction.setReorderingAllowed()
を呼び出します。次の例をご覧ください。
Kotlin
val fragment = FragmentB() supportFragmentManager.commit { setReorderingAllowed(true) setCustomAnimation(...) addSharedElement(view, view.transitionName) replace(R.id.fragment_container, fragment) addToBackStack(null) }
Java
Fragment fragment = new FragmentB(); getSupportFragmentManager().beginTransaction() .setReorderingAllowed(true) .setCustomAnimations(...) .addSharedElement(view, view.getTransitionName()) .replace(R.id.fragment_container, fragment) .addToBackStack(null) .commit();
開始遷移を延期するには、開始フラグメントの onViewCreated()
メソッドで Fragment.postponeEnterTransition()
を呼び出します。
Kotlin
class FragmentB : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { ... postponeEnterTransition() } }
Java
public class FragmentB extends Fragment { @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { ... postponeEnterTransition(); } }
データを読み込み、遷移を開始する準備ができたら、Fragment.startPostponedEnterTransition()
を呼び出します。次の例では、Glide ライブラリを使用して画像を共有 ImageView
に読み込み、画像の読み込みが完了するまで対応する遷移を延期します。
Kotlin
class FragmentB : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { ... Glide.with(this) .load(url) .listener(object : RequestListener<Drawable> { override fun onLoadFailed(...): Boolean { startPostponedEnterTransition() return false } override fun onResourceReady(...): Boolean { startPostponedEnterTransition() return false } }) .into(headerImage) } }
Java
public class FragmentB extends Fragment { @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { ... Glide.with(this) .load(url) .listener(new RequestListener<Drawable>() { @Override public boolean onLoadFailed(...) { startPostponedEnterTransition(); return false; } @Override public boolean onResourceReady(...) { startPostponedEnterTransition(); return false; } }) .into(headerImage) } }
ユーザーのインターネット接続が低速の場合などは、すべてのデータが読み込まれるのを待つのではなく、一定時間が経過した後に延期された遷移を開始した方がよい場合もあります。このような場合は、代わりに開始フラグメントの onViewCreated()
メソッドで Fragment.postponeEnterTransition(long, TimeUnit)
を呼び出して、継続時間と時間の単位を渡します。そうすると、指定された時間が経過したときに、延期された遷移が自動的に開始されます。
RecyclerView
とともに共有要素の遷移を使用する
延期された開始遷移は、開始フラグメント内のすべてのビューが測定されて配置されるまで開始しないでください。RecyclerView
を使用する場合は、データが読み込まれ、RecyclerView
のアイテムが描画可能な状態になるまで待ってから遷移を開始する必要があります。次に例を示します。
Kotlin
class FragmentA : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { postponeEnterTransition() // Wait for the data to load viewModel.data.observe(viewLifecycleOwner) { // Set the data on the RecyclerView adapter adapter.setData(it) // Start the transition once all views have been // measured and laid out (view.parent as? ViewGroup)?.doOnPreDraw { startPostponedEnterTransition() } } } }
Java
public class FragmentA extends Fragment { @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { postponeEnterTransition(); final ViewGroup parentView = (ViewGroup) view.getParent(); // Wait for the data to load viewModel.getData() .observe(getViewLifecycleOwner(), new Observer<List<String>>() { @Override public void onChanged(List<String> list) { // Set the data on the RecyclerView adapter adapter.setData(it); // Start the transition once all views have been // measured and laid out parentView.getViewTreeObserver() .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw(){ parentView.getViewTreeObserver() .removeOnPreDrawListener(this); startPostponedEnterTransition(); return true; } }); } }); } }
ViewTreeObserver.OnPreDrawListener
がフラグメント ビューの親に設定されていることに注意してください。これは、すべてのフラグメントのビューが測定、配置され、描画可能な状態になってから、延期された開始遷移を開始するためです。
RecyclerView
で共有要素の遷移を使用する場合のもう 1 つのポイントは、RecyclerView
アイテムの XML レイアウトで遷移名を設定できないことです。これは、任意の数のアイテムがそのレイアウトを共有するためです。遷移アニメーションが正しいビューを使用するように、一意の遷移名を割り当てる必要があります。
各アイテムの共有要素に一意の遷移名を付けるには、ViewHolder
がバインドされたときにアイテムに一意の遷移名を割り当てます。たとえば、各アイテムデータに一意の ID が含まれている場合、そのアイテムを遷移名として使用することもできます。次の例をご覧ください。
Kotlin
class ExampleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val image = itemView.findViewById<ImageView>(R.id.item_image) fun bind(id: String) { ViewCompat.setTransitionName(image, id) ... } }
Java
public class ExampleViewHolder extends RecyclerView.ViewHolder { private final ImageView image; ExampleViewHolder(View itemView) { super(itemView); image = itemView.findViewById(R.id.item_image); } public void bind(String id) { ViewCompat.setTransitionName(image, id); ... } }
参考情報
フラグメントの遷移について詳しくは、以下の参考情報をご覧ください。