Ogni schermata dell'app deve essere adattabile e adattarsi allo spazio disponibile.
Puoi creare un'interfaccia utente adattabile con
ConstraintLayout
che consente di adattare un approccio con un unico riquadro a molte dimensioni, ma i dispositivi più grandi potrebbero trarre vantaggio dalla suddivisione del layout in più riquadri. Ad esempio, potresti volere che una schermata mostri un elenco di elementi accanto a un elenco di dettagli dell'elemento selezionato.
Il componente
SlidingPaneLayout
supporta la visualizzazione di due riquadri affiancati su dispositivi più grandi e su dispositivi pieghevoli, adattandosi automaticamente per mostrare un solo riquadro alla volta su dispositivi più piccoli come gli smartphone.
Per indicazioni specifiche per il dispositivo, consulta la panoramica della compatibilità dello schermo.
Configura
Per utilizzare SlidingPaneLayout
, includi la seguente dipendenza nel file SlidingPaneLayout
dell'app:build.gradle
Alla moda
dependencies { implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0" }
Kotlin
dependencies { implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0") }
Configurazione del layout XML
SlidingPaneLayout
fornisce un layout orizzontale a due riquadri da utilizzare al livello superiore di un'interfaccia utente. Questo layout utilizza il primo riquadro come elenco di contenuti o browser, subordinato a una visualizzazione dettagliata principale per la visualizzazione dei contenuti nell'altro riquadro.
SlidingPaneLayout
utilizza la larghezza dei due riquadri per determinare se mostrarli affiancati. Ad esempio, se il riquadro dell'elenco ha una dimensione minima di 200 dp e il riquadro dei dettagli richiede 400 dp, SlidingPaneLayout
mostra automaticamente i due riquadri affiancati purché sia disponibile una larghezza di almeno 600 dp.
Le visualizzazioni secondarie si sovrappongono se la loro larghezza combinata supera quella disponibile in SlidingPaneLayout
. In questo caso, le visualizzazioni secondarie si espandono per riempire la larghezza disponibile in SlidingPaneLayout
. L'utente può far scorrere la visualizzazione più in alto per rimuoverla dalla visuale trascinandola dal bordo dello schermo.
Se le visualizzazioni non si sovrappongono, SlidingPaneLayout
supporta l'utilizzo del parametro di layout layout_weight
nelle visualizzazioni secondarie per definire la modalità di suddivisione dello spazio rimanente al termine della misurazione. Questo parametro è pertinente solo per la larghezza.
Su un dispositivo pieghevole con spazio sullo schermo per mostrare entrambe le visualizzazioni affiancate, SlidingPaneLayout
regola automaticamente le dimensioni dei due riquadri in modo che siano posizionati su entrambi i lati di una cerniera o piega sovrapposta. In questo
caso, le larghezze impostate sono considerate la larghezza minima che deve essere presente su ciascun
lato della funzionalità di piegatura. Se non c'è spazio sufficiente per mantenere queste dimensioni minime, SlidingPaneLayout
torna a sovrapporre le visualizzazioni.
Ecco un esempio di utilizzo di un SlidingPaneLayout
con un
RecyclerView
come
riquadro sinistro e un
FragmentContainerView
come visualizzazione dei dettagli principale per visualizzare i contenuti del riquadro sinistro:
<!-- 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>
In questo esempio, l'attributo android:name
in FragmentContainerView
aggiunge il frammento iniziale al riquadro dei dettagli, in modo che gli utenti che utilizzano dispositivi con schermo di grandi dimensioni non vedano un riquadro destro vuoto al primo avvio dell'app.
Scambiare il riquadro dei dettagli in modo programmatico
Nell'esempio XML precedente, toccare un elemento in RecyclerView
attiva una modifica nel riquadro dei dettagli. Quando utilizzi i frammenti, è necessario un
FragmentTransaction
che sostituisca il riquadro a destra, chiamando
open()
su SlidingPaneLayout
per passare al frammento appena visibile:
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(); }
Nello specifico, questo codice non chiama
addToBackStack()
su FragmentTransaction
. In questo modo, viene evitata la creazione di una serie di elementi precedenti nel riquadro dei dettagli.
Implementazione del componente di navigazione
Gli esempi in questa pagina utilizzano direttamente SlidingPaneLayout
e richiedono di gestire manualmente le transazioni dei frammenti. Tuttavia, il
componente di navigazione fornisce un'implementazione predefinita di
un layout a due riquadri tramite
AbstractListDetailFragment
,
una classe API che utilizza un SlidingPaneLayout
sotto il cofano per gestire i riquadri di elenco
e dei dettagli.
In questo modo puoi semplificare la configurazione del layout XML. Anziché dichiarare esplicitamente un SlidingPaneLayout
e entrambi i riquadri, il layout richiede solo un FragmentContainerView
per contenere l'implementazione di AbstractListDetailFragment
:
<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>
Implementa
onCreateListPaneView()
e
onListPaneViewCreated()
per fornire una visualizzazione personalizzata per il riquadro dell'elenco. Per il riquadro dei dettagli,
AbstractListDetailFragment
utilizza un
NavHostFragment
.
Ciò significa che puoi definire un grafo di navigazione contenente solo le destinazioni da mostrare nel riquadro dei dettagli. Quindi, puoi utilizzare
NavController
per spostare il
riquadro dei dettagli tra le destinazioni nel grafico di navigazione autonomo:
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(); }
Le destinazioni nel grafico di navigazione del riquadro dei dettagli non devono essere presenti in nessun grafico di navigazione esterno a livello di app. Tuttavia, tutti i link diretti all'interno del grafico di navigazione del riquadro dei dettagli devono essere collegati alla destinazione che ospita il SlidingPaneLayout
. In questo modo, i link diretti esterni passano prima alla destinazione SlidingPaneLayout
e poi alla destinazione corretta del riquadro dei dettagli.
Consulta l'esempio TwoPaneFragment per un'implementazione completa di un layout a due riquadri utilizzando il componente Navigation.
Integrazione con il pulsante Indietro del sistema
Sui dispositivi più piccoli in cui i riquadri di elenco e dei dettagli si sovrappongono, assicurati che il pulsante Indietro del sistema rimandi l'utente dal riquadro dei dettagli al riquadro dell'elenco. Per farlo,
fornisci una navigazione personalizzata e collega un
OnBackPressedCallback
allo stato corrente del 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); } }
Puoi aggiungere il callback al
OnBackPressedDispatcher
utilizzando
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. } }
Modalità di blocco
SlidingPaneLayout
ti consente sempre di chiamare manualmente open()
e
close()
per passare dal riquadro dell'elenco a quello dei dettagli sugli smartphone. Questi metodi non hanno alcun effetto se entrambi i riquadri sono visibili e non si sovrappongono.
Quando i riquadri dell'elenco e dei dettagli si sovrappongono, gli utenti possono scorrere in entrambe le direzioni per impostazione predefinita, passando liberamente da un riquadro all'altro anche quando non utilizzano la navigazione con gesti. Puoi controllare la direzione dello scorrimento impostando la modalità di blocco del SlidingPaneLayout
:
Kotlin
binding.slidingPaneLayout.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED
Java
binding.getSlidingPaneLayout().setLockMode(SlidingPaneLayout.LOCK_MODE_LOCKED);
Scopri di più
Per scoprire di più sulla progettazione di layout per diversi fattori di forma, consulta la seguente documentazione: