Uygulamanızdaki her ekran duyarlı olmalı ve mevcut alana uyum sağlamalıdır.
Tek bölmeyle birden fazla boyuta ölçekleme imkanı sunan ConstraintLayout
ile duyarlı bir kullanıcı arayüzü oluşturabilirsiniz. Ancak daha büyük cihazlar düzeni birden fazla bölmeye bölmek faydalı olabilir. Örneğin, bir ekranda, seçilen öğenin ayrıntılar listesinin yanında
öğe listesi gösterilmesini isteyebilirsiniz.
SlidingPaneLayout
bileşeni, telefonlar gibi küçük cihazlarda aynı anda yalnızca bir bölmeyi gösterecek şekilde otomatik olarak uyarlanırken daha büyük cihazlarda ve katlanabilir cihazlarda iki bölmenin yan yana gösterilmesini destekler.
Cihaza özel yardım için ekran uyumluluğuna genel bakış konusuna bakın.
Kurulum
SlidingPaneLayout
kullanmak için uygulamanızın build.gradle
dosyasına aşağıdaki bağımlılığı ekleyin:
Modern
dependencies { implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0" }
Kotlin
dependencies { implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0") }
XML düzeni yapılandırması
SlidingPaneLayout
, kullanıcı arayüzünün üst düzeyinde kullanım için yatay, iki bölmeli bir düzen sağlar. Bu düzen, ilk bölmeyi içerik listesi veya tarayıcı olarak kullanır ve ardından, diğer bölmedeki içeriği görüntülemek için birincil ayrıntı görünümüne bağlı olur.
SlidingPaneLayout
, bölmelerin yan yana gösterilip gösterilmeyeceğini belirlemek için iki bölmenin genişliğini kullanır. Örneğin, liste bölmesinin minimum 200 dp boyutunda olması ve ayrıntı bölmesinin 400 dp olması gerekiyorsa SlidingPaneLayout
, en az 600 dp genişliğe sahip olduğu sürece iki bölmeyi otomatik olarak yan yana gösterir.
Birleşik genişlikleri, SlidingPaneLayout
içinde kullanılabilen genişliği aşıyorsa alt görünümler çakışır. Bu durumda, alt görünümler SlidingPaneLayout
içindeki mevcut genişliği dolduracak şekilde genişletilir. Kullanıcı, en üstteki görünümü ekranın kenarından
geriye doğru sürükleyerek görünümün dışına kaydırabilir.
Görünümler çakışmazsa SlidingPaneLayout
, ölçüm tamamlandıktan sonra kalan alanın nasıl bölüneceğini tanımlamak için alt görünümlerde layout_weight
düzen parametresinin kullanılmasını destekler. Bu parametre yalnızca genişlikle ilgilidir.
SlidingPaneLayout
, ekranında her iki görünümü yan yana gösterecek alanı olan katlanabilir cihazlarda iki bölmenin boyutunu, üst üste binen katlama veya menteşenin her iki tarafına konumlanacak şekilde otomatik olarak ayarlar. Bu durumda, ayarlanan genişlikler, katlama özelliğinin her iki tarafında bulunması gereken minimum genişlik olarak kabul edilir. Bu minimum boyutu korumak için yeterli alan yoksa SlidingPaneLayout
, tekrar çakışan görünümlere geçer.
Aşağıda, sol bölmede RecyclerView
ve birincil ayrıntı görünümü olarak sol bölmedeki içeriği görüntülemek için FragmentContainerView
yer alan bir SlidingPaneLayout
örneği verilmiştir:
<!-- 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>
Bu örnekte, FragmentContainerView
üzerindeki android:name
özelliği, ilk parçayı ayrıntı bölmesine ekleyerek büyük ekranlı cihazlardaki kullanıcıların uygulama ilk kez başlatıldığında boş bir sağ bölme görmemesini sağlar.
Ayrıntı bölmesini programatik olarak değiştirme
Önceki XML örneğinde, RecyclerView
içindeki bir öğeye dokunmak ayrıntı bölmesinde bir değişikliği tetikler. Parçalar kullanırken bu işlem, yeni görünür parçaya geçmek için SlidingPaneLayout
üzerinde open()
çağrısı yapan ve sağ bölmenin yerini alan bir FragmentTransaction
gerektirir:
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(); }
Bu kod özellikle FragmentTransaction
üzerinde addToBackStack()
işlevini çağırmaz. Bu, ayrıntı bölmesinde arka yığın oluşturmaktan kaçınmanızı sağlar.
Gezinme bileşeni uygulaması
Bu sayfadaki örnekler doğrudan SlidingPaneLayout
özelliğini kullanır ve parça işlemlerini manuel olarak yönetmenizi gerektirir. Bununla birlikte, gezinme bileşeni, listenizi ve ayrıntı bölmelerinizi yönetmek için arka planda SlidingPaneLayout
kullanan bir API sınıfı olan AbstractListDetailFragment
aracılığıyla iki bölmeli düzenin önceden oluşturulmuş uygulamasını sağlar.
Bu, XML düzeni yapılandırmanızı basitleştirmenize olanak tanır. SlidingPaneLayout
öğesini ve her iki bölmenizi de açıkça tanımlamak yerine düzeniniz için AbstractListDetailFragment
uygulamanızı barındırması için yalnızca bir FragmentContainerView
olması gerekir:
<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>
Liste bölmeniz için özel bir görünüm sağlamak üzere onCreateListPaneView()
ve onListPaneViewCreated()
özelliklerini uygulayın. Ayrıntı bölmesi için AbstractListDetailFragment
bir NavHostFragment
kullanır.
Böylece, yalnızca ayrıntı bölmesinde gösterilecek hedefleri içeren bir gezinme grafiği tanımlayabilirsiniz. Ardından, ayrıntı bölmenizi bağımsız gezinme grafiğindeki hedefler arasında değiştirmek için NavController
kullanabilirsiniz:
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(); }
Ayrıntı bölmesinin gezinme grafiğindeki hedefler, uygulama genelindeki hiçbir dış gezinme grafiğinde bulunmamalıdır. Ancak, ayrıntı bölmesinin gezinme grafiğindeki derin bağlantılar, SlidingPaneLayout
öğesini barındıran hedefe eklenmelidir. Bu, harici derin bağlantıların önce SlidingPaneLayout
hedefine, ardından doğru ayrıntı bölmesi hedefine gitmesine yardımcı olur.
Gezinme bileşenini kullanarak iki bölmeli düzenin tam uygulaması için TwoPaneFragment örneğine bakın.
Sistemin geri düğmesiyle entegrasyon
Liste ve ayrıntı bölmelerinin çakışan daha küçük cihazlarda, sistem geri düğmesinin kullanıcıyı ayrıntı bölmesinden liste bölmesine geri götürdüğünden emin olun. Bunu özel geri gezinme sağlayarak ve bir OnBackPressedCallback
öğesini SlidingPaneLayout
öğesinin mevcut durumuna bağlayarak yapın:
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); } }
Geri çağırmayı OnBackPressedDispatcher
öğesine addCallback()
kullanarak ekleyebilirsiniz:
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. } }
Kilit modu
SlidingPaneLayout
, telefonlardaki liste ve ayrıntı bölmeleri arasında geçiş yapmak için her zaman open()
ve close()
öğelerini manuel olarak çağırmanıza olanak tanır. Her iki bölme de görünürse ve çakışmazsa bu yöntemlerin herhangi bir etkisi olmaz.
Liste ve ayrıntı bölmeleri çakıştığında, kullanıcılar hareketle gezinme özelliğini kullanmıyorken bile iki bölme arasında serbest bir şekilde geçiş yapmak için varsayılan olarak her iki yönde de kaydırabilir. SlidingPaneLayout
cihazının kilit modunu ayarlayarak kaydırma yönünü kontrol edebilirsiniz:
Kotlin
binding.slidingPaneLayout.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED
Java
binding.getSlidingPaneLayout().setLockMode(SlidingPaneLayout.LOCK_MODE_LOCKED);
Daha fazla bilgi
Farklı form faktörleri için düzenler tasarlama hakkında daha fazla bilgi edinmek istiyorsanız aşağıdaki belgelere bakın:
Ek kaynaklar
- Uyarlanabilir Düzenler codelab'i
- SlidingPaneLayout örneği GitHub'da bulabilirsiniz.