Tworzenie układu z 2 panelami

Wypróbuj sposób tworzenia wiadomości
Jetpack Compose to zalecany zestaw narzędzi UI na Androida. Dowiedz się, jak korzystać z układów w funkcji Utwórz

Każdy ekran w aplikacji musi reagować na ruch i dostosowywać się do dostępnego miejsca. Możesz utworzyć elastyczny interfejs, ConstraintLayout, który umożliwia wyświetlenie jednego panelu na dużą skalę do wielu rozmiarów, ale na większych urządzeniach lepiej układ na wiele paneli. Na przykład na ekranie może być widoczny listę elementów obok listy szczegółów wybranego elementu.

SlidingPaneLayout umożliwia wyświetlanie dwóch paneli obok siebie na większych urządzeniach. urządzeń składanych i automatycznie dostosowują się do wyświetlania tylko jednego panelu na mniejszych urządzeniach, takich jak telefony.

Wskazówki dotyczące konkretnego urządzenia znajdziesz w przeglądzie zgodności ekranów.

Konfiguracja

Aby używać funkcji SlidingPaneLayout, uwzględnij tę zależność w parametrze Plik build.gradle:

Groovy

dependencies {
    implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0"
}

Kotlin

dependencies {
    implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0")
}

Konfiguracja układu XML

SlidingPaneLayout udostępnia poziomy układ z 2 panelami do użytku u góry z poziomu interfejsu użytkownika. Ten układ używa pierwszego panelu jako listy treści lub przeglądarki, przejdź do głównego widoku szczegółowego służącego do wyświetlania treści w drugim panelu.

Obraz przedstawiający przykładowy układ SlidingPaneLayout
Rysunek 1. Przykład układu utworzonego za pomocą SlidingPaneLayout

SlidingPaneLayout używa szerokości 2 paneli, aby określić, czy wyświetlić obok siebie. Jeśli na przykład w panelu listy ma się pomiar minimalny rozmiar to 200 dp, a okienko szczegółów – 400 dp. SlidingPaneLayout automatycznie wyświetla 2 panele obok siebie, o ile ma co najmniej 600 dp szerokości.

Widoki podrzędne nakładają się, jeśli ich łączna szerokość przekracza dostępną szerokość SlidingPaneLayout W takim przypadku widoki podrzędne rozwijają się, aby wypełnić dostępne w kolumnie SlidingPaneLayout. Użytkownik może wysunąć widok górny z przez przeciąganie go z powrotem od krawędzi ekranu.

Jeśli widoki się nie nakładają, SlidingPaneLayout obsługuje stosowanie układu parametr layout_weight w widokach podrzędnych, aby określić sposób dzielenia pozostałej przestrzeni po zakończeniu pomiaru. Ten parametr ma znaczenie tylko w przypadku szerokości.

Na urządzeniu składanym, na którym jest miejsce na ekranie, by można było wyświetlać oba widoki obok siebie , SlidingPaneLayout automatycznie dostosowuje rozmiar obu paneli, są umieszczone po obu stronach zawiasu lub zawiasu. W tym ustawione szerokości są uważane za minimalną szerokość, która musi występować na każdym funkcja składania. Jeśli nie ma wystarczająco dużo miejsca, aby je utrzymać minimalny rozmiar, SlidingPaneLayout przełączy się z powrotem na nakładające się widoki.

Oto przykład użycia właściwości SlidingPaneLayout z parametrem RecyclerView jako panelu po lewej stronie FragmentContainerView jako podstawowy widok szczegółów, który wyświetla zawartość z panelu po lewej stronie:

<!-- 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>

W tym przykładzie atrybut android:name w FragmentContainerView dodaje atrybut początkowy fragment do panelu szczegółów, dzięki czemu użytkownicy korzystający z dużego ekranu urządzenia nie zobaczą pustego panelu po prawej stronie przy pierwszym uruchomieniu aplikacji.

Automatycznie zamień okienko szczegółów

W poprzednim przykładzie kodu XML kliknięcie elementu w tabeli RecyclerView powoduje zmianę w panelu szczegółów. Jeśli używasz fragmentów, wymaga to tagu FragmentTransaction które zastępuje prawy panel, wywołując open() na SlidingPaneLayout, aby zamienić na nowo widoczny fragment:

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();
}

Ten kod nie wywołuje addToBackStack()FragmentTransaction Pozwala to uniknąć tworzenia stosu tylnego w szczegółach panel.

Przykłady na tej stronie korzystają bezpośrednio z tagu SlidingPaneLayout i wymagają ręcznie zarządzać fragmentami transakcji. Jednak Komponent nawigacyjny udostępnia gotową implementację układ z 2 panelami AbstractListDetailFragment, klasa interfejsu API, która do zarządzania Twoją listą używa wbudowanego w nią atrybutu SlidingPaneLayout i panelach szczegółów.

Pozwala to uprościć konfigurację układu XML. Zamiast jawnie zadeklarujesz SlidingPaneLayout i oba panele, układ wymaga tylko FragmentContainerView do przechowywania urządzenia AbstractListDetailFragment implementacja:

<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>

Wdrażaj onCreateListPaneView() oraz onListPaneViewCreated() aby wyświetlić panel listy w widoku niestandardowym. W panelu szczegółów Komponent AbstractListDetailFragment używa NavHostFragment Oznacza to, że możesz zdefiniować nawigację, który zawiera tylko miejsca docelowe, które mają być wyświetlane w panelu szczegółów. Następnie możesz użyć funkcji NavController, aby zamienić szczegółów między miejscami docelowymi na osobnym wykresie nawigacji:

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();
}

Miejsca docelowe na wykresie nawigacyjnym panelu szczegółów nie mogą znajdować się na zewnętrznego wykresu nawigacji obejmującej całą aplikację. Jednak wszystkie precyzyjne linki w szczegółach wykres nawigacyjny panelu musi być podłączony do miejsca docelowego, w którym znajduje się SlidingPaneLayout Dzięki temu zewnętrzne precyzyjne linki będą najpierw poruszać się do miejsca docelowego SlidingPaneLayout, a następnie przejdź do odpowiednich szczegółów miejsce docelowe panelu.

Zobacz Przykład TwoPaneFragment aby w pełni wdrożyć układ z dwoma panelami przy użyciu komponentu Nawigacja.

Integracja z systemowym przyciskiem Wstecz

Na mniejszych urządzeniach, w których panele listy i szczegółów nakładają się na siebie, Przycisk Wstecz przenosi użytkownika z okien szczegółów z powrotem do okienka listy. Zrób to przesyłając zwrot niestandardowy nawigacji i połączenie OnBackPressedCallback na bieżący stan 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);
    }
}

Wywołanie zwrotne możesz dodać do OnBackPressedDispatcher za pomocą 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.
    }
}

Tryb blokady

SlidingPaneLayout zawsze pozwala ręcznie nawiązywać połączenia z numerem open() i close(). , by przełączać się między panelami listy i szczegółów na telefonach. Te metody nie mają gdy oba panele są widoczne i nie nakładają się na siebie.

Gdy panele listy i szczegółów nakładają się na siebie, użytkownicy mogą przesuwać palcem w obu kierunkach, możesz swobodnie przełączać się między dwoma panelami, nawet jeśli nie używasz gestów nawigacji. Możesz sterować kierunkiem przesuwania ustawiając tryb blokady urządzenia SlidingPaneLayout:

Kotlin

binding.slidingPaneLayout.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED

Java

binding.getSlidingPaneLayout().setLockMode(SlidingPaneLayout.LOCK_MODE_LOCKED);

Więcej informacji

Aby dowiedzieć się więcej o projektowaniu układów na potrzeby różnych formatów, przeczytaj ta dokumentacja:

Dodatkowe materiały