İki bölmeli düzen oluşturma

"Oluştur" yöntemini deneyin
Jetpack Compose, Android için önerilen kullanıcı arayüzü araç setidir. Compose'da düzenlerle nasıl çalışacağınızı öğrenin.

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 örneği gösteren bir 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 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.

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