Ogni schermata dell'app deve essere reattiva e adattarsi allo spazio disponibile.
Puoi creare una UI reattiva con
ConstraintLayout
che consente a un singolo riquadro
larga scala per molte dimensioni, ma i dispositivi più grandi potrebbero trarre vantaggio dalla suddivisione
il layout in più riquadri. Ad esempio, potresti volere che una schermata mostri
elenco di elementi accanto a un elenco di dettagli dell'elemento selezionato.
La
SlidingPaneLayout
supporta la visualizzazione di due riquadri affiancati sui dispositivi più grandi
pieghevoli e si adatta automaticamente alla visualizzazione di un solo riquadro alla volta
su dispositivi più piccoli come i telefoni.
Per indicazioni specifiche per un dispositivo, consulta le Panoramica della compatibilità dello schermo.
Configura
Per usare SlidingPaneLayout
, includi la seguente dipendenza nel
build.gradle
file:
Alla moda
dependencies { implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0" }
Kotlin
dependencies { implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0") }
Configurazione del layout XML
SlidingPaneLayout
offre un layout orizzontale a due riquadri da utilizzare nella parte superiore
a livello di UI. Questo layout utilizza il primo riquadro come elenco di contenuti o come browser,
è subordinato a una visualizzazione dei dettagli principale per la visualizzazione dei contenuti nell'altro riquadro.
SlidingPaneLayout
utilizza la larghezza dei due riquadri per determinare se mostrare o meno
affiancati i riquadri. Ad esempio, se il riquadro elenco viene misurato
una dimensione minima di 200 dp e il riquadro dei dettagli richiede 400 dp, quindi
SlidingPaneLayout
mostra automaticamente i due riquadri affiancati, purché
abbia almeno 600 dp di larghezza disponibile.
Le viste secondarie si sovrappongono se la loro larghezza combinata supera quella disponibile nei valori
SlidingPaneLayout
. In questo caso, le visualizzazioni secondarie si espandono per riempire i campi
larghezza in SlidingPaneLayout
. L'utente può far scorrere la vista più in alto per uscire
trascinandola indietro dal bordo dello schermo.
Se le visualizzazioni non si sovrappongono, SlidingPaneLayout
supporta l'utilizzo del layout
parametro layout_weight
nelle viste secondarie per definire come dividere lo spazio rimanente
al termine della misurazione. Questo parametro è pertinente solo per la larghezza.
Su un dispositivo pieghevole che ha spazio sullo schermo per mostrare entrambe le visualizzazioni una accanto all'altra
laterale, SlidingPaneLayout
regola automaticamente le dimensioni dei due riquadri in modo che
vengono posizionati ai lati di una piega o di una cerniera sovrapposta. In questo
in questo caso, le larghezze impostate sono considerate larghezze minime che devono essere
lato della caratteristica di piegatura. Se non c'è abbastanza spazio per mantenere
dimensioni minime, SlidingPaneLayout
torna alla sovrapposizione delle visualizzazioni.
Ecco un esempio di utilizzo di un SlidingPaneLayout
con un
RecyclerView
come relativo
riquadro sinistro e una
FragmentContainerView
come visualizzazione dei dettagli principale per visualizzare i contenuti dal riquadro a sinistra:
<!-- 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
su FragmentContainerView
aggiunge
il frammento iniziale del riquadro dei dettagli, garantendo che gli utenti
dispositivi non vedono un riquadro a destra vuoto al primo avvio dell'app.
Sostituisci il riquadro dei dettagli in modo programmatico
Nell'esempio XML precedente, toccando un elemento nella sezione RecyclerView
attiva una modifica nel riquadro dei dettagli. Quando utilizzi i frammenti, questo richiede
FragmentTransaction
che sostituisce il riquadro destro,
open()
sul 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(); }
Questo codice in particolare non chiama
addToBackStack()
il FragmentTransaction
. In questo modo eviterai di creare uno stack esistente nei dettagli.
riquadro.
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
AbstractListDetailFragment
,
una classe API che utilizza SlidingPaneLayout
in background per gestire il tuo elenco
e riquadri dei dettagli.
Questo ti consente di semplificare la configurazione del layout XML. Invece di indicare
dichiarare SlidingPaneLayout
e entrambi i riquadri, il layout richiede solo una
FragmentContainerView
per conservare AbstractListDetailFragment
implementazione:
<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 del riquadro elenco. Per il riquadro dei dettagli,
AbstractListDetailFragment
utilizza un
NavHostFragment
Ciò significa che puoi definire una navigazione
che contiene solo
destinazioni da visualizzare nel riquadro dei dettagli. Quindi puoi utilizzare
NavController
per scambiare
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
un grafico di navigazione esterno
a livello di app. Tuttavia, eventuali link diretti
il grafico di navigazione del riquadro deve essere collegato alla destinazione che ospita
SlidingPaneLayout
. In questo modo avrai la certezza che i link diretti esterni navighino per la prima volta
alla destinazione SlidingPaneLayout
e vai al dettaglio corretto
del riquadro di destinazione.
Consulta le Esempio TwoPaneFragment per un'implementazione completa di un layout a due riquadri utilizzando il componente Navigazione.
Integrazione con il pulsante Indietro del sistema
Sui dispositivi più piccoli in cui i riquadri dell'elenco e dei dettagli si sovrappongono, assicurati che il sistema
Indietro riporta l'utente dal riquadro dei dettagli al riquadro dell'elenco. Esegui questa azione
fornendo un riassunto personalizzato
navigazione e collegare un
Da OnBackPressedCallback
a
lo stato attuale di 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 dei dettagli al riquadro dell'elenco
e del riquadro dei dettagli sui telefoni. Questi metodi non hanno
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, è possibile spostarsi liberamente tra i due riquadri anche quando non si utilizzano gesti
. Puoi controllare la direzione di scorrimento
impostando la modalità di blocco di 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 le documentazione seguente: