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
:
Odlotowe
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.
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.
Implementacja komponentu nawigacji
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: