İki bölmeli düzen oluşturma

Oluşturma yöntemini deneyin
Android için önerilen kullanıcı arayüzü araç seti Jetpack Compose'dur. Oluşturma bölümünde düzenlerle nasıl çalışacağınızı öğrenin.

Uygulamanızdaki her ekran, mevcut alana uyum sağlayacak şekilde duyarlı olmalıdır. ConstraintLayout ile tek bölmeli yaklaşımın birçok boyuta ölçeklendirilmesine olanak tanıyan duyarlı bir kullanıcı arayüzü oluşturabilirsiniz. Ancak daha büyük cihazlar, düzenin birden fazla bölmeye bölünmesinden yararlanabilir. Örneğin, bir ekranda seçili öğenin ayrıntılarının yanında öğelerin listesini göstermek isteyebilirsiniz.

SlidingPaneLayout bileşeni, daha büyük cihazlarda ve katlanabilir cihazlarda iki bölmenin yan yana gösterilmesini desteklerken telefonlar gibi daha küçük cihazlarda bir seferde yalnızca bir bölmeyi göstermek için otomatik olarak uyum sağlar.

Cihaza özel yardım için ekran uyumluluğuna genel bakış başlıklı makaleyi inceleyin.

Kurulum

SlidingPaneLayout'ü kullanmak için uygulamanızın build.gradle dosyasına aşağıdaki bağımlılığı ekleyin:

Eski

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 seviyesinde kullanılmak üzere yatay, iki bölmeli bir düzen sağlar. Bu düzende, ilk bölme bir içerik listesi veya tarayıcı olarak kullanılır. Diğer bölmede içerik göstermek için birincil ayrıntı görünümüne bağlıdır.

SlidingPaneLayout örneğini gösteren resim
Şekil 1. SlidingPaneLayout ile oluşturulmuş bir düzen örneği.

SlidingPaneLayout, panellerin yan yana gösterilip gösterilmeyeceğini belirlemek için iki panelin genişliğini kullanır. Örneğin, liste bölmesinin minimum boyutu 200 dp, ayrıntı bölmesinin ise 400 dp olarak ölçülürse SlidingPaneLayout, en az 600 dp genişlik bulunduğu sürece iki bölmeyi otomatik olarak yan yana gösterir.

Alt görünümlerin toplam genişliği SlidingPaneLayout'teki mevcut genişliği aşarsa alt görünümler üst üste gelir. Bu durumda, alt görünümler SlidingPaneLayout içindeki kullanılabilir genişliği dolduracak şekilde genişler. Kullanıcı, en üstteki görünümü ekranın kenarından geri sürükleyerek yolun dışına itebilir.

Görünümler örtüşmüyorsa 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şlik için geçerlidir.

Ekranında her iki görünümü de yan yana gösterecek kadar yer bulunan katlanabilir cihazlarda SlidingPaneLayout, iki bölmenin boyutunu otomatik olarak ayarlar. Böylece, bölmeler örtüşen bir kat veya menteşenin iki tarafına yerleştirilir. Bu durumda, ayarlanan genişlikler, katlama özelliğinin her iki tarafında da bulunması gereken minimum genişlik olarak kabul edilir. Bu minimum boyutu korumak için yeterli alan yoksa SlidingPaneLayout, görünümleri örtüşecek şekilde geri döner.

Sol bölmesinde RecyclerView ve sol bölmede bulunan içeriği görüntülemek için birincil ayrıntı görünümü olarak FragmentContainerView bulunan bir SlidingPaneLayout'ün kullanımına dair bir örnek aşağıda 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 öğesindeki android:name özelliği ilk parçayı ayrıntılar bölmesine ekleyerek uygulama ilk kez açıldığında büyük ekranlı cihazlardaki kullanıcıların boş bir sağ bölme görmesini önler.

Ayrıntılar bölmesini programatik olarak değiştirme

Önceki XML örneğinde, RecyclerView bölümündeki bir öğeye dokunulduğunda ayrıntı bölmesinde bir değişiklik tetiklenir. Parçalar kullanıldığında, sağ bölmenin yerini alacak bir FragmentTransaction gerekir. Bu FragmentTransaction, yeni görünen parçaya geçmek için SlidingPaneLayout üzerinde open() çağrısı yapar:

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, FragmentTransaction üzerinde addToBackStack() çağırmıyor. Bu sayede ayrıntı bölmesinde arka yığın oluşturulmaz.

Bu sayfadaki örneklerde SlidingPaneLayout doğrudan kullanılır ve parça işlemlerini manuel olarak yönetmeniz gerekir. Ancak gezinme bileşeni, liste 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 bir düzenin önceden oluşturulmuş bir uygulamasını sağlar.

Bu sayede XML düzeni yapılandırmanızı basitleştirebilirsiniz. SlidingPaneLayout ve her iki bölmeniz de açıkça beyan edilmek yerine, düzeninizin AbstractListDetailFragment uygulamanızı barındırmak için yalnızca bir FragmentContainerView'e ihtiyacı vardır:

<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() öğelerini uygulayın. Ayrıntı bölmesi için AbstractListDetailFragment, NavHostFragment kullanır. Yani yalnızca ayrıntı bölmesinde gösterilecek hedefleri içeren bir gezinme grafiği tanımlayabilirsiniz. Ardından, ayrıntı bölmenizi kendi kendine yeten gezinme grafiğindeki hedefler arasında değiştirmek için NavController simgesini 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 herhangi bir dış gezinme grafiğinde bulunmamalıdır. Ancak ayrıntı bölmesinin gezinme grafiğindeki tüm derin bağlantılar, SlidingPaneLayout'yi barındıran hedefe eklenmelidir. Bu, harici derin bağlantıların önce SlidingPaneLayout hedefine, ardından doğru ayrıntı bölmesi hedefine yönlendirilmesini sağlar.

Navigasyon bileşenini kullanarak iki bölmeli bir düzenin tam olarak uygulanması için TwoPaneFragment örneğine bakın.

Sistem geri düğmesiyle entegrasyon

Liste ve ayrıntı bölmelerinin örtüştüğü 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 yapmak için özel geri gezinme sağlayın ve SlidingPaneLayout'in mevcut durumuna bir OnBackPressedCallback bağlayı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);
    }
}

addCallback() kullanarak geri aramayı OnBackPressedDispatcher alanına 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, telefonlarda liste ve ayrıntı bölmeleri arasında geçiş yapmak için her zaman open() ve close() işlevlerini manuel olarak çağırmanıza olanak tanır. Her iki bölme de görünür durumdaysa ve örtüşmüyorsa bu yöntemler hiçbir etki göstermez.

Liste ve ayrıntı bölmeleri çakıştığında kullanıcılar varsayılan olarak her iki yönde de kaydırabilir ve işaret gezinme özelliğini kullanmasalar bile iki bölme arasında özgürce geçiş yapabilir. 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 üzere aşağıdaki dokümanlara bakın:

Ek kaynaklar