アプリのすべての画面がレスポンシブで、利用可能なスペースに適応する必要があります。
レスポンシブ UI を作成するには、
ConstraintLayout
- シングルペイン
アプローチはさまざまなサイズにスケーリングできるが、デバイスが大きい場合は分割することでメリットが得られる可能性がある
複数のペインに分割できますたとえば、特定のスレッドを
選択したアイテムの詳細のリストの横にあるアイテムのリスト。
「
SlidingPaneLayout
大きなデバイスで 2 つのペインを並べて表示し、
折りたたみ式デバイスと同様に、一度に 1 つのペインのみを表示するよう自動的に
スマートフォンなどの小型デバイス向けに
設計されています
デバイス固有のガイダンスについては、 画面の互換性の概要をご覧ください。
設定
SlidingPaneLayout
を使用するには、アプリの build.gradle
ファイルに次の依存関係を含めます。
Groovy
dependencies { implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0" }
Kotlin
dependencies { implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0") }
XML のレイアウト構成
SlidingPaneLayout
は、上部で使用する水平 2 ペイン レイアウトを提供します。
必要があります。このレイアウトでは、1 つ目のペインをコンテンツ リストまたはブラウザとして使用し、別のペインにコンテンツを表示するための主な詳細ビューに従属させます。
SlidingPaneLayout
は、2 つのペインの幅を使用して、ペインを並べて表示するかどうかを決定します。たとえば、リストペインが 100 ミリ秒以内の
最小サイズが 200 dp で、詳細ペインに 400 dp が必要な場合は、
SlidingPaneLayout
では、次の条件を満たしている限り、自動的に 2 つのペインを並べて表示します。
600 dp 以上の幅が利用可能である。
組み合わされた幅が SlidingPaneLayout
で使用可能な幅を超えた場合、子ビューは重複します。この場合、子ビューは SlidingPaneLayout
で使用可能な幅いっぱいに拡張されます。ユーザーは画面の端からドラッグして戻ることで、最上部のビューを邪魔にならないようにスライドさせることができます。
ビューが重ならない場合、SlidingPaneLayout
でレイアウトの使用がサポートされます。
子ビューのパラメータ layout_weight
(余ったスペースの分割方法を定義する)
メッセージを表示できます。このパラメータは、幅にのみ関連します。
画面上に両方のビューを並べて表示するスペースがある折りたたみ式デバイス
SlidingPaneLayout
が 2 つのペインのサイズを自動的に調整して、
重なっている折り目またはヒンジの両側に配置されている。この
設定された幅は、各要素の中に存在する必要がある
折りたためますこれを維持するのに十分な容量がない場合
最小サイズの場合、SlidingPaneLayout
はビューの重複に戻ります。
以下は、RecyclerView
を左ペイン、FragmentContainerView
を左ペインのコンテンツを表示する主な詳細ビューとして持つ SlidingPaneLayout
を使用している例です。
<!-- two_pane.xml -->
<androidx.slidingpanelayout.widget.SlidingPaneLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/sliding_pane_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- The first child view becomes the left pane. When the combined needed
width, expressed using android:layout_width, doesn't fit on-screen at
once, the right pane is permitted to overlap the left. -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_pane"
android:layout_width="280dp"
android:layout_height="match_parent"
android:layout_gravity="start"/>
<!-- The second child becomes the right (content) pane. In this example,
android:layout_weight is used to expand this detail pane to consume
leftover available space when the entire window is wide enough to fit
the left and right pane.-->
<androidx.fragment.app.FragmentContainerView
android:id="@+id/detail_container"
android:layout_width="300dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:background="#ff333333"
android:name="com.example.SelectAnItemFragment" />
</androidx.slidingpanelayout.widget.SlidingPaneLayout>
この例では、FragmentContainerView
の android:name
属性によって、
最初のフラグメントが詳細ペインに追加され、大画面のユーザーが
デバイスの初回起動時に空の右ペインが表示されない。
プログラムで詳細ペインを入れ替える
上記の XML の例では、RecyclerView
の要素をタップします。
詳細ペインで変更がトリガーされますフラグメントを使用する場合、右側のペインを置き換える FragmentTransaction
が必要です。それにより、SlidingPaneLayout
上に open()
が呼び出され、新たに表示可能になったフラグメントに入れ替えられます。
Kotlin
// A method on the Fragment that owns the SlidingPaneLayout,called by the // adapter when an item is selected. fun openDetails(itemId: Int) { childFragmentManager.commit { setReorderingAllowed(true) replace<ItemFragment>(R.id.detail_container, bundleOf("itemId" to itemId)) // If it's already open and the detail pane is visible, crossfade // between the fragments. if (binding.slidingPaneLayout.isOpen) { setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) } } binding.slidingPaneLayout.open() }
Java
// A method on the Fragment that owns the SlidingPaneLayout, called by the // adapter when an item is selected. void openDetails(int itemId) { Bundle arguments = new Bundle(); arguments.putInt("itemId", itemId); FragmentTransaction ft = getChildFragmentManager().beginTransaction() .setReorderingAllowed(true) .replace(R.id.detail_container, ItemFragment.class, arguments); // If it's already open and the detail pane is visible, crossfade // between the fragments. if (binding.getSlidingPaneLayout().isOpen()) { ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); } ft.commit(); binding.getSlidingPaneLayout().open(); }
このコードは特に
addToBackStack()
(FragmentTransaction
)これにより、詳細ペインでバックスタックが構築されなくなります。
Navigation コンポーネントの実装
このページの例では、SlidingPaneLayout
を直接使用しているため、次のことを行う必要があります。
手動でフラグメント トランザクションを管理します。ただし、
Navigation コンポーネントは、事前構築済みの
2 ペイン レイアウトにする
AbstractListDetailFragment
、
内部で SlidingPaneLayout
を使用してリストを管理する API クラス
詳細ペインがあります
これにより、XML レイアウト構成を簡素化できます。SlidingPaneLayout
と両方のペインを明示的に宣言する代わりに、レイアウトには、AbstractListDetailFragment
の実装を保持するために FragmentContainerView
だけが必要となります。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/two_pane_container"
<!-- The name of your AbstractListDetailFragment implementation.-->
android:name="com.example.testapp.TwoPaneFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
<!-- The navigation graph for your detail pane.-->
app:navGraph="@navigation/two_pane_navigation" />
</FrameLayout>
導入
onCreateListPaneView()
および
onListPaneViewCreated()
リストペインのカスタムビューを提供します。詳細ペインについては
AbstractListDetailFragment
は
NavHostFragment
。
つまり、詳細ペインに表示されるデスティネーションのみを含むナビゲーション グラフを定義できます。すると NavController
を使用して、自己完結型ナビゲーション グラフのデスティネーション間で詳細ペインを入れ替えることができます。
Kotlin
fun openDetails(itemId: Int) { val navController = navHostFragment.navController navController.navigate( // Assume the itemId is the android:id of a destination in the graph. itemId, null, NavOptions.Builder() // Pop all destinations off the back stack. .setPopUpTo(navController.graph.startDestination, true) .apply { // If it's already open and the detail pane is visible, // crossfade between the destinations. if (binding.slidingPaneLayout.isOpen) { setEnterAnim(R.animator.nav_default_enter_anim) setExitAnim(R.animator.nav_default_exit_anim) } } .build() ) binding.slidingPaneLayout.open() }
Java
void openDetails(int itemId) { NavController navController = navHostFragment.getNavController(); NavOptions.Builder builder = new NavOptions.Builder() // Pop all destinations off the back stack. .setPopUpTo(navController.getGraph().getStartDestination(), true); // If it's already open and the detail pane is visible, crossfade between // the destinations. if (binding.getSlidingPaneLayout().isOpen()) { builder.setEnterAnim(R.animator.nav_default_enter_anim) .setExitAnim(R.animator.nav_default_exit_anim); } navController.navigate( // Assume the itemId is the android:id of a destination in the graph. itemId, null, builder.build() ); binding.getSlidingPaneLayout().open(); }
詳細ペインのナビゲーション グラフ内のデスティネーションは、存在しない
外側のアプリ全体のナビゲーション グラフです。ただし、詳細内のディープリンクは
ペインのナビゲーション グラフは、サービスをホストするデスティネーションにアタッチする必要があります。
SlidingPaneLayout
。これにより、外部のディープリンクが最初に移動し、
SlidingPaneLayout
デスティネーションに移動してから、正しい詳細に移動する
作成します。
詳しくは、 TwoPaneFragment の例 Navigation コンポーネントを使用した 2 ペイン レイアウトの完全な実装をご覧ください。
システムの [戻る] ボタンとの統合
リストペインと詳細ペインが重なる小型デバイスでは、システムの
[戻る] ボタンをクリックすると、ユーザーが詳細ペインからリストペインに戻ります。すべきこと
カスタム バック機能を用意することで
ナビゲーションを
OnBackPressedCallback
~
SlidingPaneLayout
の現在の状態:
Kotlin
class TwoPaneOnBackPressedCallback( private val slidingPaneLayout: SlidingPaneLayout ) : OnBackPressedCallback( // Set the default 'enabled' state to true only if it is slidable, such as // when the panes overlap, and open, such as when the detail pane is // visible. slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen ), SlidingPaneLayout.PanelSlideListener { init { slidingPaneLayout.addPanelSlideListener(this) } override fun handleOnBackPressed() { // Return to the list pane when the system back button is tapped. slidingPaneLayout.closePane() } override fun onPanelSlide(panel: View, slideOffset: Float) { } override fun onPanelOpened(panel: View) { // Intercept the system back button when the detail pane becomes // visible. isEnabled = true } override fun onPanelClosed(panel: View) { // Disable intercepting the system back button when the user returns to // the list pane. isEnabled = false } }
Java
class TwoPaneOnBackPressedCallback extends OnBackPressedCallback implements SlidingPaneLayout.PanelSlideListener { private final SlidingPaneLayout mSlidingPaneLayout; TwoPaneOnBackPressedCallback(@NonNull SlidingPaneLayout slidingPaneLayout) { // Set the default 'enabled' state to true only if it is slideable, such // as when the panes overlap, and open, such as when the detail pane is // visible. super(slidingPaneLayout.isSlideable() && slidingPaneLayout.isOpen()); mSlidingPaneLayout = slidingPaneLayout; slidingPaneLayout.addPanelSlideListener(this); } @Override public void handleOnBackPressed() { // Return to the list pane when the system back button is tapped. mSlidingPaneLayout.closePane(); } @Override public void onPanelSlide(@NonNull View panel, float slideOffset) { } @Override public void onPanelOpened(@NonNull View panel) { // Intercept the system back button when the detail pane becomes // visible. setEnabled(true); } @Override public void onPanelClosed(@NonNull View panel) { // Disable intercepting the system back button when the user returns to // the list pane. setEnabled(false); } }
次のようにコールバックを
OnBackPressedDispatcher
addCallback()
:
Kotlin
class TwoPaneFragment : Fragment(R.layout.two_pane) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val binding = TwoPaneBinding.bind(view) // Connect the SlidingPaneLayout to the system back button. requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, TwoPaneOnBackPressedCallback(binding.slidingPaneLayout)) // Set up the RecyclerView adapter. } }
Java
class TwoPaneFragment extends Fragment { public TwoPaneFragment() { super(R.layout.two_pane); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { TwoPaneBinding binding = TwoPaneBinding.bind(view); // Connect the SlidingPaneLayout to the system back button. requireActivity().getOnBackPressedDispatcher().addCallback( getViewLifecycleOwner(), new TwoPaneOnBackPressedCallback(binding.getSlidingPaneLayout())); // Set up the RecyclerView adapter. } }
ロックモード
SlidingPaneLayout
では、常に open()
と
close()
スマートフォンのリストペインと詳細ペインの間で移動できるようになりました。これらのメソッドには、
両方のペインが表示され、重なっていない場合に表示されます。
リストペインと詳細ペインが重なっている場合、ユーザーはデフォルトで両方向へのスワイプが可能で、ジェスチャー ナビゲーションを使用していない場合でも 2 つのペインを自由に切り替えることができます。SlidingPaneLayout
のロックモードを設定することで、スワイプの方向を制御できます。
Kotlin
binding.slidingPaneLayout.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED
Java
binding.getSlidingPaneLayout().setLockMode(SlidingPaneLayout.LOCK_MODE_LOCKED);
詳細
さまざまなフォーム ファクタ向けのレイアウトのデザインについて詳しくは、 次のドキュメント: