Kaydır

Kaydırma değiştiriciler

verticalScroll ve horizontalScroll değiştiriciler, kullanıcının bir öğenin sınırları maksimum boyut kısıtlamalarından büyük olduğunda öğeyi kaydırmasına imkan vermenin en basit yolunu sunar. verticalScroll ve horizontalScroll değiştiricileri sayesinde içerikleri çevirmenize veya belirli uzaklıkta kopyasını oluşturmanıza gerek kalmaz.

@Composable
private fun ScrollBoxes() {
    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .verticalScroll(rememberScrollState())
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

Kaydırma hareketlerine yanıt veren basit bir dikey liste

ScrollState, kaydırma konumunu değiştirmenize veya mevcut durumunu almanıza olanak tanır. Varsayılan parametrelerle oluşturmak için rememberScrollState() değerini kullanın.

@Composable
private fun ScrollBoxesSmooth() {
    // Smoothly scroll 100px on first composition
    val state = rememberScrollState()
    LaunchedEffect(Unit) { state.animateScrollTo(100) }

    Column(
        modifier = Modifier
            .background(Color.LightGray)
            .size(100.dp)
            .padding(horizontal = 8.dp)
            .verticalScroll(state)
    ) {
        repeat(10) {
            Text("Item $it", modifier = Modifier.padding(2.dp))
        }
    }
}

Kaydırılabilir değiştirici

scrollable değiştiricisi, scrollable ürününün kaydırma hareketlerini algılaması ancak içeriklerini dengelememesi açısından kaydırma değiştiricilerinden farklıdır. Bu değiştiricinin doğru şekilde çalışması için ScrollableState gereklidir. ScrollableState oluştururken, her kaydırma adımında (hareket girişi, yumuşak kaydırma veya kaydırma ile) delta değeri piksel cinsinden çağrılacak bir consumeScrollDelta işlevi sağlamanız gerekir. Bu işlev, scrollable değiştiricisine sahip iç içe yerleştirilmiş öğeler olduğunda etkinliğin doğru şekilde yayıldığından emin olmak için tüketilen kaydırma mesafesi miktarını döndürmelidir.

Aşağıdaki snippet, hareketleri algılar ve bir ofset için sayısal bir değer görüntüler, ancak herhangi bir öğeyi dengelemez:

@Composable
private fun ScrollableSample() {
    // actual composable state
    var offset by remember { mutableStateOf(0f) }
    Box(
        Modifier
            .size(150.dp)
            .scrollable(
                orientation = Orientation.Vertical,
                // Scrollable state: describes how to consume
                // scrolling delta and update offset
                state = rememberScrollableState { delta ->
                    offset += delta
                    delta
                }
            )
            .background(Color.LightGray),
        contentAlignment = Alignment.Center
    ) {
        Text(offset.toString())
    }
}

Parmağa basıldığını algılayan ve parmağın konumu için sayısal değeri gösteren bir kullanıcı arayüzü öğesi

İç içe kaydırma

Oluşturma işlemi,birden fazla öğenin tek bir kaydırma hareketine tepki verdiği iç içe yerleştirilmiş kaydırmayı destekler. İç içe kaydırmanın tipik bir örneği, başka bir listenin içindeki bir listedir. Daha karmaşık bir durum ise daraltılan araç çubuğudur.

Otomatik iç içe kaydırma

Basit iç içe kaydırma sizin herhangi bir işlem yapmanıza gerek yoktur. Kaydırma işlemi başlatan hareketler, çocuklardan üst öğelere otomatik olarak aktarılır. Böylece, çocuk daha fazla kaydırma yapamadığında, hareket kendi üst öğesi tarafından işlenir.

Otomatik iç içe kaydırma özelliği, oluşturma özelliğinin bazı bileşenleri ve değiştiricileri tarafından desteklenir ve kullanıma hazır olarak sunulur: verticalScroll, horizontalScroll, scrollable, Lazy API'leri ve TextField. Bu, kullanıcı iç içe yerleştirilmiş bileşenlerin iç alt öğesini kaydırdığında önceki değiştiricilerin kaydırma deltalarını iç içe kaydırma desteğine sahip üst öğelere yaydığı anlamına gelir.

Aşağıdaki örnekte, verticalScroll değiştiricisinin de uygulandığı bir kapsayıcı içinde kendisine verticalScroll değiştirici uygulanmış öğeler gösterilmektedir.

@Composable
private fun AutomaticNestedScroll() {
    val gradient = Brush.verticalGradient(0f to Color.Gray, 1000f to Color.White)
    Box(
        modifier = Modifier
            .background(Color.LightGray)
            .verticalScroll(rememberScrollState())
            .padding(32.dp)
    ) {
        Column {
            repeat(6) {
                Box(
                    modifier = Modifier
                        .height(128.dp)
                        .verticalScroll(rememberScrollState())
                ) {
                    Text(
                        "Scroll here",
                        modifier = Modifier
                            .border(12.dp, Color.DarkGray)
                            .background(brush = gradient)
                            .padding(24.dp)
                            .height(150.dp)
                    )
                }
            }
        }
    }
}

İç öğenin içindeki ve dışındaki hareketlere yanıt veren, iç içe yerleştirilmiş iki dikey kaydırma kullanıcı arayüzü öğesi

nestedScroll değiştiricisini kullanma

Birden çok öğe arasında gelişmiş bir koordine kaydırma oluşturmanız gerekiyorsa nestedScroll değiştiricisi, iç içe yerleştirilmiş bir kaydırma hiyerarşisi tanımlayarak size daha fazla esneklik sağlar. Önceki bölümde belirtildiği gibi, bazı bileşenlerde yerleşik kaydırma desteği bulunur. Bununla birlikte, Box veya Column gibi otomatik olarak kaydırılamayan composable'lar için bu tür bileşenlerdeki kaydırma deltaları, iç içe kaydırma sisteminde yayılmaz ve deltalar NestedScrollConnection öğesine veya üst bileşene ulaşmaz. Bu sorunu çözmek için özel bileşenler de dahil olmak üzere diğer bileşenlere bu tür destek sağlamak üzere nestedScroll özelliğini kullanabilirsiniz.

İç içe kaydırma birlikte çalışabilirlik (Compose 1.2.0'dan itibaren)

Kaydırılabilir View öğelerini, kaydırılabilir composable'ların içine veya tersi şekilde iç içe yerleştirmeye çalıştığınızda sorunlarla karşılaşabilirsiniz. En dikkat çekici olanlar, alt öğeyi kaydırıp başlangıç veya bitiş sınırlarına ulaştığınızda ve üst öğenin kaydırmayı tamamlamasını beklediğinizde gerçekleşir. Ancak bu beklenen davranış gerçekleşmeyebilir veya beklendiği gibi çalışmayabilir.

Bu sorun, kaydırılabilir composable'larda yerleşik olarak bulunan beklentilerin bir sonucudur. Kaydırılabilir composable'ların "nested-scroll-by-default" kuralı vardır. Bu, tüm kaydırılabilir container'ların hem NestedScrollConnection üzerinden üst öğe olarak hem de NestedScrollDispatcher aracılığıyla alt öğe olarak iç içe kaydırma zincirine katılması gerektiği anlamına gelir. Ardından, alt öğe sınırdayken üst öğe için iç içe yerleştirilmiş bir kaydırma yürütür. Örneğin, bu kural Oluştur Pager ve Oluştur LazyRow öğelerini birlikte sorunsuzca kullanmanızı sağlar. Ancak birlikte çalışabilirlik kaydırması ViewPager2 veya RecyclerView ile yapılırken NestedScrollingParent3 uygulanmadığından, alt öğeden üst öğeye sürekli kaydırma yapmak mümkün değildir.

Kaydırılabilir View öğeleri ile her iki yönde iç içe yerleştirilmiş kaydırılabilir composable'lar arasında iç içe kaydırma birlikte çalışabilirlik API'sini etkinleştirmek için aşağıdaki senaryolarda, iç içe yerleştirilmiş kayan birlikte çalışabilirlik API'sini kullanarak bu sorunları hafifletebilirsiniz.

ComposeView adlı alt öğeyi içeren, iş birliği yapan bir üst (View)

İşbirliği yapan üst View, hâlihazırda NestedScrollingParent3 yöntemini uygulayan ve bu nedenle, işbirliği yapan iç içe yerleştirilmiş bir alt composable'dan kaydırma deltaları alabilen üst öğedir. ComposeView bu durumda bir alt öğe olarak hareket eder ve NestedScrollingChild3'i (dolaylı olarak) uygulamalıdır. İşbirliği yapan üst iş ortaklarına örnek olarak androidx.coordinatorlayout.widget.CoordinatorLayout verilebilir.

Kaydırılabilir View üst container'ları ve iç içe kaydırılabilir alt composable'lar arasında iç içe kaydırma birlikte çalışabilirliğine ihtiyacınız varsa rememberNestedScrollInteropConnection() kullanabilirsiniz.

rememberNestedScrollInteropConnection(), NestedScrollingParent3 üst öğesi ile bir Compose alt öğesi arasında iç içe kaydırma birlikte çalışabilirliği sağlayan NestedScrollConnection özelliğine izin verir ve bu özelliği hatırlar.View Bu, bir nestedScroll değiştiriciyle birlikte kullanılmalıdır. Oluşturma tarafında iç içe kaydırma özelliği varsayılan olarak etkinleştirildiğinden, View tarafında iç içe kaydırmayı etkinleştirmek ve Views ile composable'lar arasına gerekli birleştirici mantığı eklemek için bu bağlantıyı kullanabilirsiniz.

Aşağıdaki örnekte gösterilen, CoordinatorLayout, CollapsingToolbarLayout ve bir "child composable"ın kullanımı sık görülen bir kullanım alanıdır:

<androidx.coordinatorlayout.widget.CoordinatorLayout
    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">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:fitsSystemWindows="true">

        <com.google.android.material.appbar.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <!--...-->

        </com.google.android.material.appbar.CollapsingToolbarLayout>

    </com.google.android.material.appbar.AppBarLayout>

    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/compose_view"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Etkinliğinizde veya Bölümünüzde, çocuğunuz için composable'ı ve gerekli NestedScrollConnection öğelerini ayarlamanız gerekir:

open class MainActivity : ComponentActivity() {
    @OptIn(ExperimentalComposeUiApi::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<ComposeView>(R.id.compose_view).apply {
            setContent {
                val nestedScrollInterop = rememberNestedScrollInteropConnection()
                // Add the nested scroll connection to your top level @Composable element
                // using the nestedScroll modifier.
                LazyColumn(modifier = Modifier.nestedScroll(nestedScrollInterop)) {
                    items(20) { item ->
                        Box(
                            modifier = Modifier
                                .padding(16.dp)
                                .height(56.dp)
                                .fillMaxWidth()
                                .background(Color.Gray),
                            contentAlignment = Alignment.Center
                        ) {
                            Text(item.toString())
                        }
                    }
                }
            }
        }
    }
}

Alt öğe (AndroidView) içeren bir üst composable

Bu senaryoda, Oluşturma tarafında iç içe yerleştirilmiş kaydırma birlikte çalışabilirlik API'sinin uygulanması ele alınmaktadır (alt AndroidView içeren bir üst composable'ınız olduğunda). AndroidView, View kaydırma alt öğesi olarak kullanıldığı için NestedScrollDispatcher özelliğinin yanı sıra NestedScrollingParent3 öğesini de hem Compose kaydırma üst öğesi için de alt öğe olarak görev yapar hem de NestedScrollingParent3 uygular. Böylece Compose üst öğesi iç içe yerleştirilmiş kaydırılabilir bir alt öğeden (View) iç içe yerleştirilmiş kaydırma deltaları alabilir.

Aşağıdaki örnekte, bu senaryoda Daraltılabilen bir Oluştur araç çubuğu ile birlikte iç içe kaydırma birlikte çalışabilirliğini nasıl gerçekleştirebileceğiniz gösterilmektedir:

@Composable
private fun NestedScrollInteropComposeParentWithAndroidChildExample() {
    val toolbarHeightPx = with(LocalDensity.current) { ToolbarHeight.roundToPx().toFloat() }
    val toolbarOffsetHeightPx = remember { mutableStateOf(0f) }

    // Sets up the nested scroll connection between the Box composable parent
    // and the child AndroidView containing the RecyclerView
    val nestedScrollConnection = remember {
        object : NestedScrollConnection {
            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
                // Updates the toolbar offset based on the scroll to enable
                // collapsible behaviour
                val delta = available.y
                val newOffset = toolbarOffsetHeightPx.value + delta
                toolbarOffsetHeightPx.value = newOffset.coerceIn(-toolbarHeightPx, 0f)
                return Offset.Zero
            }
        }
    }

    Box(
        Modifier
            .fillMaxSize()
            .nestedScroll(nestedScrollConnection)
    ) {
        TopAppBar(
            modifier = Modifier
                .height(ToolbarHeight)
                .offset { IntOffset(x = 0, y = toolbarOffsetHeightPx.value.roundToInt()) }
        )

        AndroidView(
            { context ->
                LayoutInflater.from(context)
                    .inflate(R.layout.view_in_compose_nested_scroll_interop, null).apply {
                        with(findViewById<RecyclerView>(R.id.main_list)) {
                            layoutManager = LinearLayoutManager(context, VERTICAL, false)
                            adapter = NestedScrollInteropAdapter()
                        }
                    }.also {
                        // Nested scrolling interop is enabled when
                        // nested scroll is enabled for the root View
                        ViewCompat.setNestedScrollingEnabled(it, true)
                    }
            },
            // ...
        )
    }
}

private class NestedScrollInteropAdapter :
    Adapter<NestedScrollInteropAdapter.NestedScrollInteropViewHolder>() {
    val items = (1..10).map { it.toString() }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): NestedScrollInteropViewHolder {
        return NestedScrollInteropViewHolder(
            LayoutInflater.from(parent.context)
                .inflate(R.layout.list_item, parent, false)
        )
    }

    override fun onBindViewHolder(holder: NestedScrollInteropViewHolder, position: Int) {
        // ...
    }

    class NestedScrollInteropViewHolder(view: View) : ViewHolder(view) {
        fun bind(item: String) {
            // ...
        }
    }
    // ...
}

Bu örnekte, scrollable değiştiricisiyle API'yi nasıl kullanabileceğiniz gösterilmektedir:

@Composable
fun ViewInComposeNestedScrollInteropExample() {
    Box(
        Modifier
            .fillMaxSize()
            .scrollable(rememberScrollableState {
                // View component deltas should be reflected in Compose
                // components that participate in nested scrolling
                it
            }, Orientation.Vertical)
    ) {
        AndroidView(
            { context ->
                LayoutInflater.from(context)
                    .inflate(android.R.layout.list_item, null)
                    .apply {
                        // Nested scrolling interop is enabled when
                        // nested scroll is enabled for the root View
                        ViewCompat.setNestedScrollingEnabled(this, true)
                    }
            }
        )
    }
}

Son olarak, bu örnekte başarılı bir sürükleme ve kapatma davranışı elde etmek için iç içe yerleştirilmiş kayan birlikte çalışabilirlik API'sinin BottomSheetDialogFragment ile nasıl kullanıldığı gösterilmektedir:

class BottomSheetFragment : BottomSheetDialogFragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val rootView: View = inflater.inflate(R.layout.fragment_bottom_sheet, container, false)

        rootView.findViewById<ComposeView>(R.id.compose_view).apply {
            setContent {
                val nestedScrollInterop = rememberNestedScrollInteropConnection()
                LazyColumn(
                    Modifier
                        .nestedScroll(nestedScrollInterop)
                        .fillMaxSize()
                ) {
                    item {
                        Text(text = "Bottom sheet title")
                    }
                    items(10) {
                        Text(
                            text = "List item number $it",
                            modifier = Modifier.fillMaxWidth()
                        )
                    }
                }
            }
            return rootView
        }
    }
}

rememberNestedScrollInteropConnection()'in, eklediğiniz öğeye bir NestedScrollConnection yükleyeceğini unutmayın. NestedScrollConnection, deltaları Oluşturma düzeyinden View düzeyine iletmekten sorumludur. Bu, öğenin iç içe kaydırmaya katılmasını sağlar ancak öğelerin otomatik olarak kaydırılmasını sağlamaz. Box veya Column gibi otomatik olarak kaydırılamayan composable'lar için, bu tür bileşenlerdeki kaydırma deltaları iç içe kaydırma sisteminde yayılmaz ve deltalar, rememberNestedScrollInteropConnection() tarafından sağlanan NestedScrollConnection değerine ulaşmaz. Dolayısıyla, bu deltalar üst View bileşenine ulaşmaz. Bu sorunu çözmek için bu tür iç içe yerleştirilmiş composable'lar için kaydırılabilir değiştiriciler ayarladığınızdan emin olun. Daha ayrıntılı bilgi için önceki İç içe kaydırma bölümüne bakabilirsiniz.

ComposeView alt yayıncısı olan, iş birliği yapmayan bir üst (View)

İşbirliği yapmayan Görünüm, View tarafında gerekli NestedScrolling arayüzlerinin uygulanmadığı Görünümdür. Bunun, bu Views ile iç içe kaydırma birlikte çalışabilirliğinin kullanıma hazır olarak çalışmayacağı anlamına geldiğini unutmayın. İşbirliği yapmayan Views: RecyclerView ve ViewPager2.