Jeder Bildschirm in Ihrer App muss responsiv sein und sich an den verfügbaren Platz anpassen.
Sie können eine responsive UI mit
ConstraintLayout
, mit der ein einzelner Bereich
an viele Größen zu skalieren, aber größere Geräte könnten von einer Aufteilung profitieren.
in mehrere Bereiche verschieben. Angenommen, ein Bildschirm soll eine
Liste von Elementen neben einer Liste mit Details zum ausgewählten Element.
Die
SlidingPaneLayout
können auf größeren Geräten zwei Bereiche nebeneinander angezeigt werden.
faltbare Smartphones und Tablets werden automatisch so angepasst, dass jeweils nur eine Ansicht auf dem Display
kleinere Geräte wie Smartphones.
Gerätespezifische Hinweise findest du in der Bildschirmkompatibilität.
Einrichten
Um SlidingPaneLayout
zu verwenden, füge die folgende Abhängigkeit in das
build.gradle
-Datei:
Groovy
dependencies { implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0" }
Kotlin
dependencies { implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0") }
Konfiguration des XML-Layouts
SlidingPaneLayout
bietet ein horizontales Zwei-Fenster-Layout, das oben verwendet werden kann
Ebene einer Benutzeroberfläche. Bei diesem Layout wird der erste Bereich als Inhaltsliste oder Browser verwendet.
Sie sind der primären Detailansicht untergeordnet, um Inhalte im anderen Bereich anzuzeigen.
SlidingPaneLayout
bestimmt anhand der Breite der beiden Bereiche, ob ein bestimmter Bereich
Fenster nebeneinander. Wenn das Listenfenster beispielsweise
die Mindestgröße 200 dp beträgt und der Detailbereich 400 dp benötigt,
SlidingPaneLayout
zeigt die beiden Bereiche automatisch nebeneinander an,
eine Breite von mindestens 600 dp verfügbar ist.
Untergeordnete Ansichten überlappen sich, wenn ihre kombinierte Breite die verfügbare Breite in der Spalte
SlidingPaneLayout
In diesem Fall werden die untergeordneten Ansichten erweitert, um die verfügbaren
Breite in SlidingPaneLayout
. Der Nutzer kann die oberste Ansicht aus der
ziehen Sie es vom Bildschirmrand zurück.
Wenn sich die Ansichten nicht überschneiden, unterstützt SlidingPaneLayout
die Verwendung des Layouts
Parameter layout_weight
für untergeordnete Datenansichten, um festzulegen, wie der übrig gebliebene Platz aufgeteilt werden soll
nach Abschluss der Messung. Dieser Parameter ist nur für die Breite relevant.
Auf einem faltbaren Gerät, bei dem auf dem Display beide Ansichten nebeneinander angezeigt werden können
seitlich, passt SlidingPaneLayout
die Größe der beiden Bereiche automatisch an,
sie sind an beiden Seiten einer überlappenden Faltung oder eines Scharniers positioniert. In dieser
gelten die festgelegten Breiten als Mindestbreite, die auf jedem
Seite der Faltfunktion. Falls nicht genügend Platz zur Verfügung steht,
Mindestgröße, wechselt SlidingPaneLayout
wieder so, dass die Ansichten überlappen.
Hier ist ein Beispiel für die Verwendung eines SlidingPaneLayout
mit einem
RecyclerView
als
auf der linken Seite
FragmentContainerView
als primäre Detailansicht aus, um Inhalte aus dem linken Bereich anzuzeigen:
<!-- 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 diesem Beispiel fügt das Attribut android:name
auf FragmentContainerView
vom ersten Fragment zum Detailbereich, wodurch Nutzer auf großen Bildschirmen
wird beim ersten Start der App kein leerer Bereich rechts angezeigt.
Detailbereich programmatisch austauschen
Im vorherigen XML-Beispiel wurde durch Tippen auf ein Element im RecyclerView
löst eine Änderung im Detailbereich aus. Bei der Verwendung von Fragmenten ist dafür eine
FragmentTransaction
das den rechten Bereich ersetzt und
open()
im SlidingPaneLayout
, um zum neu sichtbaren Fragment zu wechseln:
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(); }
Mit diesem Code wird kein Aufruf
addToBackStack()
am FragmentTransaction
. Dadurch wird kein Back-Stack im Detail erstellt
.
Implementierung der Navigationskomponente
In den Beispielen auf dieser Seite wird SlidingPaneLayout
direkt verwendet. Sie müssen Folgendes tun:
Transaktionen mit Fragmenten manuell verwalten. Die
Die Navigationskomponente bietet eine vordefinierte Implementierung von
ein Zwei-Fenster-Layout durch
AbstractListDetailFragment
,
eine API-Klasse, die einen SlidingPaneLayout
im Hintergrund zum Verwalten Ihrer Liste verwendet
und Detailbereichen.
Auf diese Weise können Sie die Konfiguration Ihres XML-Layouts vereinfachen. Anstelle von expliziten
Wenn Sie ein SlidingPaneLayout
und beide Bereiche deklarieren, benötigt Ihr Layout nur eine
FragmentContainerView
, um deine AbstractListDetailFragment
zu behalten
Implementierung:
<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>
Implementieren
onCreateListPaneView()
und
onListPaneViewCreated()
um eine benutzerdefinierte Ansicht für Ihr Listenfenster bereitzustellen. Im Detailbereich
AbstractListDetailFragment
verwendet eine
NavHostFragment
Das heißt, Sie können eine Navigation
Grafik, die nur den Wert
die Ziele, die im Detailbereich angezeigt werden sollen. Dann können Sie
NavController
zum Ändern der
Detailbereich zwischen den Zielen in der eigenständigen Navigationsgrafik:
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(); }
Die Ziele im Navigationsdiagramm des Detailbereichs dürfen in folgendem Ordner nicht vorhanden sein:
alle äußeren Navigationsgrafiken
in der gesamten App. Deeplinks innerhalb der Detailinformationen
Das Navigationsdiagramm des Fensters muss mit dem Ziel verbunden sein, das die
SlidingPaneLayout
Dadurch wird sichergestellt, dass
zum Ziel SlidingPaneLayout
und dann zum richtigen Detail
Zielbereich.
Weitere Informationen finden Sie in der Beispiel für TwoPaneFragment für eine vollständige Implementierung eines Zwei-Fenster-Layouts mithilfe der Navigationskomponente.
Zurück-Taste des Systems integrieren
Stellen Sie auf kleineren Geräten, auf denen sich die Listen- und Detailbereiche überlappen, das System
Zurück-Schaltfläche bringt den Nutzer vom Detailbereich zurück in den Listenbereich. Vorgehensweise
indem Sie benutzerdefinierte
Navigation und Verbinden eines
OnBackPressedCallback
bis
den aktuellen Status von 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); } }
Sie können den Callback dem Element
OnBackPressedDispatcher
mit
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. } }
Sperrmodus
Mit SlidingPaneLayout
können Sie immer manuell open()
aufrufen und
close()
um zwischen Listen- und Detailbereichen auf Smartphones zu wechseln. Diese Methoden haben keine
wenn beide Bereiche sichtbar sind und sich nicht überschneiden.
Wenn sich der Listen- und der Detailbereich überschneiden, können Nutzende in beide Richtungen wischen, indem sie
können Sie frei zwischen den beiden Bereichen wechseln, auch wenn Sie keine Touch-Geste
Navigation. Du kannst die Wischrichtung festlegen
indem du den Sperrmodus von SlidingPaneLayout
festlegst:
Kotlin
binding.slidingPaneLayout.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED
Java
binding.getSlidingPaneLayout().setLockMode(SlidingPaneLayout.LOCK_MODE_LOCKED);
Weitere Informationen
Weitere Informationen über das Entwerfen von Layouts für verschiedene Formfaktoren finden Sie in den folgenden Dokumentation: