Setiap layar di aplikasi Anda harus responsif dan beradaptasi dengan ruang yang tersedia.
Anda dapat membangun UI yang responsif dengan
ConstraintLayout
yang memungkinkan satu panel
pendekatan skala ke berbagai ukuran, tetapi perangkat yang lebih besar
mungkin mendapat manfaat dari pemisahan
tata letak menjadi
beberapa panel. Misalnya, Anda mungkin ingin
layar menampilkan
daftar item di samping daftar detail dari item yang dipilih.
Tujuan
SlidingPaneLayout
memungkinkan Anda menampilkan dua panel secara berdampingan pada perangkat yang lebih besar dan
perangkat foldable sambil otomatis beradaptasi untuk hanya menampilkan satu panel dalam satu waktu
perangkat yang lebih kecil
seperti ponsel.
Untuk panduan khusus perangkat, lihat ringkasan kompatibilitas layar.
Penyiapan
Untuk menggunakan SlidingPaneLayout
, sertakan dependensi berikut di file
build.gradle
aplikasi Anda:
Groovy
dependencies { implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0" }
Kotlin
dependencies { implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0") }
Mengonfigurasi tata letak XML
SlidingPaneLayout
menyediakan tata letak dua panel horizontal untuk digunakan di bagian atas
level UI. Tata letak ini menggunakan panel pertama sebagai daftar konten atau browser,
di bawah tampilan detail utama guna menampilkan konten di panel lainnya.
![Gambar yang menunjukkan contoh SlidingPaneLayout](https://developer.android.google.cn/static/images/ui/slidingpanelayout-example.png?authuser=0000&hl=id)
SlidingPaneLayout
.
SlidingPaneLayout
menggunakan lebar dua panel untuk menentukan apakah menampilkan
panel secara berdampingan atau tidak. Misalnya, jika panel daftar diukur memiliki
ukuran minimum 200 dp dan panel detail memerlukan 400 dp, maka
SlidingPaneLayout
otomatis menampilkan dua panel secara berdampingan selama
memiliki lebar minimal 600 dp.
Tampilan turunan akan tumpang tindih jika lebar gabungannya melebihi lebar yang tersedia di
SlidingPaneLayout
. Dalam hal ini, tampilan turunan diperluas untuk mengisi lebar
yang tersedia di SlidingPaneLayout
. Pengguna dapat menggeser keluar tampilan
paling atas dengan menariknya kembali dari tepi layar.
Jika tampilan tidak tumpang-tindih, SlidingPaneLayout
mendukung penggunaan tata letak
parameter layout_weight
pada tampilan turunan untuk menentukan cara membagi ruang yang tersisa
setelah pengukuran selesai. Parameter ini hanya relevan untuk lebar.
Di perangkat foldable yang memiliki ruang di layar untuk menampilkan kedua tampilan secara berdampingan
sisi, SlidingPaneLayout
secara otomatis menyesuaikan ukuran dua panel sehingga
mereka diposisikan di kedua sisi lipatan atau engsel yang tumpang tindih. Di sini
Jika ini terjadi, lebar yang ditetapkan dianggap sebagai lebar minimum yang harus ada di setiap
sisi fitur lipat. Jika tidak ada cukup ruang
untuk menyimpannya
ukuran minimum, SlidingPaneLayout
beralih kembali ke tampilan yang tumpang-tindih.
Berikut adalah contoh penggunaan SlidingPaneLayout
yang memiliki
RecyclerView
sebagai panel
kirinya dan
FragmentContainerView
sebagai tampilan detail utamanya untuk menampilkan konten dari panel kiri:
<!-- 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>
Dalam contoh ini, atribut android:name
pada FragmentContainerView
menambahkan
fragmen awal ke panel detail, untuk memastikan bahwa pengguna di perangkat layar besar
perangkat tidak melihat panel kanan kosong saat aplikasi pertama kali diluncurkan.
Menukarkan panel detail secara terprogram
Pada contoh XML sebelumnya, mengetuk elemen di RecyclerView
akan memicu perubahan di panel detail. Saat menggunakan fragmen, tindakan ini memerlukan
FragmentTransaction
yang menggantikan panel kanan, dengan memanggil
open()
di SlidingPaneLayout
untuk beralih ke fragmen yang baru saja terlihat:
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(); }
Kode ini secara khusus
tidak memanggil
addToBackStack()
pada FragmentTransaction
. Tindakan ini akan menghindari pembuatan data sebelumnya di panel
detail.
Penerapan komponen navigasi
Contoh di halaman ini menggunakan SlidingPaneLayout
secara langsung dan mengharuskan Anda untuk
mengelola transaksi fragmen secara manual. Namun,
Komponen navigasi menyediakan implementasi bawaan dari
tata letak dua panel melalui
AbstractListDetailFragment
,
class API yang menggunakan SlidingPaneLayout
di balik layar untuk mengelola daftar Anda
dan detail.
Tindakan ini memungkinkan Anda menyederhanakan konfigurasi tata letak XML. Daripada secara eksplisit
mendeklarasikan SlidingPaneLayout
dan kedua panel, tata letak Anda hanya memerlukan
FragmentContainerView
untuk menyimpan implementasi
AbstractListDetailFragment
:
<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>
Terapkan
onCreateListPaneView()
dan
onListPaneViewCreated()
untuk memberikan tampilan khusus bagi panel daftar Anda. Untuk panel detail,
AbstractListDetailFragment
menggunakan
NavHostFragment
.
Ini berarti Anda dapat menentukan grafik
navigasi yang hanya berisi
tujuan yang akan ditampilkan di panel detail. Kemudian, Anda dapat menggunakan
NavController
untuk menukar
panel detail antar-tujuan dalam grafik navigasi mandiri:
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(); }
Tujuan di grafik navigasi panel detail tidak boleh ada di
grafik navigasi luar
dalam seluruh aplikasi. Namun, setiap deep link dalam detail
grafik navigasi panel harus dilampirkan ke tujuan yang menghosting
SlidingPaneLayout
. Hal ini membantu memastikan bahwa deep link eksternal terlebih dahulu membuka
ke tujuan SlidingPaneLayout
, lalu buka detail yang benar
tujuan panel.
Lihat Contoh TwoPaneFragment untuk implementasi penuh tata letak dua panel menggunakan komponen Navigasi.
Mengintegrasikan dengan tombol kembali sistem
Pada perangkat yang lebih kecil, dengan panel daftar dan detail saling tumpang tindih, pastikan sistem
tombol {i>back<i} membawa pengguna dari panel detail kembali ke panel daftar. Yang harus dilakukan
dengan memberikan opsi back kustom
navigasi dan menghubungkan
OnBackPressedCallback
ke
status SlidingPaneLayout
saat ini:
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); } }
Anda dapat menambahkan callback ke
OnBackPressedDispatcher
menggunakan
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. } }
Mode kunci
SlidingPaneLayout
selalu memungkinkan Anda memanggil open()
dan
close()
untuk bertransisi antara panel
daftar dan detail di ponsel. Metode ini tidak memiliki
jika kedua panel terlihat dan tidak tumpang tindih.
Saat panel daftar dan detail tumpang tindih, pengguna dapat melakukan gestur geser ke kedua arah secara
default, dengan bebas beralih antar dua panel bahkan saat tidak menggunakan navigasi
gestur. Anda dapat mengontrol arah gestur geser
dengan menyetel mode kunci SlidingPaneLayout
:
Kotlin
binding.slidingPaneLayout.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED
Java
binding.getSlidingPaneLayout().setLockMode(SlidingPaneLayout.LOCK_MODE_LOCKED);
Pelajari lebih lanjut
Untuk mempelajari lebih lanjut tentang mendesain {i>layout<i} untuk {i>form factor<i} yang berbeda, lihat dokumentasi berikut: