İki bölmeli düzen oluşturma

Oluşturma yöntemini deneyin
Jetpack Compose, Android için önerilen kullanıcı arayüzü araç setidir. Compose'da düzenlerle çalışma hakkında bilgi edinin.

Uygulamanızdaki her ekran duyarlı olmalı ve mevcut alana uyum sağlamalıdır. ConstraintLayout ile tek bölmeli bir yaklaşımın birden fazla boyuta ölçeklenebilmesini sağlayan duyarlı bir kullanıcı arayüzü oluşturabilirsiniz ancak daha büyük cihazlarda düzeni birden fazla bölmeye bölmek fayda sağlayabilir. Örneğin, bir ekranda, seçilen öğenin ayrıntıları listesinin yanında öğe listesinin gösterilmesini isteyebilirsiniz.

SlidingPaneLayout bileşeni, daha büyük cihazlarda ve katlanabilir cihazlarda iki bölmenin yan yana gösterilmesini destekler. Aynı zamanda telefon gibi daha küçük cihazlarda aynı anda yalnızca bir bölmeyi gösterecek şekilde otomatik olarak uyarlanır.

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ılmak üzere yatay, iki bölmeli bir düzen sağlar. Bu düzen, ilk bölmeyi bir içerik listesi veya tarayıcı olarak kullanır ve diğer bölmedeki içeriğin görüntülenmesi için birincil ayrıntı görünümüne bağımlıdır.

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

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 boyutunun minimum 200 dp olduğu ve ayrıntı bölmesinin boyutunun 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.

Alt genişliklerinin toplamı SlidingPaneLayout içinde kullanılabilen genişliği aşıyorsa alt görüntülemeler çakışır. Bu durumda, alt görünümler, SlidingPaneLayout içindeki mevcut genişliği dolduracak şekilde genişler. Kullanıcı, en üstteki görünümü ekranın kenarından geri 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öleceğini tanımlamak için alt görünümlerde layout_weight düzen parametresinin kullanımını destekler. Bu parametre yalnızca genişlikle alakalıdır.

Ekranda her iki görünümü yan yana gösterecek alanı olan katlanabilir cihazlarda SlidingPaneLayout, iki bölmenin boyutunu otomatik olarak ayarlayarak iki bölmenin boyutunu, üst üste binmiş kat veya menteşenin her iki tarafına gelecek şekilde konumlandırır. Bu durumda, ayarlanan genişlikler, katlama özelliğinin her bir kenarında olması gereken minimum genişlik olarak kabul edilir. Bu minimum boyutu korumak için yeterli alan yoksa SlidingPaneLayout, görünümleri örtüşmeye devam eder.

Sol bölmede RecyclerView ve birincil ayrıntı görünümü olarak FragmentContainerView bulunan bir SlidingPaneLayout, sol bölmedeki içerikleri görüntülemek için aşağıdaki gibi kullanılabilir:

<!-- 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, ayrıntı bölmesine başlangıç parçasını ekleyerek uygulama ilk kez başlatıldığında büyük ekranlı cihazlardaki kullanıcıların boş bir sağ bölme görmemesini sağlar.

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

Yukarıdaki XML örneğinde, RecyclerView içindeki bir öğeye dokunmak ayrıntı bölmesinde bir değişikliği tetikler. Parça kullanılırken sağ bölmenin yerini alan bir FragmentTransaction gerekir. Bu işlem, yeni görünen parçaya geçmek için SlidingPaneLayout üzerinde open() yöntemini çağırır:

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() çağrısı yapmaz. Bu, ayrıntı bölmesinde geri yığın oluşturmaktan kaçınır.

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, AbstractListDetailFragment aracılığıyla iki bölmeli düzenin önceden oluşturulmuş bir uygulamasını sunar. AbstractListDetailFragment, listenizi ve ayrıntı bölmelerinizi yönetmek için arka planda SlidingPaneLayout kullanan bir API sınıfıdır.

Bu, XML düzeni yapılandırmanızı basitleştirmenizi sağlar. Düzeninizin SlidingPaneLayout ve her iki bölmenizi de açık bir şekilde belirtmek yerine AbstractListDetailFragment uygulamanızı barındırmak için yalnızca 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() uygulamalarını uygulayın. Ayrıntı bölmesi için AbstractListDetailFragment, bir NavHostFragment kullanır. Bu, yalnızca ayrıntı bölmesinde gösterilecek hedefleri içeren bir gezinme grafiği tanımlayabileceğiniz anlamına gelir. Ardından, ayrıntı bölmenizi bağımsız gezinme grafiğindeki hedefler arasında değiştirmek için NavController aracını 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 öğ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 kullanan iki bölmeli düzenin tam olarak uygulanması için TwoPaneFragment örneğine bakın.

Sistem geri düğmesiyle entegre et

Liste ve ayrıntı bölmelerinin çakıştığı daha küçük cihazlarda, sistemdeki geri düğmesinin kullanıcıyı ayrıntı penceresinden liste bölmesine geri götürdüğünden emin olun. Bunu özel geri gezinme sağlayarak ve SlidingPaneLayout öğesinin geçerli durumuna bir OnBackPressedCallback 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ı addCallback() kullanarak OnBackPressedDispatcher öğesine 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.
    }
}

Kilitleme modu

SlidingPaneLayout, telefonlarda liste ve ayrıntı bölmeleri arasında geçiş yapmak için her zaman open() ve close() numaralarını manuel olarak çağırmanıza olanak tanır. Her iki bölme de görünür durumdaysa ve üst üste binmiyorsa bu yöntemlerin hiçbir etkisi olmaz.

Liste ve ayrıntı bölmeleri çakıştığında, kullanıcılar varsayılan olarak her iki yönde de kaydırabilir ve hareketle gezinmeyi kullanmasalar bile iki bölme arasında serbestçe geçiş yapabilirler. SlidingPaneLayout öğesinin 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üzen tasarlama hakkında daha fazla bilgi edinmek üzere aşağıdaki belgelere bakın:

Ek kaynaklar