Chaque écran de votre application doit être responsif et s'adapter à l'espace disponible.
Vous pouvez créer une interface utilisateur responsive avec
ConstraintLayout
, qui permet à un seul volet
s'adaptent à de nombreuses tailles, mais il serait judicieux de diviser les appareils plus grands.
la mise en page dans
plusieurs volets. Par exemple, vous pouvez souhaiter
qu'un écran affiche une
liste d'éléments à côté d'une liste d'informations sur l'élément sélectionné.
La
SlidingPaneLayout
permet d'afficher deux volets côte à côte sur les appareils plus grands et
pliables tout en s'adaptant automatiquement pour n'afficher qu'un seul volet à la fois
les appareils plus petits
tels que les téléphones.
Pour obtenir des conseils spécifiques à votre appareil, consultez les présentation de la compatibilité des écrans.
Configuration
Pour utiliser SlidingPaneLayout
, incluez la dépendance suivante dans le fichier
Fichier build.gradle
:
Groovy
dependencies { implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0" }
Kotlin
dependencies { implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0") }
Configuration de la mise en page XML
SlidingPaneLayout
offre une mise en page horizontale à deux volets à utiliser dans la partie supérieure
niveau d'une UI. Cette mise en page utilise le premier volet
en tant que liste de contenu ou navigateur,
subordonnée à une vue détaillée principale pour afficher le contenu dans l'autre volet.
![Image montrant un exemple de SlidingPaneLayout](https://developer.android.com/static/images/ui/slidingpanelayout-example.png?authuser=19&hl=fr)
SlidingPaneLayout
SlidingPaneLayout
utilise la largeur des deux volets pour déterminer s'ils doivent être affichés.
volets côte à côte. Par exemple, si le volet de liste est mesuré pour avoir
une taille minimale de 200 dp et une taille de 400 dp pour le volet de détail,
SlidingPaneLayout
affiche automatiquement les deux volets côte à côte tant qu'il
dont la largeur est d'au moins 600 dp.
Les vues enfants se chevauchent si leur largeur combinée dépasse la largeur disponible dans les
SlidingPaneLayout
Dans ce cas, les vues enfants se développent pour remplir l'espace disponible
largeur dans SlidingPaneLayout
. L'utilisateur peut faire glisser la vue supérieure en dehors
en la faisant glisser
à partir du bord de l'écran.
Si les vues ne se chevauchent pas, SlidingPaneLayout
permet d'utiliser la mise en page.
Paramètre layout_weight
sur les vues enfants pour définir comment diviser l'espace restant
une fois la mesure terminée. Ce paramètre ne concerne que la largeur.
Sur un appareil pliable qui dispose d'espace à l'écran pour afficher les deux vues côte à côte
côté, SlidingPaneLayout
ajuste automatiquement la taille des deux volets pour
elles sont positionnées de chaque côté d'un pli
ou d'une charnière qui se chevauchent. Dans ce
les largeurs définies sont considérées comme la largeur minimale qui doit exister sur chaque
du pliage. S'il n'y a pas assez d'espace
taille minimale, SlidingPaneLayout
revient au chevauchement des vues.
Voici un exemple d'utilisation d'un élément SlidingPaneLayout
ayant une valeur
RecyclerView
,
volet gauche et un
FragmentContainerView
comme vue détaillée principale pour afficher le contenu du volet de gauche:
<!-- 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>
Dans cet exemple, l'attribut android:name
sur FragmentContainerView
ajoute
fragment initial au volet de détails, garantissant que les utilisateurs sur grand écran
les appareils ne verront pas de volet droit vide au premier lancement de l'application.
Remplacer le volet de détails par programmation
Dans l'exemple XML précédent, le fait d'appuyer sur un élément dans RecyclerView
déclenche une modification dans le volet des détails. Lorsque vous utilisez des fragments, cela nécessite
FragmentTransaction
qui remplace le volet de droite, en appelant
open()
sur SlidingPaneLayout
pour passer au nouveau fragment visible:
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(); }
Ce code n'appelle pas
addToBackStack()
le FragmentTransaction
. Cela évite de créer une pile "Retour" dans les détails
volet.
Implémentation du composant Navigation
Les exemples de cette page utilisent directement SlidingPaneLayout
et vous obligent à
gérer manuellement les transactions de fragment. Toutefois,
Le composant Navigation fournit une implémentation prédéfinie de
une mise en page à
deux volets dans
AbstractListDetailFragment
Une classe d'API qui utilise SlidingPaneLayout
en arrière-plan pour gérer votre liste
et les volets de détails.
Cela vous permet de simplifier la configuration de votre mise en page XML. Au lieu d'indiquer explicitement
déclarant un SlidingPaneLayout
ainsi que vos deux volets, votre mise en page n'a besoin que d'un
FragmentContainerView
pour maintenir votre AbstractListDetailFragment
implémentation:
<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>
Implémentation
onCreateListPaneView()
et
onListPaneViewCreated()
afin de fournir une vue personnalisée pour votre volet de liste. Pour le volet Détails,
AbstractListDetailFragment
utilise un
NavHostFragment
Vous pouvez donc définir une navigation
graphique qui ne contient que les
destinations à afficher dans le volet des détails. Ensuite, vous pouvez utiliser
NavController
pour échanger votre
Volet détaillé entre les destinations du graphique de navigation autonome:
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(); }
Les destinations indiquées dans le graphique de navigation du volet des détails ne doivent pas être présentes dans
tout graphique de navigation externe
à l'échelle de l'application. Toutefois, tous les liens profonds dans les détails
le graphique de navigation du volet doit être associé à la destination qui héberge
SlidingPaneLayout
Cela permet de s'assurer que les liens profonds externes
vers la destination SlidingPaneLayout
, puis accédez au bon détail
vers la destination du volet.
Consultez le Exemple TwoPaneFragment pour découvrir l'implémentation complète d'une mise en page à deux volets à l'aide du composant Navigation.
Intégration avec le bouton "Retour" du système
Sur les petits appareils où les volets de liste et de vue détaillée se chevauchent, assurez-vous que le système
bouton "Retour" ramène l'utilisateur du volet de détails au volet de liste. À faire
en fournissant des retours personnalisés
de navigation et de connecter
OnBackPressedCallback
jusqu'à
l'état actuel de 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); } }
Vous pouvez ajouter le rappel
OnBackPressedDispatcher
avec
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. } }
Mode verrouillé
SlidingPaneLayout
vous permet toujours d'appeler manuellement open()
et
close()
pour passer d'un volet de liste à un volet de vue détaillée sur un téléphone. Ces méthodes n'ont pas
si les deux volets sont visibles et ne se chevauchent pas.
Lorsque les volets de liste et de détail se chevauchent, les utilisateurs peuvent balayer dans les deux sens en
par défaut, vous pouvez passer d'un volet à l'autre, même si vous n'utilisez pas la geste
navigation. Vous pouvez contrôler la direction du balayage
en définissant le mode verrouillé de SlidingPaneLayout
:
Kotlin
binding.slidingPaneLayout.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED
Java
binding.getSlidingPaneLayout().setLockMode(SlidingPaneLayout.LOCK_MODE_LOCKED);
En savoir plus
Pour en savoir plus sur la conception de mises en page pour différents facteurs de forme, consultez la documentation suivante:
- Présentation de la compatibilité des écrans
- Concevoir une solution adaptée à différents facteurs de forme