Skip to content

Commit 37f86e3

Browse files
ShikaSDGerrit Code Review
authored and
Gerrit Code Review
committed
Merge "Extract AwaitFirstLayoutModifier and apply it to LazyStaggeredGrid" into androidx-main
2 parents 5b88804 + 87f4c64 commit 37f86e3

File tree

7 files changed

+118
-29
lines changed

7 files changed

+118
-29
lines changed

compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridTest.kt

+58
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ import androidx.compose.foundation.border
2222
import androidx.compose.foundation.gestures.Orientation
2323
import androidx.compose.foundation.gestures.scrollBy
2424
import androidx.compose.foundation.layout.Box
25+
import androidx.compose.foundation.layout.BoxWithConstraints
2526
import androidx.compose.foundation.layout.Spacer
2627
import androidx.compose.foundation.layout.size
2728
import androidx.compose.foundation.lazy.list.setContentWithTestViewConfiguration
2829
import androidx.compose.foundation.text.BasicText
30+
import androidx.compose.runtime.LaunchedEffect
2931
import androidx.compose.runtime.getValue
3032
import androidx.compose.runtime.mutableStateListOf
3133
import androidx.compose.runtime.mutableStateOf
@@ -1934,4 +1936,60 @@ class LazyStaggeredGridTest(
19341936
rule.onNodeWithTag("1")
19351937
.assertAxisBounds(DpOffset(itemSizeDp, itemSizeDp * 2), DpSize(itemSizeDp, itemSizeDp))
19361938
}
1939+
1940+
@Test
1941+
fun scrollDuringMeasure() {
1942+
rule.setContent {
1943+
BoxWithConstraints {
1944+
val state = rememberLazyStaggeredGridState()
1945+
LazyStaggeredGrid(
1946+
lanes = 1,
1947+
state = state,
1948+
modifier = Modifier.axisSize(
1949+
crossAxis = itemSizeDp * 2,
1950+
mainAxis = itemSizeDp * 5
1951+
),
1952+
) {
1953+
items(20) {
1954+
Spacer(
1955+
modifier = Modifier.mainAxisSize(itemSizeDp).testTag(it.toString())
1956+
)
1957+
}
1958+
}
1959+
LaunchedEffect(state) {
1960+
state.scrollToItem(10)
1961+
}
1962+
}
1963+
}
1964+
1965+
rule.onNodeWithTag("10")
1966+
.assertStartPositionInRootIsEqualTo(0.dp)
1967+
}
1968+
1969+
@Test
1970+
fun scrollInLaunchedEffect() {
1971+
rule.setContent {
1972+
val state = rememberLazyStaggeredGridState()
1973+
LazyStaggeredGrid(
1974+
lanes = 1,
1975+
state = state,
1976+
modifier = Modifier.axisSize(
1977+
crossAxis = itemSizeDp * 2,
1978+
mainAxis = itemSizeDp * 5
1979+
),
1980+
) {
1981+
items(20) {
1982+
Spacer(
1983+
modifier = Modifier.mainAxisSize(itemSizeDp).testTag(it.toString())
1984+
)
1985+
}
1986+
}
1987+
LaunchedEffect(state) {
1988+
state.scrollToItem(10)
1989+
}
1990+
}
1991+
1992+
rule.onNodeWithTag("10")
1993+
.assertStartPositionInRootIsEqualTo(0.dp)
1994+
}
19371995
}

compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt

+2-27
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ import androidx.compose.foundation.gestures.ScrollScope
2323
import androidx.compose.foundation.gestures.ScrollableState
2424
import androidx.compose.foundation.interaction.InteractionSource
2525
import androidx.compose.foundation.interaction.MutableInteractionSource
26+
import androidx.compose.foundation.lazy.layout.AwaitFirstLayoutModifier
2627
import androidx.compose.foundation.lazy.layout.LazyLayoutBeyondBoundsInfo
27-
import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState
2828
import androidx.compose.foundation.lazy.layout.LazyLayoutPinnedItemList
29+
import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState
2930
import androidx.compose.foundation.lazy.layout.animateScrollToItem
3031
import androidx.compose.runtime.Composable
3132
import androidx.compose.runtime.Stable
@@ -35,16 +36,11 @@ import androidx.compose.runtime.saveable.Saver
3536
import androidx.compose.runtime.saveable.listSaver
3637
import androidx.compose.runtime.saveable.rememberSaveable
3738
import androidx.compose.runtime.setValue
38-
import androidx.compose.ui.layout.LayoutCoordinates
39-
import androidx.compose.ui.layout.OnGloballyPositionedModifier
4039
import androidx.compose.ui.layout.Remeasurement
4140
import androidx.compose.ui.layout.RemeasurementModifier
4241
import androidx.compose.ui.unit.Constraints
4342
import androidx.compose.ui.unit.Density
4443
import androidx.compose.ui.unit.IntSize
45-
import kotlin.coroutines.Continuation
46-
import kotlin.coroutines.resume
47-
import kotlin.coroutines.suspendCoroutine
4844
import kotlin.math.abs
4945

5046
/**
@@ -436,25 +432,4 @@ private object EmptyLazyListLayoutInfo : LazyListLayoutInfo {
436432
override val beforeContentPadding = 0
437433
override val afterContentPadding = 0
438434
override val mainAxisItemSpacing = 0
439-
}
440-
441-
internal class AwaitFirstLayoutModifier : OnGloballyPositionedModifier {
442-
private var wasPositioned = false
443-
private var continuation: Continuation<Unit>? = null
444-
445-
suspend fun waitForFirstLayout() {
446-
if (!wasPositioned) {
447-
val oldContinuation = continuation
448-
suspendCoroutine<Unit> { continuation = it }
449-
oldContinuation?.resume(Unit)
450-
}
451-
}
452-
453-
override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
454-
if (!wasPositioned) {
455-
wasPositioned = true
456-
continuation?.resume(Unit)
457-
continuation = null
458-
}
459-
}
460435
}

compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import androidx.compose.foundation.gestures.ScrollScope
2323
import androidx.compose.foundation.gestures.ScrollableState
2424
import androidx.compose.foundation.interaction.InteractionSource
2525
import androidx.compose.foundation.interaction.MutableInteractionSource
26-
import androidx.compose.foundation.lazy.AwaitFirstLayoutModifier
26+
import androidx.compose.foundation.lazy.layout.AwaitFirstLayoutModifier
2727
import androidx.compose.foundation.lazy.layout.LazyLayoutBeyondBoundsInfo
2828
import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState
2929
import androidx.compose.foundation.lazy.layout.LazyLayoutPinnedItemList
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2023 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package androidx.compose.foundation.lazy.layout
18+
19+
import androidx.compose.ui.layout.LayoutCoordinates
20+
import androidx.compose.ui.layout.OnGloballyPositionedModifier
21+
import kotlin.coroutines.Continuation
22+
import kotlin.coroutines.resume
23+
import kotlin.coroutines.suspendCoroutine
24+
25+
/**
26+
* Internal modifier which allows to delay some interactions (e.g. scroll) until layout is ready.
27+
*/
28+
internal class AwaitFirstLayoutModifier : OnGloballyPositionedModifier {
29+
private var wasPositioned = false
30+
private var continuation: Continuation<Unit>? = null
31+
32+
suspend fun waitForFirstLayout() {
33+
if (!wasPositioned) {
34+
val oldContinuation = continuation
35+
suspendCoroutine { continuation = it }
36+
oldContinuation?.resume(Unit)
37+
}
38+
}
39+
40+
override fun onGloballyPositioned(coordinates: LayoutCoordinates) {
41+
if (!wasPositioned) {
42+
wasPositioned = true
43+
continuation?.resume(Unit)
44+
continuation = null
45+
}
46+
}
47+
}

compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGrid.kt

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ internal fun LazyStaggeredGrid(
8181
LazyLayout(
8282
modifier = modifier
8383
.then(state.remeasurementModifier)
84+
.then(state.awaitLayoutModifier)
8485
.lazyLayoutSemantics(
8586
itemProvider = itemProvider,
8687
state = semanticState,

compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt

+8
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.compose.foundation.gestures.ScrollScope
2222
import androidx.compose.foundation.gestures.ScrollableState
2323
import androidx.compose.foundation.interaction.InteractionSource
2424
import androidx.compose.foundation.interaction.MutableInteractionSource
25+
import androidx.compose.foundation.lazy.layout.AwaitFirstLayoutModifier
2526
import androidx.compose.foundation.lazy.layout.LazyAnimateScrollScope
2627
import androidx.compose.foundation.lazy.layout.LazyLayoutBeyondBoundsInfo
2728
import androidx.compose.foundation.lazy.layout.LazyLayoutItemProvider
@@ -166,6 +167,12 @@ class LazyStaggeredGridState private constructor(
166167
}
167168
}
168169

170+
/**
171+
* Provides a modifier which allows to delay some interactions (e.g. scroll)
172+
* until layout is ready.
173+
*/
174+
internal val awaitLayoutModifier = AwaitFirstLayoutModifier()
175+
169176
internal val beyondBoundsInfo = LazyLayoutBeyondBoundsInfo()
170177

171178
/**
@@ -228,6 +235,7 @@ class LazyStaggeredGridState private constructor(
228235
scrollPriority: MutatePriority,
229236
block: suspend ScrollScope.() -> Unit
230237
) {
238+
awaitLayoutModifier.waitForFirstLayout()
231239
scrollableState.scroll(scrollPriority, block)
232240
}
233241

compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import androidx.compose.foundation.gestures.ScrollableState
2727
import androidx.compose.foundation.gestures.animateScrollBy
2828
import androidx.compose.foundation.interaction.InteractionSource
2929
import androidx.compose.foundation.interaction.MutableInteractionSource
30-
import androidx.compose.foundation.lazy.AwaitFirstLayoutModifier
30+
import androidx.compose.foundation.lazy.layout.AwaitFirstLayoutModifier
3131
import androidx.compose.foundation.lazy.layout.LazyLayoutBeyondBoundsInfo
3232
import androidx.compose.foundation.lazy.layout.LazyLayoutPinnedItemList
3333
import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState

0 commit comments

Comments
 (0)