Ein Layout mit zwei Bereichen erstellen

Schreiben Sie jetzt
Jetpack Compose ist das empfohlene UI-Toolkit für Android. Hier erfahren Sie, wie Sie in „Compose“ mit Layouts arbeiten.
<ph type="x-smartling-placeholder"></ph> Adaptive Layouts →

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:

Cool

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.

<ph type="x-smartling-placeholder">
</ph> Bild, das ein Beispiel für ein SlidingPaneLayout zeigt <ph type="x-smartling-placeholder">
</ph> Abbildung 1: Ein Beispiel für ein Layout, das mit SlidingPaneLayout

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 .

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:

Weitere Informationen