Kaydırma

Kaydırma değiştiricileri

verticalScroll ve horizontalScroll değiştiricileri, içeriğinin sınırları maksimum boyut kısıtlamalarından daha büyük olduğunda kullanıcının bir öğeyi kaydırmasına izin vermenin en basit yolunu sağlar. verticalScroll ve horizontalScroll değiştiricileriyle içerikleri çevirmeniz veya kaydırmanız gerekmez.

@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() 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 kaydırma hareketlerini algılayıp deltaları yakalaması ancak içeriklerini otomatik olarak kaydırmaması bakımından kaydırma değiştiricilerinden farklıdır. Bunun yerine, bu değiştiricinin doğru çalışması için gerekli olan ScrollableState aracılığıyla kullanıcıya yetki verilir.

ScrollableState oluştururken her kaydırma adımında (jest girişi, sorunsuz kaydırma veya hızlı kaydırma ile) piksel cinsinden delta ile çağrılacak bir consumeScrollDelta işlevi sağlamanız gerekir. Bu işlev, scrollable değiştiricisine sahip iç içe yerleştirilmiş öğelerin bulunduğu durumlarda etkinliğin düzgün şekilde yayılmasını sağlamak için kullanılan kaydırma mesafesini döndürmelidir.

Aşağıdaki snippet, hareketleri algılar ve bir ofset için sayısal değer gösterir ancak herhangi bir öğeyi ofsetlemez:

@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())
    }
}

Parmakla basmayı algılayan ve parmağın konumuna ilişkin sayısal değeri gösteren bir kullanıcı arayüzü öğesi

İç içe kaydırma

İç içe kaydırma, birbirini içeren birden fazla kaydırma bileşeninin tek bir kaydırma hareketine tepki vererek ve kaydırma farklarını (değişikliklerini) ileterek birlikte çalıştığı bir sistemdir.

İç içe kaydırma sistemi, kaydırılabilir ve hiyerarşik olarak bağlı (çoğu zaman aynı üst öğeyi paylaşarak) bileşenler arasında koordinasyon sağlar. Bu sistem, kaydırma kapsayıcılarını bağlar ve aralarında yayılan ve paylaşılan kaydırma farklarıyla etkileşime izin verir.

Compose, composable'lar arasında iç içe kaydırmayı işlemek için birden fazla yöntem sunar. İç içe kaydırmaya tipik bir örnek, başka bir listenin içindeki listedir. Daha karmaşık bir örnek ise daraltılabilir araç çubuğudur.

Otomatik iç içe kaydırma

Basit iç içe kaydırma için herhangi bir işlem yapmanız gerekmez. Kaydırma işlemini başlatan hareketler, alt öğelerden üst öğelere otomatik olarak yayılır. Bu nedenle, alt öğe daha fazla kaydıramadığında hareket, üst öğesi tarafından işlenir.

Otomatik iç içe kaydırma, Compose'un bazı bileşenleri ve değiştiricileri tarafından kutudan çıkar çıkmaz desteklenir ve sağlanır: verticalScroll, horizontalScroll, scrollable, Lazy API'leri ve TextField. Bu, kullanıcı iç içe yerleştirilmiş bileşenlerin iç içe yerleştirilmiş bir alt öğesini kaydırdığında önceki değiştiricilerin kaydırma deltalarını iç içe yerleştirilmiş kaydırma desteği olan üst öğelere yaydığı anlamına gelir.

Aşağıdaki örnekte, verticalScroll değiştiricisi uygulanmış bir kapsayıcı içinde verticalScroll değiştiricisi 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)
                    )
                }
            }
        }
    }
}

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

nestedScroll değiştiricisini kullanma

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

İç içe yerleştirilmiş kaydırma döngüsü

İç içe yerleştirilmiş kaydırma döngüsü, iç içe yerleştirilmiş kaydırma sisteminin parçası olan tüm bileşenler (veya düğümler) aracılığıyla hiyerarşi ağacında yukarı ve aşağı gönderilen kaydırma farklarının akışıdır. Örneğin, kaydırılabilir bileşenler ve değiştiriciler veya nestedScroll kullanılarak oluşturulur.

İç içe kaydırma döngüsünün aşamaları

Kaydırılabilir bir bileşen tarafından bir tetikleyici etkinlik (ör. hareket) algılandığında, gerçek kaydırma işlemi tetiklenmeden önce oluşturulan delta değerleri iç içe yerleştirilmiş kaydırma sistemine gönderilir ve üç aşamadan geçer: kaydırma öncesi, düğüm tüketimi ve kaydırma sonrası.

İç içe kaydırma döngüsünün aşamaları

Kaydırmadan önceki ilk aşamada, tetikleyici etkinlik deltalarını alan bileşen, bu etkinlikleri hiyerarşi ağacı üzerinden en üstteki üst öğeye gönderir. Deltalar daha sonra aşağıya doğru yayılır. Yani deltalar, en kökteki üst öğeden iç içe yerleştirilmiş kaydırma döngüsünü başlatan alt öğeye doğru yayılır.

Kaydırma öncesi aşama - gönderme

Bu, iç içe yerleştirilmiş kaydırma üst öğelerine (nestedScroll veya kaydırılabilir değiştiriciler kullanan composable'lar) delta ile ilgili bir işlem yapma fırsatı verir. Bu işlem, düğümün kendisi deltayı kullanmadan önce gerçekleşir.

Kaydırma öncesi aşaması - aşağıya doğru kabarcık oluşturma

Düğüm tüketimi aşamasında, düğümün kendisi, üst öğeleri tarafından kullanılmayan tüm deltaları kullanır. Bu, kaydırma hareketinin gerçekten yapıldığı ve görünür olduğu zamandır.

Düğüm tüketimi
aşaması

Bu aşamada çocuk, kalan kaydırmanın tamamını veya bir kısmını izlemeyi seçebilir. Kalan her şey, kaydırma sonrası aşamadan geçmek üzere geri gönderilir.

Son olarak, kaydırma sonrası aşamada, düğümün kendisi tarafından kullanılmayan her şey, kullanılmak üzere tekrar üst öğelerine gönderilir.

Kaydırma sonrası aşama - gönderme

Kaydırma sonrası aşama, kaydırma öncesi aşamaya benzer şekilde çalışır. Bu aşamada üst öğelerden herhangi biri içeriği kullanmayı veya kullanmamayı seçebilir.

Kaydırma sonrası aşama - ayrıntılandırma

Kaydırma işlemine benzer şekilde, sürükleme hareketi tamamlandığında kullanıcının amacı, kaydırılabilir kapsayıcıyı fırlatmak (animasyon kullanarak kaydırmak) için kullanılan bir hıza dönüştürülebilir. Fırlatma işlemi de iç içe kaydırma döngüsünün bir parçasıdır ve sürükleme etkinliği tarafından oluşturulan hızlar benzer aşamalardan geçer: fırlatma öncesi, düğüm tüketimi ve fırlatma sonrası. Fling animasyonunun yalnızca dokunma hareketiyle ilişkili olduğunu ve diğer etkinlikler (ör. erişilebilirlik veya donanım kaydırma) tarafından tetiklenmeyeceğini unutmayın.

İç içe kaydırma döngüsüne katılma

Döngüye katılım, hiyerarşi boyunca deltaların tüketimini yakalamak, tüketmek ve tüketimle ilgili rapor oluşturmak anlamına gelir. Compose, iç içe kaydırma sisteminin nasıl çalıştığını etkilemek ve doğrudan bu sistemle etkileşim kurmak için bir dizi araç sunar. Örneğin, kaydırılabilir bir bileşen kaydırmaya başlamadan önce kaydırma farklarıyla ilgili bir işlem yapmanız gerektiğinde bu araçları kullanabilirsiniz.

İç içe kaydırma döngüsü, bir düğüm zincirinde işlem yapan bir sistemse nestedScroll değiştiricisi, bu değişiklikleri yakalamanın, bunlara ekleme yapmanın ve zincirde yayılan verileri (kaydırma farkları) etkilemenin bir yoludur. Bu değiştirici, hiyerarşinin herhangi bir yerine yerleştirilebilir ve ağaçta yukarı doğru iç içe yerleştirilmiş kaydırma değiştirici örnekleriyle iletişim kurarak bu kanal üzerinden bilgi paylaşabilir. Bu değiştiricinin yapı taşları NestedScrollConnection ve NestedScrollDispatcher'dir.

NestedScrollConnection iç içe kaydırma döngüsünün aşamalarına yanıt vermenin ve iç içe kaydırma sistemini etkilemenin bir yolunu sunar. Her biri tüketim aşamalarından birini temsil eden dört geri çağırma yönteminden oluşur: kaydırma öncesi/sonrası ve hızlıca kaydırma öncesi/sonrası:

val nestedScrollConnection = object : NestedScrollConnection {
    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
        println("Received onPreScroll callback.")
        return Offset.Zero
    }

    override fun onPostScroll(
        consumed: Offset,
        available: Offset,
        source: NestedScrollSource
    ): Offset {
        println("Received onPostScroll callback.")
        return Offset.Zero
    }
}

Her geri çağırma, yaygınlaştırılan delta hakkında da bilgi verir: available söz konusu aşamaya ait delta ve consumed önceki aşamalarda kullanılan delta. Deltaların hiyerarşide yukarı doğru yayılmasını durdurmak isterseniz bunu yapmak için iç içe kaydırma bağlantısını kullanabilirsiniz:

val disabledNestedScrollConnection = remember {
    object : NestedScrollConnection {
        override fun onPostScroll(
            consumed: Offset,
            available: Offset,
            source: NestedScrollSource
        ): Offset {
            return if (source == NestedScrollSource.SideEffect) {
                available
            } else {
                Offset.Zero
            }
        }
    }
}

Tüm geri aramalar, NestedScrollSource türü hakkında bilgi sağlar.

NestedScrollDispatcher iç içe kaydırma döngüsünü başlatır. Bir dağıtıcı kullanmak ve yöntemlerini çağırmak döngüyü tetikler. Kaydırılabilir kapsayıcılarda, hareketler sırasında yakalanan deltaları sisteme gönderen yerleşik bir dağıtıcı bulunur. Bu nedenle, iç içe kaydırmayı özelleştirmenin çoğu kullanım alanında, yeni delta göndermek yerine mevcut deltalara tepki vermek için gönderici yerine NestedScrollConnection kullanılır. Diğer kullanım alanları için NestedScrollDispatcherSample sayfasına bakın.

Kaydırma sırasında resmi yeniden boyutlandırma

Kullanıcı kaydırdıkça, resmin kaydırma konumuna göre boyutunun değiştiği dinamik bir görsel efekt oluşturabilirsiniz.

Kaydırma konumuna göre resmi yeniden boyutlandırma

Bu snippet, dikey kaydırma konumuna göre bir LazyColumn içindeki bir resmi yeniden boyutlandırmayı gösterir. Kullanıcı aşağı kaydırdıkça resim küçülür, yukarı kaydırdıkça büyür ve tanımlanan minimum ve maksimum boyut sınırları içinde kalır:

@Composable
fun ImageResizeOnScrollExample(
    modifier: Modifier = Modifier,
    maxImageSize: Dp = 300.dp,
    minImageSize: Dp = 100.dp
) {
    var currentImageSize by remember { mutableStateOf(maxImageSize) }
    var imageScale by remember { mutableFloatStateOf(1f) }

    val nestedScrollConnection = remember {
        object : NestedScrollConnection {
            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
                // Calculate the change in image size based on scroll delta
                val delta = available.y
                val newImageSize = currentImageSize + delta.dp
                val previousImageSize = currentImageSize

                // Constrain the image size within the allowed bounds
                currentImageSize = newImageSize.coerceIn(minImageSize, maxImageSize)
                val consumed = currentImageSize - previousImageSize

                // Calculate the scale for the image
                imageScale = currentImageSize / maxImageSize

                // Return the consumed scroll amount
                return Offset(0f, consumed.value)
            }
        }
    }

    Box(Modifier.nestedScroll(nestedScrollConnection)) {
        LazyColumn(
            Modifier
                .fillMaxWidth()
                .padding(15.dp)
                .offset {
                    IntOffset(0, currentImageSize.roundToPx())
                }
        ) {
            // Placeholder list items
            items(100, key = { it }) {
                Text(
                    text = "Item: $it",
                    style = MaterialTheme.typography.bodyLarge
                )
            }
        }

        Image(
            painter = ColorPainter(Color.Red),
            contentDescription = "Red color image",
            Modifier
                .size(maxImageSize)
                .align(Alignment.TopCenter)
                .graphicsLayer {
                    scaleX = imageScale
                    scaleY = imageScale
                    // Center the image vertically as it scales
                    translationY = -(maxImageSize.toPx() - currentImageSize.toPx()) / 2f
                }
        )
    }
}

Kodla ilgili önemli noktalar

  • Bu kod, kaydırma etkinliklerini yakalamak için NestedScrollConnection kullanır.
  • onPreScroll, kaydırma deltasına göre görüntü boyutundaki değişikliği hesaplar.
  • currentImageSize durum değişkeni, resmin mevcut boyutunu depolar. minImageSize ile maxImageSize. imageScale arasında sınırlandırılmış olan currentImageSize değerinden türetilir.
  • LazyColumn, currentImageSize'e göre telafi edilir.
  • Image, hesaplanan ölçeği uygulamak için graphicsLayer değiştiricisini kullanır.
  • graphicsLayer içindeki translationY, resmin ölçeklenirken dikey olarak ortalanmış kalmasını sağlar.

Sonuç

Yukarıdaki snippet, kaydırma sırasında ölçeklendirme efektiyle sonuçlanır:

1. Şekil. Kaydırma sırasında ölçeklenen resim efekti.

İç içe kaydırma birlikte çalışabilirliği

Kaydırılabilir View öğelerini kaydırılabilir composable'larda veya tam tersi şekilde iç içe yerleştirmeye çalıştığınızda sorunlarla karşılaşabilirsiniz. En belirgin olanlar, alt öğeyi kaydırıp başlangıç veya bitiş sınırlarına ulaştığınızda ve kaydırma işlemini üst öğenin devralması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 beklentilerden kaynaklanır. Kaydırılabilir composable'lar "nested-scroll-by-default" (varsayılan olarak iç içe kaydırma) kuralına sahiptir. Bu kural, herhangi bir kaydırılabilir kapsayıcının hem NestedScrollConnection aracılığıyla ü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. Bu durumda, alt öğe sınıra ulaştığında üst öğe için iç içe kaydırma işlemi gerçekleştirilir. Örneğin, bu kural, Compose Pager ve Compose LazyRow'un birlikte iyi çalışmasına olanak tanır. Ancak, birlikte çalışabilirlik kaydırma işlemi ViewPager2 veya RecyclerView ile yapıldığında bunlar NestedScrollingParent3'yi uygulamadığından alt öğeden üst öğeye sürekli kaydırma mümkün değildir.

Kaydırılabilir View öğeleri ile her iki yönde de 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 bu sorunları azaltmak amacıyla iç içe kaydırma birlikte çalışabilirlik API'sini kullanabilirsiniz.

Bir alt öğe ComposeView içeren bir üst öğe View

İşbirlikçi bir üst öğe View, NestedScrollingParent3'ı zaten uygulayan ve bu nedenle işbirlikçi bir iç içe yerleştirilmiş alt composable'dan kaydırma deltaları alabilen bir öğedir. ComposeView bu durumda çocuk gibi davranır ve NestedScrollingChild3'ı (dolaylı olarak) uygulaması gerekir. İşbirliği yapan bir ebeveyn örneği: androidx.coordinatorlayout.widget.CoordinatorLayout.

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

rememberNestedScrollInteropConnection() View üst öğesi ile Compose alt öğesi arasında iç içe kaydırma birlikte çalışabilirliğini sağlayan NestedScrollConnection öğesini etkinleştirir ve hatırlar.View üst öğesi NestedScrollingParent3 öğesini uygular. Bu, nestedScroll değiştiricisiyle birlikte kullanılmalıdır. İç içe kaydırma, Compose tarafında varsayılan olarak etkinleştirildiğinden bu bağlantıyı kullanarak hem View tarafında iç içe kaydırmayı etkinleştirebilir hem de Views ile composable'lar arasında gerekli yapıştırıcı mantığını ekleyebilirsiniz.

Sık karşılaşılan bir kullanım alanı, bu örnekte gösterildiği gibi CoordinatorLayout, CollapsingToolbarLayout ve bir alt öğe composable'ını kullanmaktı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 parçanızda, alt composable'ınızı ve gerekli NestedScrollConnection'ı ayarlamanız gerekir:

open class MainActivity : ComponentActivity() {
    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())
                        }
                    }
                }
            }
        }
    }
}

Bir alt öğe AndroidView içeren üst composable

Bu senaryo, Compose tarafında iç içe kaydırma birlikte çalışabilirlik API'sinin uygulanmasını kapsar. Bu, bir alt öğe içeren bir üst composable'ınız olduğunda geçerlidir.AndroidView. AndroidView, Compose'da kaydırma özelliği olan bir üst öğenin alt öğesi olarak davrandığı için NestedScrollDispatcher, kaydırma özelliği olan bir alt öğenin üst öğesi olarak davrandığı için de NestedScrollingParent3 arayüzünü uygular.View Böylece üst öğe, iç içe yerleştirilmiş kaydırılabilir bir alt öğeden iç içe yerleştirilmiş kaydırma farklarını alabilir View.

Aşağıdaki örnekte, bu senaryoda iç içe kaydırma birlikte çalışabilirliğinin nasıl sağlanabileceği ve Compose'da daraltılabilir bir araç çubuğunun nasıl kullanılacağı 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, API'yi scrollable değiştiricisiyle 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, iç içe kaydırma birlikte çalışabilirlik API'sinin BottomSheetDialogFragment ile nasıl kullanıldığı gösterilmektedir. Bu sayede, başarılı bir sürükleme ve kapatma davranışı elde edilir:

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() öğesinin, eklendiği öğeye NestedScrollConnection yükleyeceğini unutmayın. NestedScrollConnection, Compose düzeyindeki değişiklikleri View düzeyine iletmekten sorumludur. Bu, öğenin iç içe kaydırmaya katılmasına olanak tanır ancak öğelerin otomatik olarak kaydırılmasını sağlamaz. Box veya Column gibi otomatik olarak kaydırılamayan composable'larda, bu tür bileşenlerdeki kaydırma farkları iç içe yerleştirilmiş kaydırma sisteminde yayılmaz ve farklar rememberNestedScrollInteropConnection() tarafından sağlanan NestedScrollConnection'a ulaşmaz. Bu nedenle, bu farklar üst View bileşenine ulaşmaz. Bu sorunu çözmek için kaydırılabilir değiştiricileri bu tür iç içe yerleştirilmiş composable'lara da ayarladığınızdan emin olun. Daha ayrıntılı bilgi için iç içe yerleştirilmiş kaydırma ile ilgili önceki bölüme bakabilirsiniz.

Çocuk ComposeView içeren işbirliği yapmayan bir ebeveyn View

İşbirliği yapmayan bir görünüm, NestedScrolling tarafında gerekli View arayüzlerini uygulamayan görünümdür. Bu nedenle, bu Views ile iç içe kaydırma birlikte çalışabilirliğinin kullanıma hazır olarak çalışmadığını unutmayın. İş birliği yapmayan Views, RecyclerView ve ViewPager2'dir.

Ek kaynaklar