İki bölmeli düzen oluşturma

Compose 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 pencereli yaklaşımın birçok boyuta ölçeklenmesini sağlayan ConstraintLayout ile duyarlı bir kullanıcı arayüzü oluşturabilirsiniz. Ancak daha büyük cihazlar, düzeni birden fazla bölmeye ayırmaktan faydalanabilir. Örneğin, bir ekranda öğe listesinin yanında seçilen öğenin ayrıntılarının listelenmesini isteyebilirsiniz.

SlidingPaneLayout bileşeni, daha büyük cihazlarda ve katlanabilir cihazlarda iki bölmenin yan yana gösterilmesini desteklerken telefon gibi daha küçük cihazlarda otomatik olarak uyarlanarak tek seferde yalnızca bir bölme gösterir.

Cihaza özel rehberlik 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üzen yapılandırması

SlidingPaneLayout, kullanıcı arayüzünün üst seviyesinde kullanılmak üzere yatay, iki panelli bir düzen sağlar. Bu düzende ilk bölme, içerik listesi veya tarayıcı olarak kullanılır. Diğer bölmede içerik görüntülemek için birincil ayrıntı görünümüne bağlıdır.

SlidingPaneLayout örneğini gösteren bir resim
Şekil 1. SlidingPaneLayout ile oluşturulan 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 olarak ölçülürse ve ayrıntı bölmesi için 400 dp gerekirse SlidingPaneLayout, en az 600 dp genişliğe sahip olduğu sürece iki bölmeyi yan yana otomatik olarak gösterir.

Birleştirilmiş genişlikleri SlidingPaneLayout içindeki kullanılabilir genişliği aşıyorsa alt görünümler üst üste gelir. Bu durumda, çocuğun görüntüleri SlidingPaneLayout içinde kullanılabilir genişliği dolduracak şekilde genişletilir. Kullanıcı, en üstteki görünümü ekranın kenarından geri sürükleyerek kaldırabilir.

Görünümler çakış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 düzen parametresinin layout_weight kullanılmasını destekler. Bu parametre yalnızca genişlik için geçerlidir.

Ekranda iki görünümü yan yana gösterecek alan bulunan katlanabilir cihazlarda SlidingPaneLayout, iki bölmenin boyutunu otomatik olarak ayarlar. Böylece bölmeler, üst üste gelen bir kat veya menteşenin iki tarafına yerleştirilir. 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, görünümleri tekrar çakıştırmaya başlar.

İçeriği sol bölmeden göstermek için sol bölmesi SlidingPaneLayout olan ve birincil ayrıntı görünümü olarak RecyclerView kullanılan bir FragmentContainerView örneği 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 üzerindeki android:name özelliği, ayrıntılar bölmesine ilk parçayı ekleyerek uygulamanın ilk başlatılmasında büyük ekranlı cihazlardaki kullanıcıların boş bir sağ bölme görmemesini sağlar.

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

Önceki XML örneğinde, RecyclerView öğesindeki bir öğeye dokunulduğunda ayrıntı bölmesinde değişiklik tetiklenir. Parçalar kullanılırken bu, sağ bölmenin yerini alan bir FragmentTransaction gerektirir. Yeni görünür parçaya geçmek için SlidingPaneLayout üzerinde open() çağrılı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() işlevini çağırmaz. Bu, ayrıntı bölmesinde geri yığını oluşturulmasını önler.

Bu sayfadaki örneklerde doğrudan SlidingPaneLayout kullanılır ve parça işlemlerini manuel olarak yönetmeniz gerekir. Ancak Navigation component, AbstractListDetailFragment aracılığıyla iki panelli düzenin önceden oluşturulmuş bir uygulamasını sağlar. SlidingPaneLayout, liste ve ayrıntı panellerinizi yönetmek için arka planda SlidingPaneLayout kullanan bir API sınıfıdır.

Bu sayede XML düzen yapılandırmanızı basitleştirebilirsiniz. SlidingPaneLayout ve her iki bölmenizi açıkça bildirmek yerine, düzeninizde AbstractListDetailFragment uygulamanızı tutacak bir FragmentContainerView olması yeterlidir:

<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() 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, bağımsız gezinme grafiğindeki hedefler arasında ayrıntı bölmenizi 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ılar bölmesinin gezinme grafiğindeki hedefler, herhangi bir dış ve uygulama genelinde 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 sayede, harici derin bağlantıların önce SlidingPaneLayout hedefine, ardından doğru ayrıntı bölmesi hedefine gitmesi sağlanır.

Navigation bileşenini kullanarak iki panelli düzenin tam uygulamasını görmek için TwoPaneFragment örneğine bakın.

Sistemin geri düğmesiyle entegrasyon

Liste ve ayrıntı panellerinin çakıştığı daha küçük cihazlarda, sistemin geri düğmesinin kullanıcıyı ayrıntı panelinden liste paneline geri götürdüğünden emin olun. Bunu, özel geri gezinme sağlayarak ve 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ğırma işlevini 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, telefonlarda liste ve ayrıntı bölmeleri arasında geçiş yapmak için open() ve close() simgelerini manuel olarak kullanmanıza her zaman olanak tanır. Her iki bölme de görünürse ve üst üste gelmezse bu yöntemlerin 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 gezinme özelliğini kullanmadıklarında bile iki bölme arasında serbestçe geçiş yapabilir. SlidingPaneLayout 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 istiyorsanız aşağıdaki belgelere bakın:

Ek kaynaklar