Deprecate LazyColumnFor, LazyRowFor, LazyColumnForIndexed and LazyRowForIndexed
Test: existing tests
Relnote: Deprecate LazyColumnFor, LazyRowFor, LazyColumnForIndexed and LazyRowForIndexed. Use LazyColumn and LazyRow instead
Change-Id: I5b48c8a3b1fef2f603ab69ded1d19709aa9f87fb
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisiblilityLazyColumnDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisiblilityLazyColumnDemo.kt
index d28cb90..2d1b4b4 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisiblilityLazyColumnDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/AnimatedVisiblilityLazyColumnDemo.kt
@@ -27,7 +27,7 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyColumnForIndexed
+import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@@ -62,13 +62,15 @@
Text("Remove")
}
}
- LazyColumnForIndexed(turquoiseColors) { i, color ->
- AnimatedVisibility(
- (turquoiseColors.size - itemNum) <= i,
- enter = expandVertically(),
- exit = shrinkVertically()
- ) {
- Spacer(Modifier.fillMaxWidth().height(90.dp).background(color))
+ LazyColumn {
+ itemsIndexed(turquoiseColors) { i, color ->
+ AnimatedVisibility(
+ (turquoiseColors.size - itemNum) <= i,
+ enter = expandVertically(),
+ exit = shrinkVertically()
+ ) {
+ Spacer(Modifier.fillMaxWidth().height(90.dp).background(color))
+ }
}
}
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt
index d5b30d9..4e85b17 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/EnterExitTransitionDemo.kt
@@ -37,7 +37,7 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.preferredHeight
import androidx.compose.foundation.layout.wrapContentWidth
-import androidx.compose.foundation.lazy.LazyColumnFor
+import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.selection.selectable
import androidx.compose.material.Button
import androidx.compose.material.Checkbox
@@ -224,11 +224,10 @@
}
}
}
- LazyColumnFor(
- menuText,
- modifier = Modifier.fillMaxSize().background(Color(0xFFd8c7ff))
- ) {
- Text(it, Modifier.padding(5.dp))
+ LazyColumn(Modifier.fillMaxSize().background(Color(0xFFd8c7ff))) {
+ items(menuText) {
+ Text(it, Modifier.padding(5.dp))
+ }
}
}
}
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index 542f765..833d3c5 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -325,10 +325,10 @@
}
public final class LazyForKt {
- method @androidx.compose.runtime.Composable public static <T> void LazyColumnFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
- method @androidx.compose.runtime.Composable public static <T> void LazyColumnForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
- method @androidx.compose.runtime.Composable public static <T> void LazyRowFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
- method @androidx.compose.runtime.Composable public static <T> void LazyRowForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T> void LazyColumnFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T> void LazyColumnForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T> void LazyRowFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T> void LazyRowForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
}
public final class LazyGridKt {
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index 542f765..833d3c5 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -325,10 +325,10 @@
}
public final class LazyForKt {
- method @androidx.compose.runtime.Composable public static <T> void LazyColumnFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
- method @androidx.compose.runtime.Composable public static <T> void LazyColumnForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
- method @androidx.compose.runtime.Composable public static <T> void LazyRowFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
- method @androidx.compose.runtime.Composable public static <T> void LazyRowForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T> void LazyColumnFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T> void LazyColumnForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T> void LazyRowFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T> void LazyRowForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
}
public final class LazyGridKt {
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 542f765..833d3c5 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -325,10 +325,10 @@
}
public final class LazyForKt {
- method @androidx.compose.runtime.Composable public static <T> void LazyColumnFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
- method @androidx.compose.runtime.Composable public static <T> void LazyColumnForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
- method @androidx.compose.runtime.Composable public static <T> void LazyRowFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
- method @androidx.compose.runtime.Composable public static <T> void LazyRowForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T> void LazyColumnFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T> void LazyColumnForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T> void LazyRowFor(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+ method @Deprecated @androidx.compose.runtime.Composable public static <T> void LazyRowForIndexed(java.util.List<? extends T> items, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.LazyListState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional boolean reverseLayout, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
}
public final class LazyGridKt {
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
index ec4fd27..d3d049e 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
@@ -36,11 +36,7 @@
import androidx.compose.foundation.layout.preferredWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.LazyColumnFor
-import androidx.compose.foundation.lazy.LazyColumnForIndexed
import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.foundation.lazy.LazyRowFor
-import androidx.compose.foundation.lazy.LazyRowForIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -87,17 +83,19 @@
@Composable
private fun LazyColumnDemo() {
- LazyColumnFor(
- items = listOf(
- "Hello,", "World:", "It works!", "",
- "this one is really long and spans a few lines for scrolling purposes",
- "these", "are", "offscreen"
- ) + (1..100).map { "$it" }
- ) {
- Text(text = it, fontSize = 80.sp)
+ LazyColumn {
+ items(
+ items = listOf(
+ "Hello,", "World:", "It works!", "",
+ "this one is really long and spans a few lines for scrolling purposes",
+ "these", "are", "offscreen"
+ ) + (1..100).map { "$it" }
+ ) {
+ Text(text = it, fontSize = 80.sp)
- if (it.contains("works")) {
- Text("You can even emit multiple components per item.")
+ if (it.contains("works")) {
+ Text("You can even emit multiple components per item.")
+ }
}
}
}
@@ -113,11 +111,10 @@
Button(modifier = buttonModifier, onClick = { numItems-- }) { Text("Remove") }
Button(modifier = buttonModifier, onClick = { offset++ }) { Text("Offset") }
}
- LazyColumnFor(
- (1..numItems).map { it + offset }.toList(),
- Modifier.fillMaxWidth()
- ) {
- Text("$it", style = AmbientTextStyle.current.copy(fontSize = 40.sp))
+ LazyColumn(Modifier.fillMaxWidth()) {
+ items((1..numItems).map { it + offset }.toList()) {
+ Text("$it", style = AmbientTextStyle.current.copy(fontSize = 40.sp))
+ }
}
}
}
@@ -183,12 +180,13 @@
fontSize = 20.sp
)
}
- LazyColumnFor(
- (0..1000).toList(),
+ LazyColumn(
Modifier.fillMaxWidth(),
state = state
) {
- Text("$it", style = AmbientTextStyle.current.copy(fontSize = 40.sp))
+ items((0..1000).toList()) {
+ Text("$it", style = AmbientTextStyle.current.copy(fontSize = 40.sp))
+ }
}
}
}
@@ -207,8 +205,10 @@
@Composable
private fun LazyRowItemsDemo() {
- LazyRowFor(items = (1..1000).toList()) {
- Square(it)
+ LazyRow {
+ items((1..1000).toList()) {
+ Square(it)
+ }
}
}
@@ -227,11 +227,15 @@
private fun ListWithIndexSample() {
val friends = listOf("Alex", "John", "Danny", "Sam")
Column {
- LazyRowForIndexed(friends, Modifier.fillMaxWidth()) { index, friend ->
- Text("$friend at index $index", Modifier.padding(16.dp))
+ LazyRow(Modifier.fillMaxWidth()) {
+ itemsIndexed(friends) { index, friend ->
+ Text("$friend at index $index", Modifier.padding(16.dp))
+ }
}
- LazyColumnForIndexed(friends, Modifier.fillMaxWidth()) { index, friend ->
- Text("$friend at index $index", Modifier.padding(16.dp))
+ LazyColumn(Modifier.fillMaxWidth()) {
+ itemsIndexed(friends) { index, friend ->
+ Text("$friend at index $index", Modifier.padding(16.dp))
+ }
}
}
}
@@ -239,14 +243,16 @@
@Composable
private fun RtlListDemo() {
Providers(AmbientLayoutDirection provides LayoutDirection.Rtl) {
- LazyRowForIndexed((0..100).toList(), Modifier.fillMaxWidth()) { index, item ->
- Text(
- "$item",
- Modifier
- .size(100.dp)
- .background(if (index % 2 == 0) Color.LightGray else Color.Transparent)
- .padding(16.dp)
- )
+ LazyRow(Modifier.fillMaxWidth()) {
+ itemsIndexed((0..100).toList()) { index, item ->
+ Text(
+ "$item",
+ Modifier
+ .size(100.dp)
+ .background(if (index % 2 == 0) Color.LightGray else Color.Transparent)
+ .padding(16.dp)
+ )
+ }
}
}
}
@@ -254,8 +260,10 @@
@Composable
private fun PagerLikeDemo() {
val pages = listOf(Color.LightGray, Color.White, Color.DarkGray)
- LazyRowFor(pages) {
- Spacer(Modifier.fillParentMaxSize().background(it))
+ LazyRow {
+ items(pages) {
+ Spacer(Modifier.fillParentMaxSize().background(it))
+ }
}
}
@@ -457,8 +465,10 @@
}
LazyColumn {
item {
- LazyRowFor(List(100) { it }) {
- item(it)
+ LazyRow {
+ items(List(100) { it }) {
+ item(it)
+ }
}
}
items(List(100) { it }) {
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyForSamples.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyForSamples.kt
deleted file mode 100644
index d1b87dd..0000000
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/LazyForSamples.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.foundation.samples
-
-import androidx.annotation.Sampled
-import androidx.compose.foundation.lazy.LazyColumnFor
-import androidx.compose.foundation.lazy.LazyColumnForIndexed
-import androidx.compose.foundation.lazy.LazyRowFor
-import androidx.compose.foundation.lazy.LazyRowForIndexed
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-
-@Sampled
-@Composable
-fun LazyColumnForSample() {
- val items = listOf("A", "B", "C")
- LazyColumnFor(items) {
- Text("Item is $it")
- }
-}
-
-@Sampled
-@Composable
-fun LazyRowForSample() {
- val items = listOf("A", "B", "C")
- LazyRowFor(items) {
- Text("Item is $it")
- }
-}
-
-@Sampled
-@Composable
-fun LazyColumnForIndexedSample() {
- val items = listOf("A", "B", "C")
- LazyColumnForIndexed(items) { index, item ->
- Text("Item at index $index is $item")
- }
-}
-
-@Sampled
-@Composable
-fun LazyRowForIndexedSample() {
- val items = listOf("A", "B", "C")
- LazyRowForIndexed(items) { index, item ->
- Text("Item at index $index is $item")
- }
-}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnForTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnForTest.kt
deleted file mode 100644
index bbd257b..0000000
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnForTest.kt
+++ /dev/null
@@ -1,1136 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.foundation.lazy
-
-import androidx.compose.animation.core.ExponentialDecay
-import androidx.compose.animation.core.ManualAnimationClock
-import androidx.compose.animation.core.snap
-import androidx.compose.foundation.animation.FlingConfig
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.preferredHeight
-import androidx.compose.foundation.layout.preferredSize
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.sizeIn
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.text.BasicText
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.onCommit
-import androidx.compose.runtime.onDispose
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawBehind
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.gesture.TouchSlop
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.SemanticsNodeInteraction
-import androidx.compose.ui.test.assertCountEquals
-import androidx.compose.ui.test.assertHeightIsEqualTo
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.assertIsEqualTo
-import androidx.compose.ui.test.assertIsNotDisplayed
-import androidx.compose.ui.test.assertPositionInRootIsEqualTo
-import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
-import androidx.compose.ui.test.assertWidthIsEqualTo
-import androidx.compose.ui.test.center
-import androidx.compose.ui.test.click
-import androidx.compose.ui.test.getUnclippedBoundsInRoot
-import androidx.compose.ui.test.junit4.StateRestorationTester
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onChildren
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.onNodeWithText
-import androidx.compose.ui.test.performGesture
-import androidx.compose.ui.test.swipeUp
-import androidx.compose.ui.test.swipeWithVelocity
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.LargeTest
-import com.google.common.collect.Range
-import com.google.common.truth.IntegerSubject
-import com.google.common.truth.Truth.assertThat
-import com.google.common.truth.Truth.assertWithMessage
-import kotlinx.coroutines.runBlocking
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import java.util.concurrent.CountDownLatch
-
-@LargeTest
-@RunWith(AndroidJUnit4::class)
-class LazyColumnForTest {
- private val LazyColumnForTag = "TestLazyColumnFor"
-
- @get:Rule
- val rule = createComposeRule()
-
- @Test
- fun compositionsAreDisposed_whenNodesAreScrolledOff() {
- var composed: Boolean
- var disposed = false
- // Ten 31dp spacers in a 300dp list
- val latch = CountDownLatch(10)
- // Make it long enough that it's _definitely_ taller than the screen
- val data = (1..50).toList()
-
- rule.setContent {
- // Fixed height to eliminate device size as a factor
- Box(Modifier.testTag(LazyColumnForTag).preferredHeight(300.dp)) {
- LazyColumnFor(items = data, modifier = Modifier.fillMaxSize()) {
- onCommit {
- composed = true
- // Signal when everything is done composing
- latch.countDown()
- onDispose {
- disposed = true
- }
- }
-
- // There will be 10 of these in the 300dp box
- Spacer(Modifier.preferredHeight(31.dp))
- }
- }
- }
-
- latch.await()
- composed = false
-
- assertWithMessage("Compositions were disposed before we did any scrolling")
- .that(disposed).isFalse()
-
- // Mostly a validity check, this is not part of the behavior under test
- assertWithMessage("Additional composition occurred for no apparent reason")
- .that(composed).isFalse()
-
- rule.onNodeWithTag(LazyColumnForTag)
- .performGesture { swipeUp() }
-
- rule.waitForIdle()
-
- assertWithMessage("No additional items were composed after scroll, scroll didn't work")
- .that(composed).isTrue()
-
- // We may need to modify this test once we prefetch/cache items outside the viewport
- assertWithMessage(
- "No compositions were disposed after scrolling, compositions were leaked"
- ).that(disposed).isTrue()
- }
-
- @Test
- fun compositionsAreDisposed_whenDataIsChanged() {
- var composed = 0
- var disposals = 0
- val data1 = (1..3).toList()
- val data2 = (4..5).toList() // smaller, to ensure removal is handled properly
-
- var part2 by mutableStateOf(false)
-
- rule.setContent {
- LazyColumnFor(
- items = if (!part2) data1 else data2,
- modifier = Modifier.testTag(LazyColumnForTag).fillMaxSize()
- ) {
- onCommit {
- composed++
- onDispose {
- disposals++
- }
- }
-
- Spacer(Modifier.height(50.dp))
- }
- }
-
- rule.runOnIdle {
- assertWithMessage("Not all items were composed")
- .that(composed).isEqualTo(data1.size)
- composed = 0
-
- part2 = true
- }
-
- rule.runOnIdle {
- assertWithMessage(
- "No additional items were composed after data change, something didn't work"
- ).that(composed).isEqualTo(data2.size)
-
- // We may need to modify this test once we prefetch/cache items outside the viewport
- assertWithMessage(
- "Not enough compositions were disposed after scrolling, compositions were leaked"
- ).that(disposals).isEqualTo(data1.size)
- }
- }
-
- @Test
- fun compositionsAreDisposed_whenAdapterListIsDisposed() {
- var emitAdapterList by mutableStateOf(true)
- var disposeCalledOnFirstItem = false
- var disposeCalledOnSecondItem = false
-
- rule.setContent {
- if (emitAdapterList) {
- LazyColumnFor(
- items = listOf(0, 1),
- modifier = Modifier.fillMaxSize()
- ) {
- Box(Modifier.size(100.dp))
- onDispose {
- if (it == 1) {
- disposeCalledOnFirstItem = true
- } else {
- disposeCalledOnSecondItem = true
- }
- }
- }
- }
- }
-
- rule.runOnIdle {
- assertWithMessage("First item is not immediately disposed")
- .that(disposeCalledOnFirstItem).isFalse()
- assertWithMessage("Second item is not immediately disposed")
- .that(disposeCalledOnFirstItem).isFalse()
- emitAdapterList = false
- }
-
- rule.runOnIdle {
- assertWithMessage("First item is correctly disposed")
- .that(disposeCalledOnFirstItem).isTrue()
- assertWithMessage("Second item is correctly disposed")
- .that(disposeCalledOnSecondItem).isTrue()
- }
- }
-
- @Test
- fun removeItemsTest() {
- val startingNumItems = 3
- var numItems = startingNumItems
- var numItemsModel by mutableStateOf(numItems)
- val tag = "List"
- rule.setContent {
- LazyColumnFor((1..numItemsModel).toList(), modifier = Modifier.testTag(tag)) {
- BasicText("$it")
- }
- }
-
- while (numItems >= 0) {
- // Confirm the number of children to ensure there are no extra items
- rule.onNodeWithTag(tag)
- .onChildren()
- .assertCountEquals(numItems)
-
- // Confirm the children's content
- for (i in 1..3) {
- rule.onNodeWithText("$i").apply {
- if (i <= numItems) {
- assertExists()
- } else {
- assertDoesNotExist()
- }
- }
- }
- numItems--
- if (numItems >= 0) {
- // Don't set the model to -1
- rule.runOnIdle { numItemsModel = numItems }
- }
- }
- }
-
- @Test
- fun changingDataTest() {
- val dataLists = listOf(
- (1..3).toList(),
- (4..8).toList(),
- (3..4).toList()
- )
- var dataModel by mutableStateOf(dataLists[0])
- val tag = "List"
- rule.setContent {
- LazyColumnFor(dataModel, modifier = Modifier.testTag(tag)) {
- BasicText("$it")
- }
- }
-
- for (data in dataLists) {
- rule.runOnIdle { dataModel = data }
-
- // Confirm the number of children to ensure there are no extra items
- val numItems = data.size
- rule.onNodeWithTag(tag)
- .onChildren()
- .assertCountEquals(numItems)
-
- // Confirm the children's content
- for (item in data) {
- rule.onNodeWithText("$item").assertExists()
- }
- }
- }
-
- @Test
- fun whenItemsAreInitiallyCreatedWith0SizeWeCanScrollWhenTheyExpanded() {
- val thirdTag = "third"
- val items = (1..3).toList()
- var thirdHasSize by mutableStateOf(false)
-
- rule.setContent {
- LazyColumnFor(
- items = items,
- modifier = Modifier.fillMaxWidth()
- .preferredHeight(100.dp)
- .testTag(LazyColumnForTag)
- ) {
- if (it == 3) {
- Spacer(
- Modifier.testTag(thirdTag)
- .fillParentMaxWidth()
- .preferredHeight(if (thirdHasSize) 60.dp else 0.dp)
- )
- } else {
- Spacer(Modifier.fillParentMaxWidth().preferredHeight(60.dp))
- }
- }
- }
-
- rule.onNodeWithTag(LazyColumnForTag)
- .scrollBy(y = 21.dp, density = rule.density)
-
- rule.onNodeWithTag(thirdTag)
- .assertExists()
- .assertIsNotDisplayed()
-
- rule.runOnIdle {
- thirdHasSize = true
- }
-
- rule.waitForIdle()
-
- rule.onNodeWithTag(LazyColumnForTag)
- .scrollBy(y = 10.dp, density = rule.density)
-
- rule.onNodeWithTag(thirdTag)
- .assertIsDisplayed()
- }
-
- @Test
- fun lazyColumnWrapsContent() = with(rule.density) {
- val itemInsideLazyColumn = "itemInsideLazyColumn"
- val itemOutsideLazyColumn = "itemOutsideLazyColumn"
- var sameSizeItems by mutableStateOf(true)
-
- rule.setContent {
- Row {
- LazyColumnFor(
- items = listOf(1, 2),
- modifier = Modifier.testTag(LazyColumnForTag)
- ) {
- if (it == 1) {
- Spacer(Modifier.preferredSize(50.dp).testTag(itemInsideLazyColumn))
- } else {
- Spacer(Modifier.preferredSize(if (sameSizeItems) 50.dp else 70.dp))
- }
- }
- Spacer(Modifier.preferredSize(50.dp).testTag(itemOutsideLazyColumn))
- }
- }
-
- rule.onNodeWithTag(itemInsideLazyColumn)
- .assertIsDisplayed()
-
- rule.onNodeWithTag(itemOutsideLazyColumn)
- .assertIsDisplayed()
-
- var lazyColumnBounds = rule.onNodeWithTag(LazyColumnForTag)
- .getUnclippedBoundsInRoot()
-
- assertThat(lazyColumnBounds.left.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
- assertThat(lazyColumnBounds.right.toIntPx()).isWithin1PixelFrom(50.dp.toIntPx())
- assertThat(lazyColumnBounds.top.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
- assertThat(lazyColumnBounds.bottom.toIntPx()).isWithin1PixelFrom(100.dp.toIntPx())
-
- rule.runOnIdle {
- sameSizeItems = false
- }
-
- rule.waitForIdle()
-
- rule.onNodeWithTag(itemInsideLazyColumn)
- .assertIsDisplayed()
-
- rule.onNodeWithTag(itemOutsideLazyColumn)
- .assertIsDisplayed()
-
- lazyColumnBounds = rule.onNodeWithTag(LazyColumnForTag)
- .getUnclippedBoundsInRoot()
-
- assertThat(lazyColumnBounds.left.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
- assertThat(lazyColumnBounds.right.toIntPx()).isWithin1PixelFrom(70.dp.toIntPx())
- assertThat(lazyColumnBounds.top.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
- assertThat(lazyColumnBounds.bottom.toIntPx()).isWithin1PixelFrom(120.dp.toIntPx())
- }
-
- private val firstItemTag = "firstItemTag"
- private val secondItemTag = "secondItemTag"
-
- private fun prepareLazyColumnsItemsAlignment(horizontalGravity: Alignment.Horizontal) {
- rule.setContent {
- LazyColumnFor(
- items = listOf(1, 2),
- modifier = Modifier.testTag(LazyColumnForTag).width(100.dp),
- horizontalAlignment = horizontalGravity
- ) {
- if (it == 1) {
- Spacer(Modifier.preferredSize(50.dp).testTag(firstItemTag))
- } else {
- Spacer(Modifier.preferredSize(70.dp).testTag(secondItemTag))
- }
- }
- }
-
- rule.onNodeWithTag(firstItemTag)
- .assertIsDisplayed()
-
- rule.onNodeWithTag(secondItemTag)
- .assertIsDisplayed()
-
- val lazyColumnBounds = rule.onNodeWithTag(LazyColumnForTag)
- .getUnclippedBoundsInRoot()
-
- with(rule.density) {
- // Verify the width of the column
- assertThat(lazyColumnBounds.left.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
- assertThat(lazyColumnBounds.right.toIntPx()).isWithin1PixelFrom(100.dp.toIntPx())
- }
- }
-
- @Test
- fun lazyColumnAlignmentCenterHorizontally() {
- prepareLazyColumnsItemsAlignment(Alignment.CenterHorizontally)
-
- rule.onNodeWithTag(firstItemTag)
- .assertPositionInRootIsEqualTo(25.dp, 0.dp)
-
- rule.onNodeWithTag(secondItemTag)
- .assertPositionInRootIsEqualTo(15.dp, 50.dp)
- }
-
- @Test
- fun lazyColumnAlignmentStart() {
- prepareLazyColumnsItemsAlignment(Alignment.Start)
-
- rule.onNodeWithTag(firstItemTag)
- .assertPositionInRootIsEqualTo(0.dp, 0.dp)
-
- rule.onNodeWithTag(secondItemTag)
- .assertPositionInRootIsEqualTo(0.dp, 50.dp)
- }
-
- @Test
- fun lazyColumnAlignmentEnd() {
- prepareLazyColumnsItemsAlignment(Alignment.End)
-
- rule.onNodeWithTag(firstItemTag)
- .assertPositionInRootIsEqualTo(50.dp, 0.dp)
-
- rule.onNodeWithTag(secondItemTag)
- .assertPositionInRootIsEqualTo(30.dp, 50.dp)
- }
-
- @Test
- fun itemFillingParentWidth() {
- rule.setContent {
- LazyColumnFor(
- items = listOf(0),
- modifier = Modifier.size(width = 100.dp, height = 150.dp)
- ) {
- Spacer(Modifier.fillParentMaxWidth().height(50.dp).testTag(firstItemTag))
- }
- }
-
- rule.onNodeWithTag(firstItemTag)
- .assertWidthIsEqualTo(100.dp)
- .assertHeightIsEqualTo(50.dp)
- }
-
- @Test
- fun itemFillingParentHeight() {
- rule.setContent {
- LazyColumnFor(
- items = listOf(0),
- modifier = Modifier.size(width = 100.dp, height = 150.dp)
- ) {
- Spacer(Modifier.width(50.dp).fillParentMaxHeight().testTag(firstItemTag))
- }
- }
-
- rule.onNodeWithTag(firstItemTag)
- .assertWidthIsEqualTo(50.dp)
- .assertHeightIsEqualTo(150.dp)
- }
-
- @Test
- fun itemFillingParentSize() {
- rule.setContent {
- LazyColumnFor(
- items = listOf(0),
- modifier = Modifier.size(width = 100.dp, height = 150.dp)
- ) {
- Spacer(Modifier.fillParentMaxSize().testTag(firstItemTag))
- }
- }
-
- rule.onNodeWithTag(firstItemTag)
- .assertWidthIsEqualTo(100.dp)
- .assertHeightIsEqualTo(150.dp)
- }
-
- @Test
- fun itemFillingParentWidthFraction() {
- rule.setContent {
- LazyColumnFor(
- items = listOf(0),
- modifier = Modifier.size(width = 100.dp, height = 150.dp)
- ) {
- Spacer(Modifier.fillParentMaxWidth(0.6f).height(50.dp).testTag(firstItemTag))
- }
- }
-
- rule.onNodeWithTag(firstItemTag)
- .assertWidthIsEqualTo(60.dp)
- .assertHeightIsEqualTo(50.dp)
- }
-
- @Test
- fun itemFillingParentHeightFraction() {
- rule.setContent {
- LazyColumnFor(
- items = listOf(0),
- modifier = Modifier.size(width = 100.dp, height = 150.dp)
- ) {
- Spacer(Modifier.width(50.dp).fillParentMaxHeight(0.2f).testTag(firstItemTag))
- }
- }
-
- rule.onNodeWithTag(firstItemTag)
- .assertWidthIsEqualTo(50.dp)
- .assertHeightIsEqualTo(30.dp)
- }
-
- @Test
- fun itemFillingParentSizeFraction() {
- rule.setContent {
- LazyColumnFor(
- items = listOf(0),
- modifier = Modifier.size(width = 100.dp, height = 150.dp)
- ) {
- Spacer(Modifier.fillParentMaxSize(0.1f).testTag(firstItemTag))
- }
- }
-
- rule.onNodeWithTag(firstItemTag)
- .assertWidthIsEqualTo(10.dp)
- .assertHeightIsEqualTo(15.dp)
- }
-
- @Test
- fun itemFillingParentSizeParentResized() {
- var parentSize by mutableStateOf(100.dp)
- rule.setContent {
- LazyColumnFor(
- items = listOf(0),
- modifier = Modifier.size(parentSize)
- ) {
- Spacer(Modifier.fillParentMaxSize().testTag(firstItemTag))
- }
- }
-
- rule.runOnIdle {
- parentSize = 150.dp
- }
-
- rule.onNodeWithTag(firstItemTag)
- .assertWidthIsEqualTo(150.dp)
- .assertHeightIsEqualTo(150.dp)
- }
-
- @Test
- fun whenNotAnymoreAvailableItemWasDisplayed() {
- var items by mutableStateOf((1..30).toList())
- rule.setContent {
- LazyColumnFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyColumnForTag)
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- // after scroll we will display items 16-20
- rule.onNodeWithTag(LazyColumnForTag)
- .scrollBy(y = 300.dp, density = rule.density)
-
- rule.runOnIdle {
- items = (1..10).toList()
- }
-
- // there is no item 16 anymore so we will just display the last items 6-10
- rule.onNodeWithTag("6")
- .assertTopPositionIsAlmost(0.dp)
- }
-
- @Test
- fun whenFewDisplayedItemsWereRemoved() {
- var items by mutableStateOf((1..10).toList())
- rule.setContent {
- LazyColumnFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyColumnForTag)
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- // after scroll we will display items 6-10
- rule.onNodeWithTag(LazyColumnForTag)
- .scrollBy(y = 100.dp, density = rule.density)
-
- rule.runOnIdle {
- items = (1..8).toList()
- }
-
- // there are no more items 9 and 10, so we have to scroll back
- rule.onNodeWithTag("4")
- .assertTopPositionIsAlmost(0.dp)
- }
-
- @Test
- fun whenItemsBecameEmpty() {
- var items by mutableStateOf((1..10).toList())
- rule.setContent {
- LazyColumnFor(
- items = items,
- modifier = Modifier.sizeIn(maxHeight = 100.dp).testTag(LazyColumnForTag)
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- // after scroll we will display items 2-6
- rule.onNodeWithTag(LazyColumnForTag)
- .scrollBy(y = 20.dp, density = rule.density)
-
- rule.runOnIdle {
- items = emptyList()
- }
-
- // there are no more items so the LazyColumn is zero sized
- rule.onNodeWithTag(LazyColumnForTag)
- .assertWidthIsEqualTo(0.dp)
- .assertHeightIsEqualTo(0.dp)
-
- // and has no children
- rule.onNodeWithTag("1")
- .assertDoesNotExist()
- rule.onNodeWithTag("2")
- .assertDoesNotExist()
- }
-
- @Test
- fun scrollBackAndForth() {
- val items by mutableStateOf((1..20).toList())
- rule.setContent {
- LazyColumnFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyColumnForTag)
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- // after scroll we will display items 6-10
- rule.onNodeWithTag(LazyColumnForTag)
- .scrollBy(y = 100.dp, density = rule.density)
-
- // and scroll back
- rule.onNodeWithTag(LazyColumnForTag)
- .scrollBy(y = (-100).dp, density = rule.density)
-
- rule.onNodeWithTag("1")
- .assertTopPositionIsAlmost(0.dp)
- }
-
- @Test
- fun tryToScrollBackwardWhenAlreadyOnTop() {
- val items by mutableStateOf((1..20).toList())
- rule.setContent {
- LazyColumnFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyColumnForTag)
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- // we already displaying the first item, so this should do nothing
- rule.onNodeWithTag(LazyColumnForTag)
- .scrollBy(y = (-50).dp, density = rule.density)
-
- rule.onNodeWithTag("1")
- .assertTopPositionIsAlmost(0.dp)
- rule.onNodeWithTag("5")
- .assertTopPositionIsAlmost(80.dp)
- }
-
- @Test
- fun contentOfNotStableItemsIsNotRecomposedDuringScroll() {
- val items = listOf(NotStable(1), NotStable(2))
- var firstItemRecomposed = 0
- var secondItemRecomposed = 0
- rule.setContent {
- LazyColumnFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyColumnForTag)
- ) {
- if (it.count == 1) {
- firstItemRecomposed++
- } else {
- secondItemRecomposed++
- }
- Spacer(Modifier.size(75.dp))
- }
- }
-
- rule.runOnIdle {
- assertThat(firstItemRecomposed).isEqualTo(1)
- assertThat(secondItemRecomposed).isEqualTo(1)
- }
-
- rule.onNodeWithTag(LazyColumnForTag)
- .scrollBy(y = (50).dp, density = rule.density)
-
- rule.runOnIdle {
- assertThat(firstItemRecomposed).isEqualTo(1)
- assertThat(secondItemRecomposed).isEqualTo(1)
- }
- }
-
- @Test
- fun onlyOneMeasurePassForScrollEvent() {
- val items by mutableStateOf((1..20).toList())
- lateinit var state: LazyListState
- rule.setContent {
- state = rememberLazyListState()
- LazyColumnFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyColumnForTag),
- state = state
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- val initialMeasurePasses = state.numMeasurePasses
-
- rule.runOnIdle {
- with(rule.density) {
- state.onScroll(-110.dp.toPx())
- }
- }
-
- rule.waitForIdle()
-
- assertThat(state.numMeasurePasses).isEqualTo(initialMeasurePasses + 1)
- }
-
- @Test
- fun stateUpdatedAfterScroll() {
- val items by mutableStateOf((1..20).toList())
- lateinit var state: LazyListState
- rule.setContent {
- state = rememberLazyListState()
- LazyColumnFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyColumnForTag),
- state = state
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- rule.runOnIdle {
- assertThat(state.firstVisibleItemIndex).isEqualTo(0)
- assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
- }
-
- rule.onNodeWithTag(LazyColumnForTag)
- .scrollBy(y = 30.dp, density = rule.density)
-
- rule.runOnIdle {
- assertThat(state.firstVisibleItemIndex).isEqualTo(1)
-
- with(rule.density) {
- // TODO(b/169232491): test scrolling doesn't appear to be scrolling exactly the right
- // number of pixels
- val expectedOffset = 10.dp.toIntPx()
- val tolerance = 2.dp.toIntPx()
- assertThat(state.firstVisibleItemScrollOffset).isEqualTo(expectedOffset, tolerance)
- }
- }
- }
-
- @Test
- fun isAnimationRunningUpdate() {
- val items by mutableStateOf((1..20).toList())
- val clock = ManualAnimationClock(0L)
- val state = LazyListState(
- flingConfig = FlingConfig(ExponentialDecay()),
- animationClock = clock
- )
- rule.setContent {
- LazyColumnFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyColumnForTag),
- state = state
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- rule.runOnIdle {
- assertThat(state.firstVisibleItemIndex).isEqualTo(0)
- assertThat(state.isAnimationRunning).isEqualTo(false)
- }
-
- rule.onNodeWithTag(LazyColumnForTag)
- .performGesture { swipeUp() }
-
- rule.runOnIdle {
- clock.clockTimeMillis += 100
- assertThat(state.firstVisibleItemIndex).isNotEqualTo(0)
- assertThat(state.isAnimationRunning).isEqualTo(true)
- }
-
- // TODO (jelle): this should be down, and not click to be 100% fair
- rule.onNodeWithTag(LazyColumnForTag)
- .performGesture { click() }
-
- rule.runOnIdle {
- assertThat(state.isAnimationRunning).isEqualTo(false)
- }
- }
-
- @Test
- fun stateUpdatedAfterScrollWithinTheSameItem() {
- val items by mutableStateOf((1..20).toList())
- lateinit var state: LazyListState
- rule.setContent {
- state = rememberLazyListState()
- LazyColumnFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyColumnForTag),
- state = state
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- rule.onNodeWithTag(LazyColumnForTag)
- .scrollBy(y = 10.dp, density = rule.density)
-
- rule.runOnIdle {
- assertThat(state.firstVisibleItemIndex).isEqualTo(0)
- with(rule.density) {
- val expectedOffset = 10.dp.toIntPx()
- val tolerance = 2.dp.toIntPx()
- assertThat(state.firstVisibleItemScrollOffset)
- .isEqualTo(expectedOffset, tolerance)
- }
- }
- }
-
- @Test
- fun initialScrollIsApplied() {
- val items by mutableStateOf((0..20).toList())
- lateinit var state: LazyListState
- val expectedOffset = with(rule.density) { 10.dp.toIntPx() }
- rule.setContent {
- state = rememberLazyListState(2, expectedOffset)
- LazyColumnFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyColumnForTag),
- state = state
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- rule.runOnIdle {
- assertThat(state.firstVisibleItemIndex).isEqualTo(2)
- assertThat(state.firstVisibleItemScrollOffset).isEqualTo(expectedOffset)
- }
-
- rule.onNodeWithTag("2")
- .assertTopPositionInRootIsEqualTo((-10).dp)
- }
-
- @Test
- fun stateIsRestored() {
- val restorationTester = StateRestorationTester(rule)
- val items by mutableStateOf((1..20).toList())
- var state: LazyListState? = null
- restorationTester.setContent {
- state = rememberLazyListState()
- LazyColumnFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyColumnForTag),
- state = state!!
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- rule.onNodeWithTag(LazyColumnForTag)
- .scrollBy(y = 30.dp, density = rule.density)
-
- val (index, scrollOffset) = rule.runOnIdle {
- state!!.firstVisibleItemIndex to state!!.firstVisibleItemScrollOffset
- }
-
- state = null
-
- restorationTester.emulateSavedInstanceStateRestore()
-
- rule.runOnIdle {
- assertThat(state!!.firstVisibleItemIndex).isEqualTo(index)
- assertThat(state!!.firstVisibleItemScrollOffset).isEqualTo(scrollOffset)
- }
- }
-
- @Test
- fun scroll_makeListSmaller_scroll() {
- var items by mutableStateOf((1..100).toList())
- rule.setContent {
- LazyColumnFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyColumnForTag)
- ) {
- Spacer(Modifier.size(10.dp).testTag("$it"))
- }
- }
-
- rule.onNodeWithTag(LazyColumnForTag)
- .scrollBy(y = 300.dp, density = rule.density)
-
- rule.runOnIdle {
- items = (1..11).toList()
- }
-
- // try to scroll after the data set has been updated. this was causing a crash previously
- rule.onNodeWithTag(LazyColumnForTag)
- .scrollBy(y = (-10).dp, density = rule.density)
-
- rule.onNodeWithTag("1")
- .assertIsDisplayed()
- }
-
- @Test
- fun snapToItemIndex() {
- val items by mutableStateOf((1..20).toList())
- lateinit var state: LazyListState
- rule.setContent {
- state = rememberLazyListState()
- LazyColumnFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyColumnForTag),
- state = state
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- rule.runOnIdle {
- runBlocking {
- state.snapToItemIndex(3, 10)
- }
- assertThat(state.firstVisibleItemIndex).isEqualTo(3)
- assertThat(state.firstVisibleItemScrollOffset).isEqualTo(10)
- }
- }
-
- @Test
- fun itemsAreNotRedrawnDuringScroll() {
- val items = (0..20).toList()
- val redrawCount = Array(6) { 0 }
- rule.setContent {
- LazyColumnFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyColumnForTag)
- ) {
- Spacer(
- Modifier.size(20.dp)
- .drawBehind { redrawCount[it]++ }
- )
- }
- }
-
- rule.onNodeWithTag(LazyColumnForTag)
- .scrollBy(y = 10.dp, density = rule.density)
-
- rule.runOnIdle {
- redrawCount.forEachIndexed { index, i ->
- assertWithMessage("Item with index $index was redrawn $i times")
- .that(i).isEqualTo(1)
- }
- }
- }
-
- @Test
- fun itemInvalidationIsNotCausingAnotherItemToRedraw() {
- val items = (0..1).toList()
- val redrawCount = Array(2) { 0 }
- var stateUsedInDrawScope by mutableStateOf(false)
- rule.setContent {
- LazyColumnFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyColumnForTag)
- ) {
- Spacer(
- Modifier.size(50.dp)
- .drawBehind {
- redrawCount[it]++
- if (it == 1) {
- stateUsedInDrawScope.hashCode()
- }
- }
- )
- }
- }
-
- rule.runOnIdle {
- stateUsedInDrawScope = true
- }
-
- rule.runOnIdle {
- assertWithMessage("First items is not expected to be redrawn")
- .that(redrawCount[0]).isEqualTo(1)
- assertWithMessage("Second items is expected to be redrawn")
- .that(redrawCount[1]).isEqualTo(2)
- }
- }
-
- @Test
- fun notVisibleAnymoreItemNotAffectingCrossAxisSize() {
- val items = (0..1).toList()
- val itemSize = with(rule.density) { 30.toDp() }
- val itemSizeMinusOne = with(rule.density) { 29.toDp() }
- lateinit var state: LazyListState
- rule.setContent {
- LazyColumnFor(
- items = items,
- state = rememberLazyListState().also { state = it },
- modifier = Modifier.height(itemSizeMinusOne).testTag(LazyColumnForTag)
- ) {
- Spacer(
- if (it == 0) {
- Modifier.width(30.dp).height(itemSizeMinusOne)
- } else {
- Modifier.width(20.dp).height(itemSize)
- }
- )
- }
- }
-
- state.scrollBy(itemSize)
-
- rule.onNodeWithTag(LazyColumnForTag)
- .assertWidthIsEqualTo(20.dp)
- }
-
- @Test
- fun itemStillVisibleAfterOverscrollIsAffectingCrossAxisSize() {
- val items = (0..2).toList()
- val itemSize = with(rule.density) { 30.toDp() }
- lateinit var state: LazyListState
- rule.setContent {
- LazyColumnFor(
- items = items,
- state = rememberLazyListState().also { state = it },
- modifier = Modifier.height(itemSize * 1.75f).testTag(LazyColumnForTag)
- ) {
- Spacer(
- if (it == 0) {
- Modifier.width(30.dp).height(itemSize / 2)
- } else if (it == 1) {
- Modifier.width(20.dp).height(itemSize / 2)
- } else {
- Modifier.width(20.dp).height(itemSize)
- }
- )
- }
- }
-
- state.scrollBy(itemSize)
-
- rule.onNodeWithTag(LazyColumnForTag)
- .assertWidthIsEqualTo(30.dp)
- }
-
- private fun SemanticsNodeInteraction.assertTopPositionIsAlmost(expected: Dp) {
- getUnclippedBoundsInRoot().top.assertIsEqualTo(expected, tolerance = 1.dp)
- }
-
- private fun LazyListState.scrollBy(offset: Dp) {
- runBlocking {
- smoothScrollBy(with(rule.density) { offset.toIntPx().toFloat() }, snap())
- }
- }
-}
-
-data class NotStable(val count: Int)
-
-internal fun IntegerSubject.isWithin1PixelFrom(expected: Int) {
- isEqualTo(expected, 1)
-}
-
-internal fun IntegerSubject.isEqualTo(expected: Int, tolerance: Int) {
- isIn(Range.closed(expected - tolerance, expected + tolerance))
-}
-
-internal fun SemanticsNodeInteraction.scrollBy(x: Dp = 0.dp, y: Dp = 0.dp, density: Density) =
- performGesture {
- with(density) {
- val touchSlop = TouchSlop.toIntPx()
- val xPx = x.toIntPx()
- val yPx = y.toIntPx()
- val offsetX = if (xPx > 0) xPx + touchSlop else if (xPx < 0) xPx - touchSlop else 0
- val offsetY = if (yPx > 0) yPx + touchSlop else if (yPx < 0) yPx - touchSlop else 0
- swipeWithVelocity(
- start = center,
- end = Offset(center.x - offsetX, center.y - offsetY),
- endVelocity = 0f
- )
- }
- }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt
index 7e629eb3..d0c6400 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyColumnTest.kt
@@ -16,101 +16,77 @@
package androidx.compose.foundation.lazy
+import androidx.compose.animation.core.ExponentialDecay
+import androidx.compose.animation.core.ManualAnimationClock
+import androidx.compose.animation.core.snap
+import androidx.compose.foundation.animation.FlingConfig
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.preferredHeight
import androidx.compose.foundation.layout.preferredSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.onCommit
+import androidx.compose.runtime.onDispose
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.gesture.TouchSlop
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.assertCountEquals
+import androidx.compose.ui.test.assertHeightIsEqualTo
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEqualTo
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.center
+import androidx.compose.ui.test.click
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.StateRestorationTester
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onChildren
import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performGesture
+import androidx.compose.ui.test.swipeUp
+import androidx.compose.ui.test.swipeWithVelocity
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
+import androidx.test.filters.LargeTest
+import com.google.common.collect.Range
+import com.google.common.truth.IntegerSubject
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.runBlocking
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import java.util.concurrent.CountDownLatch
-@MediumTest
+@LargeTest
@RunWith(AndroidJUnit4::class)
class LazyColumnTest {
- private val LazyColumnTag = "LazyColumnTag"
+ private val LazyListTag = "LazyListTag"
@get:Rule
val rule = createComposeRule()
@Test
- fun lazyColumnShowsItem() {
- val itemTestTag = "itemTestTag"
-
- rule.setContent {
- LazyColumn {
- item {
- Spacer(
- Modifier.preferredHeight(10.dp).fillParentMaxWidth().testTag(itemTestTag)
- )
- }
- }
- }
-
- rule.onNodeWithTag(itemTestTag)
- .assertIsDisplayed()
- }
-
- @Test
- fun lazyColumnShowsItems() {
- val items = (1..4).map { it.toString() }
-
- rule.setContent {
- LazyColumn(Modifier.preferredHeight(200.dp)) {
- items(items) {
- Spacer(Modifier.preferredHeight(101.dp).fillParentMaxWidth().testTag(it))
- }
- }
- }
-
- rule.onNodeWithTag("1")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("2")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("3")
- .assertDoesNotExist()
-
- rule.onNodeWithTag("4")
- .assertDoesNotExist()
- }
-
- @Test
- fun lazyColumnShowsIndexedItems() {
- val items = (1..4).map { it.toString() }
-
- rule.setContent {
- LazyColumn(Modifier.preferredHeight(200.dp)) {
- itemsIndexed(items) { index, item ->
- Spacer(
- Modifier.preferredHeight(101.dp).fillParentMaxWidth()
- .testTag("$index-$item")
- )
- }
- }
- }
-
- rule.onNodeWithTag("0-1")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("1-2")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("2-3")
- .assertDoesNotExist()
-
- rule.onNodeWithTag("3-4")
- .assertDoesNotExist()
- }
-
- @Test
fun lazyColumnShowsCombinedItems() {
val itemTestTag = "itemTestTag"
val items = listOf(1, 2).map { it.toString() }
@@ -155,59 +131,6 @@
}
@Test
- fun lazyColumnShowsItemsOnScroll() {
- val items = (1..4).map { it.toString() }
-
- rule.setContent {
- LazyColumn(Modifier.preferredHeight(200.dp).testTag(LazyColumnTag)) {
- items(items) {
- Spacer(Modifier.preferredHeight(101.dp).fillParentMaxWidth().testTag(it))
- }
- }
- }
-
- rule.onNodeWithTag(LazyColumnTag)
- .scrollBy(y = 50.dp, density = rule.density)
-
- rule.onNodeWithTag("1")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("2")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("3")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("4")
- .assertDoesNotExist()
- }
-
- @Test
- fun lazyColumnScrollHidesItem() {
- val items = (1..4).map { it.toString() }
-
- rule.setContent {
- LazyColumn(Modifier.preferredHeight(200.dp).testTag(LazyColumnTag)) {
- items(items) {
- Spacer(Modifier.preferredHeight(101.dp).fillParentMaxWidth().testTag(it))
- }
- }
- }
-
- rule.onNodeWithTag(LazyColumnTag)
- .scrollBy(y = 103.dp, density = rule.density)
-
- rule.onNodeWithTag("1")
- .assertDoesNotExist()
-
- rule.onNodeWithTag("2")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("3")
- .assertIsDisplayed()
- }
-
- @Test
fun lazyColumnAllowEmptyListItems() {
val itemTag = "itemTag"
@@ -253,4 +176,1050 @@
rule.onNodeWithTag("3")
.assertDoesNotExist()
}
-}
\ No newline at end of file
+
+ @Test
+ fun compositionsAreDisposed_whenNodesAreScrolledOff() {
+ var composed: Boolean
+ var disposed = false
+ // Ten 31dp spacers in a 300dp list
+ val latch = CountDownLatch(10)
+ // Make it long enough that it's _definitely_ taller than the screen
+ val data = (1..50).toList()
+
+ rule.setContent {
+ // Fixed height to eliminate device size as a factor
+ Box(Modifier.testTag(LazyListTag).preferredHeight(300.dp)) {
+ LazyColumn(Modifier.fillMaxSize()) {
+ items(data) {
+ onCommit {
+ composed = true
+ // Signal when everything is done composing
+ latch.countDown()
+ onDispose {
+ disposed = true
+ }
+ }
+
+ // There will be 10 of these in the 300dp box
+ Spacer(Modifier.preferredHeight(31.dp))
+ }
+ }
+ }
+ }
+
+ latch.await()
+ composed = false
+
+ assertWithMessage("Compositions were disposed before we did any scrolling")
+ .that(disposed).isFalse()
+
+ // Mostly a validity check, this is not part of the behavior under test
+ assertWithMessage("Additional composition occurred for no apparent reason")
+ .that(composed).isFalse()
+
+ rule.onNodeWithTag(LazyListTag)
+ .performGesture { swipeUp() }
+
+ rule.waitForIdle()
+
+ assertWithMessage("No additional items were composed after scroll, scroll didn't work")
+ .that(composed).isTrue()
+
+ // We may need to modify this test once we prefetch/cache items outside the viewport
+ assertWithMessage(
+ "No compositions were disposed after scrolling, compositions were leaked"
+ ).that(disposed).isTrue()
+ }
+
+ @Test
+ fun compositionsAreDisposed_whenDataIsChanged() {
+ var composed = 0
+ var disposals = 0
+ val data1 = (1..3).toList()
+ val data2 = (4..5).toList() // smaller, to ensure removal is handled properly
+
+ var part2 by mutableStateOf(false)
+
+ rule.setContent {
+ LazyColumn(Modifier.testTag(LazyListTag).fillMaxSize()) {
+ items(if (!part2) data1 else data2) {
+ onCommit {
+ composed++
+ onDispose {
+ disposals++
+ }
+ }
+
+ Spacer(Modifier.height(50.dp))
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertWithMessage("Not all items were composed")
+ .that(composed).isEqualTo(data1.size)
+ composed = 0
+
+ part2 = true
+ }
+
+ rule.runOnIdle {
+ assertWithMessage(
+ "No additional items were composed after data change, something didn't work"
+ ).that(composed).isEqualTo(data2.size)
+
+ // We may need to modify this test once we prefetch/cache items outside the viewport
+ assertWithMessage(
+ "Not enough compositions were disposed after scrolling, compositions were leaked"
+ ).that(disposals).isEqualTo(data1.size)
+ }
+ }
+
+ @Test
+ fun compositionsAreDisposed_whenAdapterListIsDisposed() {
+ var emitAdapterList by mutableStateOf(true)
+ var disposeCalledOnFirstItem = false
+ var disposeCalledOnSecondItem = false
+
+ rule.setContent {
+ if (emitAdapterList) {
+ LazyColumn(Modifier.fillMaxSize()) {
+ items(listOf(0, 1)) {
+ Box(Modifier.size(100.dp))
+ onDispose {
+ if (it == 1) {
+ disposeCalledOnFirstItem = true
+ } else {
+ disposeCalledOnSecondItem = true
+ }
+ }
+ }
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertWithMessage("First item is not immediately disposed")
+ .that(disposeCalledOnFirstItem).isFalse()
+ assertWithMessage("Second item is not immediately disposed")
+ .that(disposeCalledOnFirstItem).isFalse()
+ emitAdapterList = false
+ }
+
+ rule.runOnIdle {
+ assertWithMessage("First item is correctly disposed")
+ .that(disposeCalledOnFirstItem).isTrue()
+ assertWithMessage("Second item is correctly disposed")
+ .that(disposeCalledOnSecondItem).isTrue()
+ }
+ }
+
+ @Test
+ fun removeItemsTest() {
+ val startingNumItems = 3
+ var numItems = startingNumItems
+ var numItemsModel by mutableStateOf(numItems)
+ val tag = "List"
+ rule.setContent {
+ LazyColumn(Modifier.testTag(tag)) {
+ items((1..numItemsModel).toList()) {
+ BasicText("$it")
+ }
+ }
+ }
+
+ while (numItems >= 0) {
+ // Confirm the number of children to ensure there are no extra items
+ rule.onNodeWithTag(tag)
+ .onChildren()
+ .assertCountEquals(numItems)
+
+ // Confirm the children's content
+ for (i in 1..3) {
+ rule.onNodeWithText("$i").apply {
+ if (i <= numItems) {
+ assertExists()
+ } else {
+ assertDoesNotExist()
+ }
+ }
+ }
+ numItems--
+ if (numItems >= 0) {
+ // Don't set the model to -1
+ rule.runOnIdle { numItemsModel = numItems }
+ }
+ }
+ }
+
+ @Test
+ fun changingDataTest() {
+ val dataLists = listOf(
+ (1..3).toList(),
+ (4..8).toList(),
+ (3..4).toList()
+ )
+ var dataModel by mutableStateOf(dataLists[0])
+ val tag = "List"
+ rule.setContent {
+ LazyColumn(Modifier.testTag(tag)) {
+ items(dataModel) {
+ BasicText("$it")
+ }
+ }
+ }
+
+ for (data in dataLists) {
+ rule.runOnIdle { dataModel = data }
+
+ // Confirm the number of children to ensure there are no extra items
+ val numItems = data.size
+ rule.onNodeWithTag(tag)
+ .onChildren()
+ .assertCountEquals(numItems)
+
+ // Confirm the children's content
+ for (item in data) {
+ rule.onNodeWithText("$item").assertExists()
+ }
+ }
+ }
+
+ @Test
+ fun whenItemsAreInitiallyCreatedWith0SizeWeCanScrollWhenTheyExpanded() {
+ val thirdTag = "third"
+ val items = (1..3).toList()
+ var thirdHasSize by mutableStateOf(false)
+
+ rule.setContent {
+ LazyColumn(
+ Modifier.fillMaxWidth()
+ .preferredHeight(100.dp)
+ .testTag(LazyListTag)
+ ) {
+ items(items) {
+ if (it == 3) {
+ Spacer(
+ Modifier.testTag(thirdTag)
+ .fillParentMaxWidth()
+ .preferredHeight(if (thirdHasSize) 60.dp else 0.dp)
+ )
+ } else {
+ Spacer(Modifier.fillParentMaxWidth().preferredHeight(60.dp))
+ }
+ }
+ }
+ }
+
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(y = 21.dp, density = rule.density)
+
+ rule.onNodeWithTag(thirdTag)
+ .assertExists()
+ .assertIsNotDisplayed()
+
+ rule.runOnIdle {
+ thirdHasSize = true
+ }
+
+ rule.waitForIdle()
+
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(y = 10.dp, density = rule.density)
+
+ rule.onNodeWithTag(thirdTag)
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun lazyColumnWrapsContent() = with(rule.density) {
+ val itemInsideLazyColumn = "itemInsideLazyColumn"
+ val itemOutsideLazyColumn = "itemOutsideLazyColumn"
+ var sameSizeItems by mutableStateOf(true)
+
+ rule.setContent {
+ Row {
+ LazyColumn(Modifier.testTag(LazyListTag)) {
+ items(listOf(1, 2)) {
+ if (it == 1) {
+ Spacer(Modifier.preferredSize(50.dp).testTag(itemInsideLazyColumn))
+ } else {
+ Spacer(Modifier.preferredSize(if (sameSizeItems) 50.dp else 70.dp))
+ }
+ }
+ }
+ Spacer(Modifier.preferredSize(50.dp).testTag(itemOutsideLazyColumn))
+ }
+ }
+
+ rule.onNodeWithTag(itemInsideLazyColumn)
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag(itemOutsideLazyColumn)
+ .assertIsDisplayed()
+
+ var lazyColumnBounds = rule.onNodeWithTag(LazyListTag)
+ .getUnclippedBoundsInRoot()
+
+ assertThat(lazyColumnBounds.left.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
+ assertThat(lazyColumnBounds.right.toIntPx()).isWithin1PixelFrom(50.dp.toIntPx())
+ assertThat(lazyColumnBounds.top.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
+ assertThat(lazyColumnBounds.bottom.toIntPx()).isWithin1PixelFrom(100.dp.toIntPx())
+
+ rule.runOnIdle {
+ sameSizeItems = false
+ }
+
+ rule.waitForIdle()
+
+ rule.onNodeWithTag(itemInsideLazyColumn)
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag(itemOutsideLazyColumn)
+ .assertIsDisplayed()
+
+ lazyColumnBounds = rule.onNodeWithTag(LazyListTag)
+ .getUnclippedBoundsInRoot()
+
+ assertThat(lazyColumnBounds.left.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
+ assertThat(lazyColumnBounds.right.toIntPx()).isWithin1PixelFrom(70.dp.toIntPx())
+ assertThat(lazyColumnBounds.top.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
+ assertThat(lazyColumnBounds.bottom.toIntPx()).isWithin1PixelFrom(120.dp.toIntPx())
+ }
+
+ private val firstItemTag = "firstItemTag"
+ private val secondItemTag = "secondItemTag"
+
+ private fun prepareLazyColumnsItemsAlignment(horizontalGravity: Alignment.Horizontal) {
+ rule.setContent {
+ LazyColumn(
+ Modifier.testTag(LazyListTag).width(100.dp),
+ horizontalAlignment = horizontalGravity
+ ) {
+ items(listOf(1, 2)) {
+ if (it == 1) {
+ Spacer(Modifier.preferredSize(50.dp).testTag(firstItemTag))
+ } else {
+ Spacer(Modifier.preferredSize(70.dp).testTag(secondItemTag))
+ }
+ }
+ }
+ }
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag(secondItemTag)
+ .assertIsDisplayed()
+
+ val lazyColumnBounds = rule.onNodeWithTag(LazyListTag)
+ .getUnclippedBoundsInRoot()
+
+ with(rule.density) {
+ // Verify the width of the column
+ assertThat(lazyColumnBounds.left.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
+ assertThat(lazyColumnBounds.right.toIntPx()).isWithin1PixelFrom(100.dp.toIntPx())
+ }
+ }
+
+ @Test
+ fun lazyColumnAlignmentCenterHorizontally() {
+ prepareLazyColumnsItemsAlignment(Alignment.CenterHorizontally)
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertPositionInRootIsEqualTo(25.dp, 0.dp)
+
+ rule.onNodeWithTag(secondItemTag)
+ .assertPositionInRootIsEqualTo(15.dp, 50.dp)
+ }
+
+ @Test
+ fun lazyColumnAlignmentStart() {
+ prepareLazyColumnsItemsAlignment(Alignment.Start)
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+
+ rule.onNodeWithTag(secondItemTag)
+ .assertPositionInRootIsEqualTo(0.dp, 50.dp)
+ }
+
+ @Test
+ fun lazyColumnAlignmentEnd() {
+ prepareLazyColumnsItemsAlignment(Alignment.End)
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertPositionInRootIsEqualTo(50.dp, 0.dp)
+
+ rule.onNodeWithTag(secondItemTag)
+ .assertPositionInRootIsEqualTo(30.dp, 50.dp)
+ }
+
+ @Test
+ fun itemFillingParentWidth() {
+ rule.setContent {
+ LazyColumn(Modifier.size(width = 100.dp, height = 150.dp)) {
+ items(listOf(0)) {
+ Spacer(Modifier.fillParentMaxWidth().height(50.dp).testTag(firstItemTag))
+ }
+ }
+ }
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertWidthIsEqualTo(100.dp)
+ .assertHeightIsEqualTo(50.dp)
+ }
+
+ @Test
+ fun itemFillingParentHeight() {
+ rule.setContent {
+ LazyColumn(Modifier.size(width = 100.dp, height = 150.dp)) {
+ items(listOf(0)) {
+ Spacer(Modifier.width(50.dp).fillParentMaxHeight().testTag(firstItemTag))
+ }
+ }
+ }
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertWidthIsEqualTo(50.dp)
+ .assertHeightIsEqualTo(150.dp)
+ }
+
+ @Test
+ fun itemFillingParentSize() {
+ rule.setContent {
+ LazyColumn(Modifier.size(width = 100.dp, height = 150.dp)) {
+ items(listOf(0)) {
+ Spacer(Modifier.fillParentMaxSize().testTag(firstItemTag))
+ }
+ }
+ }
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertWidthIsEqualTo(100.dp)
+ .assertHeightIsEqualTo(150.dp)
+ }
+
+ @Test
+ fun itemFillingParentWidthFraction() {
+ rule.setContent {
+ LazyColumn(Modifier.size(width = 100.dp, height = 150.dp)) {
+ items(listOf(0)) {
+ Spacer(Modifier.fillParentMaxWidth(0.6f).height(50.dp).testTag(firstItemTag))
+ }
+ }
+ }
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertWidthIsEqualTo(60.dp)
+ .assertHeightIsEqualTo(50.dp)
+ }
+
+ @Test
+ fun itemFillingParentHeightFraction() {
+ rule.setContent {
+ LazyColumn(Modifier.size(width = 100.dp, height = 150.dp)) {
+ items(listOf(0)) {
+ Spacer(Modifier.width(50.dp).fillParentMaxHeight(0.2f).testTag(firstItemTag))
+ }
+ }
+ }
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertWidthIsEqualTo(50.dp)
+ .assertHeightIsEqualTo(30.dp)
+ }
+
+ @Test
+ fun itemFillingParentSizeFraction() {
+ rule.setContent {
+ LazyColumn(Modifier.size(width = 100.dp, height = 150.dp)) {
+ items(listOf(0)) {
+ Spacer(Modifier.fillParentMaxSize(0.1f).testTag(firstItemTag))
+ }
+ }
+ }
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertWidthIsEqualTo(10.dp)
+ .assertHeightIsEqualTo(15.dp)
+ }
+
+ @Test
+ fun itemFillingParentSizeParentResized() {
+ var parentSize by mutableStateOf(100.dp)
+ rule.setContent {
+ LazyColumn(Modifier.size(parentSize)) {
+ items(listOf(0)) {
+ Spacer(Modifier.fillParentMaxSize().testTag(firstItemTag))
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ parentSize = 150.dp
+ }
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertWidthIsEqualTo(150.dp)
+ .assertHeightIsEqualTo(150.dp)
+ }
+
+ @Test
+ fun whenNotAnymoreAvailableItemWasDisplayed() {
+ var items by mutableStateOf((1..30).toList())
+ rule.setContent {
+ LazyColumn(Modifier.size(100.dp).testTag(LazyListTag)) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ // after scroll we will display items 16-20
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(y = 300.dp, density = rule.density)
+
+ rule.runOnIdle {
+ items = (1..10).toList()
+ }
+
+ // there is no item 16 anymore so we will just display the last items 6-10
+ rule.onNodeWithTag("6")
+ .assertTopPositionIsAlmost(0.dp)
+ }
+
+ @Test
+ fun whenFewDisplayedItemsWereRemoved() {
+ var items by mutableStateOf((1..10).toList())
+ rule.setContent {
+ LazyColumn(Modifier.size(100.dp).testTag(LazyListTag)) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ // after scroll we will display items 6-10
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(y = 100.dp, density = rule.density)
+
+ rule.runOnIdle {
+ items = (1..8).toList()
+ }
+
+ // there are no more items 9 and 10, so we have to scroll back
+ rule.onNodeWithTag("4")
+ .assertTopPositionIsAlmost(0.dp)
+ }
+
+ @Test
+ fun whenItemsBecameEmpty() {
+ var items by mutableStateOf((1..10).toList())
+ rule.setContent {
+ LazyColumn(Modifier.sizeIn(maxHeight = 100.dp).testTag(LazyListTag)) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ // after scroll we will display items 2-6
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(y = 20.dp, density = rule.density)
+
+ rule.runOnIdle {
+ items = emptyList()
+ }
+
+ // there are no more items so the LazyColumn is zero sized
+ rule.onNodeWithTag(LazyListTag)
+ .assertWidthIsEqualTo(0.dp)
+ .assertHeightIsEqualTo(0.dp)
+
+ // and has no children
+ rule.onNodeWithTag("1")
+ .assertDoesNotExist()
+ rule.onNodeWithTag("2")
+ .assertDoesNotExist()
+ }
+
+ @Test
+ fun scrollBackAndForth() {
+ val items by mutableStateOf((1..20).toList())
+ rule.setContent {
+ LazyColumn(Modifier.size(100.dp).testTag(LazyListTag)) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ // after scroll we will display items 6-10
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(y = 100.dp, density = rule.density)
+
+ // and scroll back
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(y = (-100).dp, density = rule.density)
+
+ rule.onNodeWithTag("1")
+ .assertTopPositionIsAlmost(0.dp)
+ }
+
+ @Test
+ fun tryToScrollBackwardWhenAlreadyOnTop() {
+ val items by mutableStateOf((1..20).toList())
+ rule.setContent {
+ LazyColumn(Modifier.size(100.dp).testTag(LazyListTag)) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ // we already displaying the first item, so this should do nothing
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(y = (-50).dp, density = rule.density)
+
+ rule.onNodeWithTag("1")
+ .assertTopPositionIsAlmost(0.dp)
+ rule.onNodeWithTag("5")
+ .assertTopPositionIsAlmost(80.dp)
+ }
+
+ @Test
+ fun contentOfNotStableItemsIsNotRecomposedDuringScroll() {
+ val items = listOf(NotStable(1), NotStable(2))
+ var firstItemRecomposed = 0
+ var secondItemRecomposed = 0
+ rule.setContent {
+ LazyColumn(Modifier.size(100.dp).testTag(LazyListTag)) {
+ items(items) {
+ if (it.count == 1) {
+ firstItemRecomposed++
+ } else {
+ secondItemRecomposed++
+ }
+ Spacer(Modifier.size(75.dp))
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertThat(firstItemRecomposed).isEqualTo(1)
+ assertThat(secondItemRecomposed).isEqualTo(1)
+ }
+
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(y = (50).dp, density = rule.density)
+
+ rule.runOnIdle {
+ assertThat(firstItemRecomposed).isEqualTo(1)
+ assertThat(secondItemRecomposed).isEqualTo(1)
+ }
+ }
+
+ @Test
+ fun onlyOneMeasurePassForScrollEvent() {
+ val items by mutableStateOf((1..20).toList())
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState()
+ LazyColumn(
+ Modifier.size(100.dp).testTag(LazyListTag),
+ state = state
+ ) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ val initialMeasurePasses = state.numMeasurePasses
+
+ rule.runOnIdle {
+ with(rule.density) {
+ state.onScroll(-110.dp.toPx())
+ }
+ }
+
+ rule.waitForIdle()
+
+ assertThat(state.numMeasurePasses).isEqualTo(initialMeasurePasses + 1)
+ }
+
+ @Test
+ fun stateUpdatedAfterScroll() {
+ val items by mutableStateOf((1..20).toList())
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState()
+ LazyColumn(
+ Modifier.size(100.dp).testTag(LazyListTag),
+ state = state
+ ) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+ assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
+ }
+
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(y = 30.dp, density = rule.density)
+
+ rule.runOnIdle {
+ assertThat(state.firstVisibleItemIndex).isEqualTo(1)
+
+ with(rule.density) {
+ // TODO(b/169232491): test scrolling doesn't appear to be scrolling exactly the right
+ // number of pixels
+ val expectedOffset = 10.dp.toIntPx()
+ val tolerance = 2.dp.toIntPx()
+ assertThat(state.firstVisibleItemScrollOffset).isEqualTo(expectedOffset, tolerance)
+ }
+ }
+ }
+
+ @Test
+ fun isAnimationRunningUpdate() {
+ val items by mutableStateOf((1..20).toList())
+ val clock = ManualAnimationClock(0L)
+ val state = LazyListState(
+ flingConfig = FlingConfig(ExponentialDecay()),
+ animationClock = clock
+ )
+ rule.setContent {
+ LazyColumn(
+ Modifier.size(100.dp).testTag(LazyListTag),
+ state = state
+ ) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+ assertThat(state.isAnimationRunning).isEqualTo(false)
+ }
+
+ rule.onNodeWithTag(LazyListTag)
+ .performGesture { swipeUp() }
+
+ rule.runOnIdle {
+ clock.clockTimeMillis += 100
+ assertThat(state.firstVisibleItemIndex).isNotEqualTo(0)
+ assertThat(state.isAnimationRunning).isEqualTo(true)
+ }
+
+ // TODO (jelle): this should be down, and not click to be 100% fair
+ rule.onNodeWithTag(LazyListTag)
+ .performGesture { click() }
+
+ rule.runOnIdle {
+ assertThat(state.isAnimationRunning).isEqualTo(false)
+ }
+ }
+
+ @Test
+ fun stateUpdatedAfterScrollWithinTheSameItem() {
+ val items by mutableStateOf((1..20).toList())
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState()
+ LazyColumn(
+ Modifier.size(100.dp).testTag(LazyListTag),
+ state = state
+ ) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(y = 10.dp, density = rule.density)
+
+ rule.runOnIdle {
+ assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+ with(rule.density) {
+ val expectedOffset = 10.dp.toIntPx()
+ val tolerance = 2.dp.toIntPx()
+ assertThat(state.firstVisibleItemScrollOffset)
+ .isEqualTo(expectedOffset, tolerance)
+ }
+ }
+ }
+
+ @Test
+ fun initialScrollIsApplied() {
+ val items by mutableStateOf((0..20).toList())
+ lateinit var state: LazyListState
+ val expectedOffset = with(rule.density) { 10.dp.toIntPx() }
+ rule.setContent {
+ state = rememberLazyListState(2, expectedOffset)
+ LazyColumn(
+ Modifier.size(100.dp).testTag(LazyListTag),
+ state = state
+ ) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertThat(state.firstVisibleItemIndex).isEqualTo(2)
+ assertThat(state.firstVisibleItemScrollOffset).isEqualTo(expectedOffset)
+ }
+
+ rule.onNodeWithTag("2")
+ .assertTopPositionInRootIsEqualTo((-10).dp)
+ }
+
+ @Test
+ fun stateIsRestored() {
+ val restorationTester = StateRestorationTester(rule)
+ val items by mutableStateOf((1..20).toList())
+ var state: LazyListState? = null
+ restorationTester.setContent {
+ state = rememberLazyListState()
+ LazyColumn(
+ Modifier.size(100.dp).testTag(LazyListTag),
+ state = state!!
+ ) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(y = 30.dp, density = rule.density)
+
+ val (index, scrollOffset) = rule.runOnIdle {
+ state!!.firstVisibleItemIndex to state!!.firstVisibleItemScrollOffset
+ }
+
+ state = null
+
+ restorationTester.emulateSavedInstanceStateRestore()
+
+ rule.runOnIdle {
+ assertThat(state!!.firstVisibleItemIndex).isEqualTo(index)
+ assertThat(state!!.firstVisibleItemScrollOffset).isEqualTo(scrollOffset)
+ }
+ }
+
+ @Test
+ fun scroll_makeListSmaller_scroll() {
+ var items by mutableStateOf((1..100).toList())
+ rule.setContent {
+ LazyColumn(Modifier.size(100.dp).testTag(LazyListTag)) {
+ items(items) {
+ Spacer(Modifier.size(10.dp).testTag("$it"))
+ }
+ }
+ }
+
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(y = 300.dp, density = rule.density)
+
+ rule.runOnIdle {
+ items = (1..11).toList()
+ }
+
+ // try to scroll after the data set has been updated. this was causing a crash previously
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(y = (-10).dp, density = rule.density)
+
+ rule.onNodeWithTag("1")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun snapToItemIndex() {
+ val items by mutableStateOf((1..20).toList())
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState()
+ LazyColumn(
+ Modifier.size(100.dp).testTag(LazyListTag),
+ state = state
+ ) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ runBlocking {
+ state.snapToItemIndex(3, 10)
+ }
+ assertThat(state.firstVisibleItemIndex).isEqualTo(3)
+ assertThat(state.firstVisibleItemScrollOffset).isEqualTo(10)
+ }
+ }
+
+ @Test
+ fun itemsAreNotRedrawnDuringScroll() {
+ val items = (0..20).toList()
+ val redrawCount = Array(6) { 0 }
+ rule.setContent {
+ LazyColumn(Modifier.size(100.dp).testTag(LazyListTag)) {
+ items(items) {
+ Spacer(
+ Modifier.size(20.dp)
+ .drawBehind { redrawCount[it]++ }
+ )
+ }
+ }
+ }
+
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(y = 10.dp, density = rule.density)
+
+ rule.runOnIdle {
+ redrawCount.forEachIndexed { index, i ->
+ assertWithMessage("Item with index $index was redrawn $i times")
+ .that(i).isEqualTo(1)
+ }
+ }
+ }
+
+ @Test
+ fun itemInvalidationIsNotCausingAnotherItemToRedraw() {
+ val items = (0..1).toList()
+ val redrawCount = Array(2) { 0 }
+ var stateUsedInDrawScope by mutableStateOf(false)
+ rule.setContent {
+ LazyColumn(Modifier.size(100.dp).testTag(LazyListTag)) {
+ items(items) {
+ Spacer(
+ Modifier.size(50.dp)
+ .drawBehind {
+ redrawCount[it]++
+ if (it == 1) {
+ stateUsedInDrawScope.hashCode()
+ }
+ }
+ )
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ stateUsedInDrawScope = true
+ }
+
+ rule.runOnIdle {
+ assertWithMessage("First items is not expected to be redrawn")
+ .that(redrawCount[0]).isEqualTo(1)
+ assertWithMessage("Second items is expected to be redrawn")
+ .that(redrawCount[1]).isEqualTo(2)
+ }
+ }
+
+ @Test
+ fun notVisibleAnymoreItemNotAffectingCrossAxisSize() {
+ val items = (0..1).toList()
+ val itemSize = with(rule.density) { 30.toDp() }
+ val itemSizeMinusOne = with(rule.density) { 29.toDp() }
+ lateinit var state: LazyListState
+ rule.setContent {
+ LazyColumn(
+ Modifier.height(itemSizeMinusOne).testTag(LazyListTag),
+ state = rememberLazyListState().also { state = it }
+ ) {
+ items(items) {
+ Spacer(
+ if (it == 0) {
+ Modifier.width(30.dp).height(itemSizeMinusOne)
+ } else {
+ Modifier.width(20.dp).height(itemSize)
+ }
+ )
+ }
+ }
+ }
+
+ state.scrollBy(itemSize)
+
+ rule.onNodeWithTag(LazyListTag)
+ .assertWidthIsEqualTo(20.dp)
+ }
+
+ @Test
+ fun itemStillVisibleAfterOverscrollIsAffectingCrossAxisSize() {
+ val items = (0..2).toList()
+ val itemSize = with(rule.density) { 30.toDp() }
+ lateinit var state: LazyListState
+ rule.setContent {
+ LazyColumn(
+ Modifier.height(itemSize * 1.75f).testTag(LazyListTag),
+ state = rememberLazyListState().also { state = it }
+ ) {
+ items(items) {
+ Spacer(
+ if (it == 0) {
+ Modifier.width(30.dp).height(itemSize / 2)
+ } else if (it == 1) {
+ Modifier.width(20.dp).height(itemSize / 2)
+ } else {
+ Modifier.width(20.dp).height(itemSize)
+ }
+ )
+ }
+ }
+ }
+
+ state.scrollBy(itemSize)
+
+ rule.onNodeWithTag(LazyListTag)
+ .assertWidthIsEqualTo(30.dp)
+ }
+
+ private fun SemanticsNodeInteraction.assertTopPositionIsAlmost(expected: Dp) {
+ getUnclippedBoundsInRoot().top.assertIsEqualTo(expected, tolerance = 1.dp)
+ }
+
+ private fun LazyListState.scrollBy(offset: Dp) {
+ runBlocking {
+ smoothScrollBy(with(rule.density) { offset.toIntPx().toFloat() }, snap())
+ }
+ }
+}
+
+data class NotStable(val count: Int)
+
+internal fun IntegerSubject.isWithin1PixelFrom(expected: Int) {
+ isEqualTo(expected, 1)
+}
+
+internal fun IntegerSubject.isEqualTo(expected: Int, tolerance: Int) {
+ isIn(Range.closed(expected - tolerance, expected + tolerance))
+}
+
+internal fun SemanticsNodeInteraction.scrollBy(x: Dp = 0.dp, y: Dp = 0.dp, density: Density) =
+ performGesture {
+ with(density) {
+ val touchSlop = TouchSlop.toIntPx()
+ val xPx = x.toIntPx()
+ val yPx = y.toIntPx()
+ val offsetX = if (xPx > 0) xPx + touchSlop else if (xPx < 0) xPx - touchSlop else 0
+ val offsetY = if (yPx > 0) yPx + touchSlop else if (yPx < 0) yPx - touchSlop else 0
+ swipeWithVelocity(
+ start = center,
+ end = Offset(center.x - offsetX, center.y - offsetY),
+ endVelocity = 0f
+ )
+ }
+ }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyForIndexedTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyForIndexedTest.kt
deleted file mode 100644
index 37c72b8..0000000
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyForIndexedTest.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.foundation.lazy
-
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.preferredHeight
-import androidx.compose.foundation.layout.preferredWidth
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.text.BasicText
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
-import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithText
-import androidx.compose.ui.unit.dp
-import org.junit.Rule
-import org.junit.Test
-
-class LazyForIndexedTest {
-
- @get:Rule
- val rule = createComposeRule()
-
- @Test
- fun columnWithIndexesComposedWithCorrectIndexAndItem() {
- val items = (0..1).map { it.toString() }
-
- rule.setContent {
- LazyColumnForIndexed(items, Modifier.preferredHeight(200.dp)) { index, item ->
- BasicText("${index}x$item", Modifier.fillParentMaxWidth().height(100.dp))
- }
- }
-
- rule.onNodeWithText("0x0")
- .assertTopPositionInRootIsEqualTo(0.dp)
-
- rule.onNodeWithText("1x1")
- .assertTopPositionInRootIsEqualTo(100.dp)
- }
-
- @Test
- fun rowWithIndexesComposedWithCorrectIndexAndItem() {
- val items = (0..1).map { it.toString() }
-
- rule.setContent {
- LazyRowForIndexed(items, Modifier.preferredWidth(200.dp)) { index, item ->
- BasicText("${index}x$item", Modifier.fillParentMaxHeight().width(100.dp))
- }
- }
-
- rule.onNodeWithText("0x0")
- .assertLeftPositionInRootIsEqualTo(0.dp)
-
- rule.onNodeWithText("1x1")
- .assertLeftPositionInRootIsEqualTo(100.dp)
- }
-}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListsContentPaddingTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListsContentPaddingTest.kt
index c77dee6..8b33fbf 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListsContentPaddingTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListsContentPaddingTest.kt
@@ -68,11 +68,10 @@
val smallPaddingSize = itemSize / 4
val largePaddingSize = itemSize
rule.setContent {
- LazyColumnFor(
- items = listOf(1),
- state = rememberLazyListState().also { state = it },
+ LazyColumn(
modifier = Modifier.size(containerSize)
.testTag(LazyListTag),
+ state = rememberLazyListState().also { state = it },
contentPadding = PaddingValues(
start = smallPaddingSize,
top = largePaddingSize,
@@ -80,7 +79,9 @@
bottom = largePaddingSize
)
) {
- Spacer(Modifier.fillParentMaxWidth().preferredHeight(itemSize).testTag(ItemTag))
+ items(listOf(1)) {
+ Spacer(Modifier.fillParentMaxWidth().preferredHeight(itemSize).testTag(ItemTag))
+ }
}
}
@@ -101,17 +102,18 @@
fun column_contentPaddingIsNotAffectingScrollPosition() {
lateinit var state: LazyListState
rule.setContent {
- LazyColumnFor(
- items = listOf(1),
- state = rememberLazyListState().also { state = it },
+ LazyColumn(
modifier = Modifier.size(itemSize * 2)
.testTag(LazyListTag),
+ state = rememberLazyListState().also { state = it },
contentPadding = PaddingValues(
top = itemSize,
bottom = itemSize
)
) {
- Spacer(Modifier.fillParentMaxWidth().preferredHeight(itemSize).testTag(ItemTag))
+ items(listOf(1)) {
+ Spacer(Modifier.fillParentMaxWidth().preferredHeight(itemSize).testTag(ItemTag))
+ }
}
}
@@ -127,17 +129,18 @@
lateinit var state: LazyListState
val padding = itemSize * 1.5f
rule.setContent {
- LazyColumnFor(
- items = (0..3).toList(),
- state = rememberLazyListState().also { state = it },
+ LazyColumn(
modifier = Modifier.size(padding * 2 + itemSize)
.testTag(LazyListTag),
+ state = rememberLazyListState().also { state = it },
contentPadding = PaddingValues(
top = padding,
bottom = padding
)
) {
- Spacer(Modifier.size(itemSize).testTag(it.toString()))
+ items((0..3).toList()) {
+ Spacer(Modifier.size(itemSize).testTag(it.toString()))
+ }
}
}
@@ -167,17 +170,18 @@
lateinit var state: LazyListState
val padding = itemSize * 1.5f
rule.setContent {
- LazyColumnFor(
- items = (0..3).toList(),
- state = rememberLazyListState().also { state = it },
+ LazyColumn(
modifier = Modifier.size(itemSize + padding * 2)
.testTag(LazyListTag),
+ state = rememberLazyListState().also { state = it },
contentPadding = PaddingValues(
top = padding,
bottom = padding
)
) {
- Spacer(Modifier.size(itemSize).testTag(it.toString()))
+ items((0..3).toList()) {
+ Spacer(Modifier.size(itemSize).testTag(it.toString()))
+ }
}
}
@@ -201,17 +205,18 @@
lateinit var state: LazyListState
val padding = itemSize * 1.5f
rule.setContent {
- LazyColumnFor(
- items = (0..3).toList(),
- state = rememberLazyListState().also { state = it },
+ LazyColumn(
modifier = Modifier.size(padding * 2 + itemSize)
.testTag(LazyListTag),
+ state = rememberLazyListState().also { state = it },
contentPadding = PaddingValues(
top = padding,
bottom = padding
)
) {
- Spacer(Modifier.size(itemSize).testTag(it.toString()))
+ items((0..3).toList()) {
+ Spacer(Modifier.size(itemSize).testTag(it.toString()))
+ }
}
}
@@ -244,17 +249,18 @@
lateinit var state: LazyListState
val padding = itemSize * 1.5f
rule.setContent {
- LazyColumnFor(
- items = (0..3).toList(),
- state = rememberLazyListState().also { state = it },
+ LazyColumn(
modifier = Modifier.size(padding * 2 + itemSize)
.testTag(LazyListTag),
+ state = rememberLazyListState().also { state = it },
contentPadding = PaddingValues(
top = padding,
bottom = padding
)
) {
- Spacer(Modifier.size(itemSize).testTag(it.toString()))
+ items((0..3).toList()) {
+ Spacer(Modifier.size(itemSize).testTag(it.toString()))
+ }
}
}
@@ -275,8 +281,7 @@
fun column_contentPaddingAndWrapContent() {
rule.setContent {
Box(modifier = Modifier.testTag(ContainerTag)) {
- LazyColumnFor(
- items = listOf(1),
+ LazyColumn(
contentPadding = PaddingValues(
start = 2.dp,
top = 4.dp,
@@ -284,7 +289,9 @@
bottom = 8.dp
)
) {
- Spacer(Modifier.size(itemSize).testTag(ItemTag))
+ items(listOf(1)) {
+ Spacer(Modifier.size(itemSize).testTag(ItemTag))
+ }
}
}
}
@@ -306,8 +313,7 @@
fun column_contentPaddingAndNoContent() {
rule.setContent {
Box(modifier = Modifier.testTag(ContainerTag)) {
- LazyColumnFor(
- items = listOf(0),
+ LazyColumn(
contentPadding = PaddingValues(
start = 2.dp,
top = 4.dp,
@@ -315,6 +321,8 @@
bottom = 8.dp
)
) {
+ items(listOf(0)) {
+ }
}
}
}
@@ -333,11 +341,10 @@
val smallPaddingSize = itemSize / 4
val largePaddingSize = itemSize
rule.setContent {
- LazyRowFor(
- items = listOf(1),
- state = rememberLazyListState().also { state = it },
+ LazyRow(
modifier = Modifier.size(containerSize)
.testTag(LazyListTag),
+ state = rememberLazyListState().also { state = it },
contentPadding = PaddingValues(
top = smallPaddingSize,
start = largePaddingSize,
@@ -345,7 +352,9 @@
end = largePaddingSize
)
) {
- Spacer(Modifier.fillParentMaxHeight().preferredWidth(itemSize).testTag(ItemTag))
+ items(listOf(1)) {
+ Spacer(Modifier.fillParentMaxHeight().preferredWidth(itemSize).testTag(ItemTag))
+ }
}
}
@@ -369,17 +378,18 @@
50.dp.toIntPx().toDp()
}
rule.setContent {
- LazyRowFor(
- items = listOf(1),
- state = rememberLazyListState().also { state = it },
+ LazyRow(
modifier = Modifier.size(itemSize * 2)
.testTag(LazyListTag),
+ state = rememberLazyListState().also { state = it },
contentPadding = PaddingValues(
start = itemSize,
end = itemSize
)
) {
- Spacer(Modifier.fillParentMaxHeight().preferredWidth(itemSize).testTag(ItemTag))
+ items(listOf(1)) {
+ Spacer(Modifier.fillParentMaxHeight().preferredWidth(itemSize).testTag(ItemTag))
+ }
}
}
@@ -395,17 +405,18 @@
lateinit var state: LazyListState
val padding = itemSize * 1.5f
rule.setContent {
- LazyRowFor(
- items = (0..3).toList(),
- state = rememberLazyListState().also { state = it },
+ LazyRow(
modifier = Modifier.size(padding * 2 + itemSize)
.testTag(LazyListTag),
+ state = rememberLazyListState().also { state = it },
contentPadding = PaddingValues(
start = padding,
end = padding
)
) {
- Spacer(Modifier.size(itemSize).testTag(it.toString()))
+ items((0..3).toList()) {
+ Spacer(Modifier.size(itemSize).testTag(it.toString()))
+ }
}
}
@@ -435,17 +446,18 @@
lateinit var state: LazyListState
val padding = itemSize * 1.5f
rule.setContent {
- LazyRowFor(
- items = (0..3).toList(),
- state = rememberLazyListState().also { state = it },
+ LazyRow(
modifier = Modifier.size(itemSize + padding * 2)
.testTag(LazyListTag),
+ state = rememberLazyListState().also { state = it },
contentPadding = PaddingValues(
start = padding,
end = padding
)
) {
- Spacer(Modifier.size(itemSize).testTag(it.toString()))
+ items((0..3).toList()) {
+ Spacer(Modifier.size(itemSize).testTag(it.toString()))
+ }
}
}
@@ -469,17 +481,18 @@
lateinit var state: LazyListState
val padding = itemSize * 1.5f
rule.setContent {
- LazyRowFor(
- items = (0..3).toList(),
- state = rememberLazyListState().also { state = it },
+ LazyRow(
modifier = Modifier.size(padding * 2 + itemSize)
.testTag(LazyListTag),
+ state = rememberLazyListState().also { state = it },
contentPadding = PaddingValues(
start = padding,
end = padding
)
) {
- Spacer(Modifier.size(itemSize).testTag(it.toString()))
+ items((0..3).toList()) {
+ Spacer(Modifier.size(itemSize).testTag(it.toString()))
+ }
}
}
@@ -512,17 +525,18 @@
lateinit var state: LazyListState
val padding = itemSize * 1.5f
rule.setContent {
- LazyRowFor(
- items = (0..3).toList(),
- state = rememberLazyListState().also { state = it },
+ LazyRow(
modifier = Modifier.size(padding * 2 + itemSize)
.testTag(LazyListTag),
+ state = rememberLazyListState().also { state = it },
contentPadding = PaddingValues(
start = padding,
end = padding
)
) {
- Spacer(Modifier.size(itemSize).testTag(it.toString()))
+ items((0..3).toList()) {
+ Spacer(Modifier.size(itemSize).testTag(it.toString()))
+ }
}
}
@@ -543,8 +557,7 @@
fun row_contentPaddingAndWrapContent() {
rule.setContent {
Box(modifier = Modifier.testTag(ContainerTag)) {
- LazyRowFor(
- items = listOf(1),
+ LazyRow(
contentPadding = PaddingValues(
start = 2.dp,
top = 4.dp,
@@ -552,7 +565,9 @@
bottom = 8.dp
)
) {
- Spacer(Modifier.size(itemSize).testTag(ItemTag))
+ items(listOf(1)) {
+ Spacer(Modifier.size(itemSize).testTag(ItemTag))
+ }
}
}
}
@@ -574,8 +589,7 @@
fun row_contentPaddingAndNoContent() {
rule.setContent {
Box(modifier = Modifier.testTag(ContainerTag)) {
- LazyRowFor(
- items = listOf(0),
+ LazyRow(
contentPadding = PaddingValues(
start = 2.dp,
top = 4.dp,
@@ -583,6 +597,7 @@
bottom = 8.dp
)
) {
+ items(listOf(0)) {}
}
}
}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListsIndexedTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListsIndexedTest.kt
new file mode 100644
index 0000000..16bb089
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyListsIndexedTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.lazy
+
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.preferredHeight
+import androidx.compose.foundation.layout.preferredWidth
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.dp
+import org.junit.Rule
+import org.junit.Test
+
+class LazyListsIndexedTest {
+
+ @get:Rule
+ val rule = createComposeRule()
+
+ @Test
+ fun lazyColumnShowsIndexedItems() {
+ val items = (1..4).map { it.toString() }
+
+ rule.setContent {
+ LazyColumn(Modifier.preferredHeight(200.dp)) {
+ itemsIndexed(items) { index, item ->
+ Spacer(
+ Modifier.preferredHeight(101.dp).fillParentMaxWidth()
+ .testTag("$index-$item")
+ )
+ }
+ }
+ }
+
+ rule.onNodeWithTag("0-1")
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag("1-2")
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag("2-3")
+ .assertDoesNotExist()
+
+ rule.onNodeWithTag("3-4")
+ .assertDoesNotExist()
+ }
+
+ @Test
+ fun columnWithIndexesComposedWithCorrectIndexAndItem() {
+ val items = (0..1).map { it.toString() }
+
+ rule.setContent {
+ LazyColumn(Modifier.preferredHeight(200.dp)) {
+ itemsIndexed(items) { index, item ->
+ BasicText("${index}x$item", Modifier.fillParentMaxWidth().height(100.dp))
+ }
+ }
+ }
+
+ rule.onNodeWithText("0x0")
+ .assertTopPositionInRootIsEqualTo(0.dp)
+
+ rule.onNodeWithText("1x1")
+ .assertTopPositionInRootIsEqualTo(100.dp)
+ }
+
+ @Test
+ fun lazyRowShowsIndexedItems() {
+ val items = (1..4).map { it.toString() }
+
+ rule.setContent {
+ LazyRow(Modifier.preferredWidth(200.dp)) {
+ itemsIndexed(items) { index, item ->
+ Spacer(
+ Modifier.preferredWidth(101.dp).fillParentMaxHeight()
+ .testTag("$index-$item")
+ )
+ }
+ }
+ }
+
+ rule.onNodeWithTag("0-1")
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag("1-2")
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag("2-3")
+ .assertDoesNotExist()
+
+ rule.onNodeWithTag("3-4")
+ .assertDoesNotExist()
+ }
+
+ @Test
+ fun rowWithIndexesComposedWithCorrectIndexAndItem() {
+ val items = (0..1).map { it.toString() }
+
+ rule.setContent {
+ LazyRow(Modifier.preferredWidth(200.dp)) {
+ itemsIndexed(items) { index, item ->
+ BasicText("${index}x$item", Modifier.fillParentMaxHeight().width(100.dp))
+ }
+ }
+ }
+
+ rule.onNodeWithText("0x0")
+ .assertLeftPositionInRootIsEqualTo(0.dp)
+
+ rule.onNodeWithText("1x1")
+ .assertLeftPositionInRootIsEqualTo(100.dp)
+ }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyNestedScrollingTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyNestedScrollingTest.kt
index 769791b..fca6c58 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyNestedScrollingTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyNestedScrollingTest.kt
@@ -72,11 +72,10 @@
}
)
) {
- LazyColumnFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyTag)
- ) {
- Spacer(Modifier.size(50.dp).testTag("$it"))
+ LazyColumn(Modifier.size(100.dp).testTag(LazyTag)) {
+ items(items) {
+ Spacer(Modifier.size(50.dp).testTag("$it"))
+ }
}
}
}
@@ -107,11 +106,10 @@
}
)
) {
- LazyColumnFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyTag)
- ) {
- Spacer(Modifier.size(50.dp).testTag("$it"))
+ LazyColumn(Modifier.size(100.dp).testTag(LazyTag)) {
+ items(items) {
+ Spacer(Modifier.size(50.dp).testTag("$it"))
+ }
}
}
}
@@ -152,11 +150,10 @@
}
)
) {
- LazyColumnFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyTag)
- ) {
- Spacer(Modifier.size(40.dp).testTag("$it"))
+ LazyColumn(Modifier.size(100.dp).testTag(LazyTag)) {
+ items(items) {
+ Spacer(Modifier.size(40.dp).testTag("$it"))
+ }
}
}
}
@@ -187,11 +184,10 @@
}
)
) {
- LazyColumnFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyTag)
- ) {
- Spacer(Modifier.size(50.dp).testTag("$it"))
+ LazyColumn(Modifier.size(100.dp).testTag(LazyTag)) {
+ items(items) {
+ Spacer(Modifier.size(50.dp).testTag("$it"))
+ }
}
}
}
@@ -227,11 +223,12 @@
}
)
) {
- LazyRowFor(
- items = items,
+ LazyRow(
modifier = Modifier.size(100.dp).testTag(LazyTag)
) {
- Spacer(Modifier.size(50.dp).testTag("$it"))
+ items(items) {
+ Spacer(Modifier.size(50.dp).testTag("$it"))
+ }
}
}
}
@@ -262,11 +259,12 @@
}
)
) {
- LazyRowFor(
- items = items,
+ LazyRow(
modifier = Modifier.size(100.dp).testTag(LazyTag)
) {
- Spacer(Modifier.size(50.dp).testTag("$it"))
+ items(items) {
+ Spacer(Modifier.size(50.dp).testTag("$it"))
+ }
}
}
}
@@ -307,11 +305,12 @@
}
)
) {
- LazyRowFor(
- items = items,
+ LazyRow(
modifier = Modifier.size(100.dp).testTag(LazyTag)
) {
- Spacer(Modifier.size(40.dp).testTag("$it"))
+ items(items) {
+ Spacer(Modifier.size(40.dp).testTag("$it"))
+ }
}
}
}
@@ -342,11 +341,12 @@
}
)
) {
- LazyRowFor(
- items = items,
+ LazyRow(
modifier = Modifier.size(100.dp).testTag(LazyTag)
) {
- Spacer(Modifier.size(50.dp).testTag("$it"))
+ items(items) {
+ Spacer(Modifier.size(50.dp).testTag("$it"))
+ }
}
}
}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowForTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowForTest.kt
deleted file mode 100644
index f4d3fd4..0000000
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowForTest.kt
+++ /dev/null
@@ -1,903 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.foundation.lazy
-
-import androidx.compose.animation.core.snap
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.preferredSize
-import androidx.compose.foundation.layout.preferredWidth
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.sizeIn
-import androidx.compose.foundation.layout.width
-import androidx.compose.runtime.Providers
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawBehind
-import androidx.compose.ui.platform.AmbientLayoutDirection
-import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.SemanticsNodeInteraction
-import androidx.compose.ui.test.assertHeightIsEqualTo
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.assertIsEqualTo
-import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
-import androidx.compose.ui.test.assertPositionInRootIsEqualTo
-import androidx.compose.ui.test.assertWidthIsEqualTo
-import androidx.compose.ui.test.getUnclippedBoundsInRoot
-import androidx.compose.ui.test.junit4.StateRestorationTester
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import com.google.common.truth.Truth
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.runBlocking
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-class LazyRowForTest {
- private val LazyRowForTag = "LazyRowForTag"
-
- @get:Rule
- val rule = createComposeRule()
-
- @Test
- fun lazyRowOnlyVisibleItemsAdded() {
- val items = (1..4).map { it.toString() }
-
- rule.setContent {
- Box(Modifier.preferredWidth(200.dp)) {
- LazyRowFor(items) {
- Spacer(Modifier.preferredWidth(101.dp).fillParentMaxHeight().testTag(it))
- }
- }
- }
-
- rule.onNodeWithTag("1")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("2")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("3")
- .assertDoesNotExist()
-
- rule.onNodeWithTag("4")
- .assertDoesNotExist()
- }
-
- @Test
- fun lazyRowScrollToShowItems123() {
- val items = (1..4).map { it.toString() }
-
- rule.setContent {
- Box(Modifier.preferredWidth(200.dp)) {
- LazyRowFor(items, Modifier.testTag(LazyRowForTag)) {
- Spacer(Modifier.preferredWidth(101.dp).fillParentMaxHeight().testTag(it))
- }
- }
- }
-
- rule.onNodeWithTag(LazyRowForTag)
- .scrollBy(x = 50.dp, density = rule.density)
-
- rule.onNodeWithTag("1")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("2")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("3")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("4")
- .assertDoesNotExist()
- }
-
- @Test
- fun lazyRowScrollToHideFirstItem() {
- val items = (1..4).map { it.toString() }
-
- rule.setContent {
- Box(Modifier.preferredWidth(200.dp)) {
- LazyRowFor(items, Modifier.testTag(LazyRowForTag)) {
- Spacer(Modifier.preferredWidth(101.dp).fillParentMaxHeight().testTag(it))
- }
- }
- }
-
- rule.onNodeWithTag(LazyRowForTag)
- .scrollBy(x = 102.dp, density = rule.density)
-
- rule.onNodeWithTag("1")
- .assertDoesNotExist()
-
- rule.onNodeWithTag("2")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("3")
- .assertIsDisplayed()
- }
-
- @Test
- fun lazyRowScrollToShowItems234() {
- val items = (1..4).map { it.toString() }
-
- rule.setContent {
- Box(Modifier.preferredWidth(200.dp)) {
- LazyRowFor(items, Modifier.testTag(LazyRowForTag)) {
- Spacer(Modifier.preferredWidth(101.dp).fillParentMaxHeight().testTag(it))
- }
- }
- }
-
- rule.onNodeWithTag(LazyRowForTag)
- .scrollBy(x = 150.dp, density = rule.density)
-
- rule.onNodeWithTag("1")
- .assertDoesNotExist()
-
- rule.onNodeWithTag("2")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("3")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("4")
- .assertIsDisplayed()
- }
-
- @Test
- fun lazyRowWrapsContent() = with(rule.density) {
- val itemInsideLazyRow = "itemInsideLazyRow"
- val itemOutsideLazyRow = "itemOutsideLazyRow"
- var sameSizeItems by mutableStateOf(true)
-
- rule.setContent {
- Column {
- LazyRowFor(
- items = listOf(1, 2),
- modifier = Modifier.testTag(LazyRowForTag)
- ) {
- if (it == 1) {
- Spacer(Modifier.preferredSize(50.dp).testTag(itemInsideLazyRow))
- } else {
- Spacer(Modifier.preferredSize(if (sameSizeItems) 50.dp else 70.dp))
- }
- }
- Spacer(Modifier.preferredSize(50.dp).testTag(itemOutsideLazyRow))
- }
- }
-
- rule.onNodeWithTag(itemInsideLazyRow)
- .assertIsDisplayed()
-
- rule.onNodeWithTag(itemOutsideLazyRow)
- .assertIsDisplayed()
-
- var lazyRowBounds = rule.onNodeWithTag(LazyRowForTag)
- .getUnclippedBoundsInRoot()
-
- assertThat(lazyRowBounds.left.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
- assertThat(lazyRowBounds.right.toIntPx()).isWithin1PixelFrom(100.dp.toIntPx())
- assertThat(lazyRowBounds.top.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
- assertThat(lazyRowBounds.bottom.toIntPx()).isWithin1PixelFrom(50.dp.toIntPx())
-
- rule.runOnIdle {
- sameSizeItems = false
- }
-
- rule.waitForIdle()
-
- rule.onNodeWithTag(itemInsideLazyRow)
- .assertIsDisplayed()
-
- rule.onNodeWithTag(itemOutsideLazyRow)
- .assertIsDisplayed()
-
- lazyRowBounds = rule.onNodeWithTag(LazyRowForTag)
- .getUnclippedBoundsInRoot()
-
- assertThat(lazyRowBounds.left.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
- assertThat(lazyRowBounds.right.toIntPx()).isWithin1PixelFrom(120.dp.toIntPx())
- assertThat(lazyRowBounds.top.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
- assertThat(lazyRowBounds.bottom.toIntPx()).isWithin1PixelFrom(70.dp.toIntPx())
- }
-
- private val firstItemTag = "firstItemTag"
- private val secondItemTag = "secondItemTag"
-
- private fun prepareLazyRowForAlignment(verticalGravity: Alignment.Vertical) {
- rule.setContent {
- LazyRowFor(
- items = listOf(1, 2),
- modifier = Modifier.testTag(LazyRowForTag).height(100.dp),
- verticalAlignment = verticalGravity
- ) {
- if (it == 1) {
- Spacer(Modifier.preferredSize(50.dp).testTag(firstItemTag))
- } else {
- Spacer(Modifier.preferredSize(70.dp).testTag(secondItemTag))
- }
- }
- }
-
- rule.onNodeWithTag(firstItemTag)
- .assertIsDisplayed()
-
- rule.onNodeWithTag(secondItemTag)
- .assertIsDisplayed()
-
- val lazyRowBounds = rule.onNodeWithTag(LazyRowForTag)
- .getUnclippedBoundsInRoot()
-
- with(rule.density) {
- // Verify the height of the row
- assertThat(lazyRowBounds.top.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
- assertThat(lazyRowBounds.bottom.toIntPx()).isWithin1PixelFrom(100.dp.toIntPx())
- }
- }
-
- @Test
- fun lazyRowAlignmentCenterVertically() {
- prepareLazyRowForAlignment(Alignment.CenterVertically)
-
- rule.onNodeWithTag(firstItemTag)
- .assertPositionInRootIsEqualTo(0.dp, 25.dp)
-
- rule.onNodeWithTag(secondItemTag)
- .assertPositionInRootIsEqualTo(50.dp, 15.dp)
- }
-
- @Test
- fun lazyRowAlignmentTop() {
- prepareLazyRowForAlignment(Alignment.Top)
-
- rule.onNodeWithTag(firstItemTag)
- .assertPositionInRootIsEqualTo(0.dp, 0.dp)
-
- rule.onNodeWithTag(secondItemTag)
- .assertPositionInRootIsEqualTo(50.dp, 0.dp)
- }
-
- @Test
- fun lazyRowAlignmentBottom() {
- prepareLazyRowForAlignment(Alignment.Bottom)
-
- rule.onNodeWithTag(firstItemTag)
- .assertPositionInRootIsEqualTo(0.dp, 50.dp)
-
- rule.onNodeWithTag(secondItemTag)
- .assertPositionInRootIsEqualTo(50.dp, 30.dp)
- }
-
- @Test
- fun itemFillingParentWidth() {
- rule.setContent {
- LazyRowFor(
- items = listOf(0),
- modifier = Modifier.size(width = 100.dp, height = 150.dp)
- ) {
- Spacer(Modifier.fillParentMaxWidth().height(50.dp).testTag(firstItemTag))
- }
- }
-
- rule.onNodeWithTag(firstItemTag)
- .assertWidthIsEqualTo(100.dp)
- .assertHeightIsEqualTo(50.dp)
- }
-
- @Test
- fun itemFillingParentHeight() {
- rule.setContent {
- LazyRowFor(
- items = listOf(0),
- modifier = Modifier.size(width = 100.dp, height = 150.dp)
- ) {
- Spacer(Modifier.width(50.dp).fillParentMaxHeight().testTag(firstItemTag))
- }
- }
-
- rule.onNodeWithTag(firstItemTag)
- .assertWidthIsEqualTo(50.dp)
- .assertHeightIsEqualTo(150.dp)
- }
-
- @Test
- fun itemFillingParentSize() {
- rule.setContent {
- LazyRowFor(
- items = listOf(0),
- modifier = Modifier.size(width = 100.dp, height = 150.dp)
- ) {
- Spacer(Modifier.fillParentMaxSize().testTag(firstItemTag))
- }
- }
-
- rule.onNodeWithTag(firstItemTag)
- .assertWidthIsEqualTo(100.dp)
- .assertHeightIsEqualTo(150.dp)
- }
-
- @Test
- fun itemFillingParentWidthFraction() {
- rule.setContent {
- LazyRowFor(
- items = listOf(0),
- modifier = Modifier.size(width = 100.dp, height = 150.dp)
- ) {
- Spacer(Modifier.fillParentMaxWidth(0.7f).height(50.dp).testTag(firstItemTag))
- }
- }
-
- rule.onNodeWithTag(firstItemTag)
- .assertWidthIsEqualTo(70.dp)
- .assertHeightIsEqualTo(50.dp)
- }
-
- @Test
- fun itemFillingParentHeightFraction() {
- rule.setContent {
- LazyRowFor(
- items = listOf(0),
- modifier = Modifier.size(width = 100.dp, height = 150.dp)
- ) {
- Spacer(Modifier.width(50.dp).fillParentMaxHeight(0.3f).testTag(firstItemTag))
- }
- }
-
- rule.onNodeWithTag(firstItemTag)
- .assertWidthIsEqualTo(50.dp)
- .assertHeightIsEqualTo(45.dp)
- }
-
- @Test
- fun itemFillingParentSizeFraction() {
- rule.setContent {
- LazyRowFor(
- items = listOf(0),
- modifier = Modifier.size(width = 100.dp, height = 150.dp)
- ) {
- Spacer(Modifier.fillParentMaxSize(0.5f).testTag(firstItemTag))
- }
- }
-
- rule.onNodeWithTag(firstItemTag)
- .assertWidthIsEqualTo(50.dp)
- .assertHeightIsEqualTo(75.dp)
- }
-
- @Test
- fun itemFillingParentSizeParentResized() {
- var parentSize by mutableStateOf(100.dp)
- rule.setContent {
- LazyRowFor(
- items = listOf(0),
- modifier = Modifier.size(parentSize)
- ) {
- Spacer(Modifier.fillParentMaxSize().testTag(firstItemTag))
- }
- }
-
- rule.runOnIdle {
- parentSize = 150.dp
- }
-
- rule.onNodeWithTag(firstItemTag)
- .assertWidthIsEqualTo(150.dp)
- .assertHeightIsEqualTo(150.dp)
- }
-
- @Test
- fun scrollsLeftInRtl() {
- val items = (1..4).map { it.toString() }
-
- rule.setContent {
- Providers(AmbientLayoutDirection provides LayoutDirection.Rtl) {
- Box(Modifier.preferredWidth(100.dp)) {
- LazyRowFor(items, Modifier.testTag(LazyRowForTag)) {
- Spacer(Modifier.preferredWidth(101.dp).fillParentMaxHeight().testTag(it))
- }
- }
- }
- }
-
- rule.onNodeWithTag(LazyRowForTag)
- .scrollBy(x = (-150).dp, density = rule.density)
-
- rule.onNodeWithTag("1")
- .assertDoesNotExist()
-
- rule.onNodeWithTag("2")
- .assertIsDisplayed()
- }
-
- @Test
- fun whenNotAnymoreAvailableItemWasDisplayed() {
- var items by mutableStateOf((1..30).toList())
- rule.setContent {
- LazyRowFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyRowForTag)
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- // after scroll we will display items 16-20
- rule.onNodeWithTag(LazyRowForTag)
- .scrollBy(x = 300.dp, density = rule.density)
-
- rule.runOnIdle {
- items = (1..10).toList()
- }
-
- // there is no item 16 anymore so we will just display the last items 6-10
- rule.onNodeWithTag("6")
- .assertLeftPositionIsAlmost(0.dp)
- }
-
- @Test
- fun whenFewDisplayedItemsWereRemoved() {
- var items by mutableStateOf((1..10).toList())
- rule.setContent {
- LazyRowFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyRowForTag)
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- // after scroll we will display items 6-10
- rule.onNodeWithTag(LazyRowForTag)
- .scrollBy(x = 100.dp, density = rule.density)
-
- rule.runOnIdle {
- items = (1..8).toList()
- }
-
- // there are no more items 9 and 10, so we have to scroll back
- rule.onNodeWithTag("4")
- .assertLeftPositionIsAlmost(0.dp)
- }
-
- @Test
- fun whenItemsBecameEmpty() {
- var items by mutableStateOf((1..10).toList())
- rule.setContent {
- LazyRowFor(
- items = items,
- modifier = Modifier.sizeIn(maxHeight = 100.dp).testTag(LazyRowForTag)
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- // after scroll we will display items 2-6
- rule.onNodeWithTag(LazyRowForTag)
- .scrollBy(x = 20.dp, density = rule.density)
-
- rule.runOnIdle {
- items = emptyList()
- }
-
- // there are no more items so the LazyRow is zero sized
- rule.onNodeWithTag(LazyRowForTag)
- .assertWidthIsEqualTo(0.dp)
- .assertHeightIsEqualTo(0.dp)
-
- // and has no children
- rule.onNodeWithTag("1")
- .assertDoesNotExist()
- rule.onNodeWithTag("2")
- .assertDoesNotExist()
- }
-
- @Test
- fun scrollBackAndForth() {
- val items by mutableStateOf((1..20).toList())
- rule.setContent {
- LazyRowFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyRowForTag)
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- // after scroll we will display items 6-10
- rule.onNodeWithTag(LazyRowForTag)
- .scrollBy(x = 100.dp, density = rule.density)
-
- // and scroll back
- rule.onNodeWithTag(LazyRowForTag)
- .scrollBy(x = (-100).dp, density = rule.density)
-
- rule.onNodeWithTag("1")
- .assertLeftPositionIsAlmost(0.dp)
- }
-
- @Test
- fun tryToScrollBackwardWhenAlreadyOnTop() {
- val items by mutableStateOf((1..20).toList())
- rule.setContent {
- LazyRowFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyRowForTag)
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- // we already displaying the first item, so this should do nothing
- rule.onNodeWithTag(LazyRowForTag)
- .scrollBy(x = (-50).dp, density = rule.density)
-
- rule.onNodeWithTag("1")
- .assertLeftPositionIsAlmost(0.dp)
- rule.onNodeWithTag("5")
- .assertLeftPositionIsAlmost(80.dp)
- }
-
- private fun SemanticsNodeInteraction.assertLeftPositionIsAlmost(expected: Dp) {
- getUnclippedBoundsInRoot().left.assertIsEqualTo(expected, tolerance = 1.dp)
- }
-
- @Test
- fun contentOfNotStableItemsIsNotRecomposedDuringScroll() {
- val items = listOf(NotStable(1), NotStable(2))
- var firstItemRecomposed = 0
- var secondItemRecomposed = 0
- rule.setContent {
- LazyRowFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyRowForTag)
- ) {
- if (it.count == 1) {
- firstItemRecomposed++
- } else {
- secondItemRecomposed++
- }
- Spacer(Modifier.size(75.dp))
- }
- }
-
- rule.runOnIdle {
- assertThat(firstItemRecomposed).isEqualTo(1)
- assertThat(secondItemRecomposed).isEqualTo(1)
- }
-
- rule.onNodeWithTag(LazyRowForTag)
- .scrollBy(x = (50).dp, density = rule.density)
-
- rule.runOnIdle {
- assertThat(firstItemRecomposed).isEqualTo(1)
- assertThat(secondItemRecomposed).isEqualTo(1)
- }
- }
-
- @Test
- fun onlyOneMeasurePassForScrollEvent() {
- val items by mutableStateOf((1..20).toList())
- lateinit var state: LazyListState
- rule.setContent {
- state = rememberLazyListState()
- LazyRowFor(
- items = items,
- modifier = Modifier.size(100.dp),
- state = state
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- val initialMeasurePasses = state.numMeasurePasses
-
- rule.runOnIdle {
- with(rule.density) {
- state.onScroll(-110.dp.toPx())
- }
- }
-
- rule.waitForIdle()
-
- assertThat(state.numMeasurePasses).isEqualTo(initialMeasurePasses + 1)
- }
-
- @Test
- fun stateUpdatedAfterScroll() {
- val items by mutableStateOf((1..20).toList())
- lateinit var state: LazyListState
- rule.setContent {
- state = rememberLazyListState()
- LazyRowFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyRowForTag),
- state = state
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- rule.runOnIdle {
- assertThat(state.firstVisibleItemIndex).isEqualTo(0)
- assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
- }
-
- rule.onNodeWithTag(LazyRowForTag)
- .scrollBy(x = 30.dp, density = rule.density)
-
- rule.runOnIdle {
- assertThat(state.firstVisibleItemIndex).isEqualTo(1)
-
- with(rule.density) {
- // TODO(b/169232491): test scrolling doesn't appear to be scrolling exactly the right
- // number of pixels
- val expectedOffset = 10.dp.toIntPx()
- val tolerance = 2.dp.toIntPx()
- assertThat(state.firstVisibleItemScrollOffset).isEqualTo(expectedOffset, tolerance)
- }
- }
- }
-
- @Test
- fun stateUpdatedAfterScrollWithinTheSameItem() {
- val items by mutableStateOf((1..20).toList())
- lateinit var state: LazyListState
- rule.setContent {
- state = rememberLazyListState()
- LazyRowFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyRowForTag),
- state = state
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- rule.onNodeWithTag(LazyRowForTag)
- .scrollBy(x = 10.dp, density = rule.density)
-
- rule.runOnIdle {
- assertThat(state.firstVisibleItemIndex).isEqualTo(0)
- with(rule.density) {
- val expectedOffset = 10.dp.toIntPx()
- val tolerance = 2.dp.toIntPx()
- assertThat(state.firstVisibleItemScrollOffset)
- .isEqualTo(expectedOffset, tolerance)
- }
- }
- }
-
- @Test
- fun initialScrollIsApplied() {
- val items by mutableStateOf((0..20).toList())
- lateinit var state: LazyListState
- val expectedOffset = with(rule.density) { 10.dp.toIntPx() }
- rule.setContent {
- state = rememberLazyListState(2, expectedOffset)
- LazyRowFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyRowForTag),
- state = state
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- rule.runOnIdle {
- assertThat(state.firstVisibleItemIndex).isEqualTo(2)
- assertThat(state.firstVisibleItemScrollOffset).isEqualTo(expectedOffset)
- }
-
- rule.onNodeWithTag("2")
- .assertLeftPositionInRootIsEqualTo((-10).dp)
- }
-
- @Test
- fun stateIsRestored() {
- val restorationTester = StateRestorationTester(rule)
- val items by mutableStateOf((1..20).toList())
- var state: LazyListState? = null
- restorationTester.setContent {
- state = rememberLazyListState()
- LazyRowFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyRowForTag),
- state = state!!
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- rule.onNodeWithTag(LazyRowForTag)
- .scrollBy(x = 30.dp, density = rule.density)
-
- val (index, scrollOffset) = rule.runOnIdle {
- state!!.firstVisibleItemIndex to state!!.firstVisibleItemScrollOffset
- }
-
- state = null
-
- restorationTester.emulateSavedInstanceStateRestore()
-
- rule.runOnIdle {
- assertThat(state!!.firstVisibleItemIndex).isEqualTo(index)
- assertThat(state!!.firstVisibleItemScrollOffset).isEqualTo(scrollOffset)
- }
- }
-
- @Test
- fun snapToItemIndex() {
- val items by mutableStateOf((1..20).toList())
- lateinit var state: LazyListState
- rule.setContent {
- state = rememberLazyListState()
- LazyRowFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyRowForTag),
- state = state
- ) {
- Spacer(Modifier.size(20.dp).testTag("$it"))
- }
- }
-
- rule.runOnIdle {
- runBlocking {
- state.snapToItemIndex(3, 10)
- }
- assertThat(state.firstVisibleItemIndex).isEqualTo(3)
- assertThat(state.firstVisibleItemScrollOffset).isEqualTo(10)
- }
- }
-
- @Test
- fun itemsAreNotRedrawnDuringScroll() {
- val items = (0..20).toList()
- val redrawCount = Array(6) { 0 }
- rule.setContent {
- LazyRowFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyRowForTag)
- ) {
- Spacer(
- Modifier.size(20.dp)
- .drawBehind { redrawCount[it]++ }
- )
- }
- }
-
- rule.onNodeWithTag(LazyRowForTag)
- .scrollBy(x = 10.dp, density = rule.density)
-
- rule.runOnIdle {
- redrawCount.forEachIndexed { index, i ->
- Truth.assertWithMessage("Item with index $index was redrawn $i times")
- .that(i).isEqualTo(1)
- }
- }
- }
-
- @Test
- fun itemInvalidationIsNotCausingAnotherItemToRedraw() {
- val items = (0..1).toList()
- val redrawCount = Array(2) { 0 }
- var stateUsedInDrawScope by mutableStateOf(false)
- rule.setContent {
- LazyRowFor(
- items = items,
- modifier = Modifier.size(100.dp).testTag(LazyRowForTag)
- ) {
- Spacer(
- Modifier.size(50.dp)
- .drawBehind {
- redrawCount[it]++
- if (it == 1) {
- stateUsedInDrawScope.hashCode()
- }
- }
- )
- }
- }
-
- rule.runOnIdle {
- stateUsedInDrawScope = true
- }
-
- rule.runOnIdle {
- Truth.assertWithMessage("First items is not expected to be redrawn")
- .that(redrawCount[0]).isEqualTo(1)
- Truth.assertWithMessage("Second items is expected to be redrawn")
- .that(redrawCount[1]).isEqualTo(2)
- }
- }
-
- @Test
- fun notVisibleAnymoreItemNotAffectingCrossAxisSize() {
- val items = (0..1).toList()
- val itemSize = with(rule.density) { 30.toDp() }
- val itemSizeMinusOne = with(rule.density) { 29.toDp() }
- lateinit var state: LazyListState
- rule.setContent {
- LazyRowFor(
- items = items,
- state = rememberLazyListState().also { state = it },
- modifier = Modifier.width(itemSizeMinusOne).testTag(LazyRowForTag)
- ) {
- Spacer(
- if (it == 0) {
- Modifier.height(30.dp).width(itemSizeMinusOne)
- } else {
- Modifier.height(20.dp).width(itemSize)
- }
- )
- }
- }
-
- state.scrollBy(itemSize)
-
- rule.onNodeWithTag(LazyRowForTag)
- .assertHeightIsEqualTo(20.dp)
- }
-
- @Test
- fun itemStillVisibleAfterOverscrollIsAffectingCrossAxisSize() {
- val items = (0..2).toList()
- val itemSize = with(rule.density) { 30.toDp() }
- lateinit var state: LazyListState
- rule.setContent {
- LazyRowFor(
- items = items,
- state = rememberLazyListState().also { state = it },
- modifier = Modifier.width(itemSize * 1.75f).testTag(LazyRowForTag)
- ) {
- Spacer(
- if (it == 0) {
- Modifier.height(30.dp).width(itemSize / 2)
- } else if (it == 1) {
- Modifier.height(20.dp).width(itemSize / 2)
- } else {
- Modifier.height(20.dp).width(itemSize)
- }
- )
- }
- }
-
- state.scrollBy(itemSize)
-
- rule.onNodeWithTag(LazyRowForTag)
- .assertHeightIsEqualTo(30.dp)
- }
-
- private fun LazyListState.scrollBy(offset: Dp) {
- runBlocking {
- smoothScrollBy(with(rule.density) { offset.toIntPx().toFloat() }, snap())
- }
- }
-}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt
index fe720d7..1ab378a 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyRowTest.kt
@@ -16,17 +16,44 @@
package androidx.compose.foundation.lazy
+import androidx.compose.animation.core.snap
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.preferredSize
import androidx.compose.foundation.layout.preferredWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Providers
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.platform.AmbientLayoutDirection
import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.assertHeightIsEqualTo
import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEqualTo
+import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.StateRestorationTester
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -34,83 +61,12 @@
@MediumTest
@RunWith(AndroidJUnit4::class)
class LazyRowTest {
- private val LazyRowTag = "LazyRowTag"
+ private val LazyListTag = "LazyListTag"
@get:Rule
val rule = createComposeRule()
@Test
- fun lazyRowShowsItem() {
- val itemTestTag = "itemTestTag"
-
- rule.setContent {
- LazyRow {
- item {
- Spacer(
- Modifier.preferredWidth(10.dp).fillParentMaxHeight().testTag(itemTestTag)
- )
- }
- }
- }
-
- rule.onNodeWithTag(itemTestTag)
- .assertIsDisplayed()
- }
-
- @Test
- fun lazyRowShowsItems() {
- val items = (1..4).map { it.toString() }
-
- rule.setContent {
- LazyRow(Modifier.preferredWidth(200.dp)) {
- items(items) {
- Spacer(Modifier.preferredWidth(101.dp).fillParentMaxHeight().testTag(it))
- }
- }
- }
-
- rule.onNodeWithTag("1")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("2")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("3")
- .assertDoesNotExist()
-
- rule.onNodeWithTag("4")
- .assertDoesNotExist()
- }
-
- @Test
- fun lazyRowShowsIndexedItems() {
- val items = (1..4).map { it.toString() }
-
- rule.setContent {
- LazyRow(Modifier.preferredWidth(200.dp)) {
- itemsIndexed(items) { index, item ->
- Spacer(
- Modifier.preferredWidth(101.dp).fillParentMaxHeight()
- .testTag("$index-$item")
- )
- }
- }
- }
-
- rule.onNodeWithTag("0-1")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("1-2")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("2-3")
- .assertDoesNotExist()
-
- rule.onNodeWithTag("3-4")
- .assertDoesNotExist()
- }
-
- @Test
fun lazyRowShowsCombinedItems() {
val itemTestTag = "itemTestTag"
val items = listOf(1, 2).map { it.toString() }
@@ -155,59 +111,6 @@
}
@Test
- fun lazyRowShowsItemsOnScroll() {
- val items = (1..4).map { it.toString() }
-
- rule.setContent {
- LazyRow(Modifier.preferredWidth(200.dp).testTag(LazyRowTag)) {
- items(items) {
- Spacer(Modifier.preferredWidth(101.dp).fillParentMaxHeight().testTag(it))
- }
- }
- }
-
- rule.onNodeWithTag(LazyRowTag)
- .scrollBy(x = 50.dp, density = rule.density)
-
- rule.onNodeWithTag("1")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("2")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("3")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("4")
- .assertDoesNotExist()
- }
-
- @Test
- fun lazyRowScrollHidesItem() {
- val items = (1..4).map { it.toString() }
-
- rule.setContent {
- LazyRow(Modifier.preferredWidth(200.dp).testTag(LazyRowTag)) {
- items(items) {
- Spacer(Modifier.preferredWidth(101.dp).fillParentMaxHeight().testTag(it))
- }
- }
- }
-
- rule.onNodeWithTag(LazyRowTag)
- .scrollBy(x = 103.dp, density = rule.density)
-
- rule.onNodeWithTag("1")
- .assertDoesNotExist()
-
- rule.onNodeWithTag("2")
- .assertIsDisplayed()
-
- rule.onNodeWithTag("3")
- .assertIsDisplayed()
- }
-
- @Test
fun lazyRowAllowEmptyListItems() {
val itemTag = "itemTag"
@@ -253,4 +156,838 @@
rule.onNodeWithTag("3")
.assertDoesNotExist()
}
-}
\ No newline at end of file
+
+ @Test
+ fun lazyRowOnlyVisibleItemsAdded() {
+ val items = (1..4).map { it.toString() }
+
+ rule.setContent {
+ Box(Modifier.preferredWidth(200.dp)) {
+ LazyRow {
+ items(items) {
+ Spacer(Modifier.preferredWidth(101.dp).fillParentMaxHeight().testTag(it))
+ }
+ }
+ }
+ }
+
+ rule.onNodeWithTag("1")
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag("2")
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag("3")
+ .assertDoesNotExist()
+
+ rule.onNodeWithTag("4")
+ .assertDoesNotExist()
+ }
+
+ @Test
+ fun lazyRowScrollToShowItems123() {
+ val items = (1..4).map { it.toString() }
+
+ rule.setContent {
+ Box(Modifier.preferredWidth(200.dp)) {
+ LazyRow(Modifier.testTag(LazyListTag)) {
+ items(items) {
+ Spacer(Modifier.preferredWidth(101.dp).fillParentMaxHeight().testTag(it))
+ }
+ }
+ }
+ }
+
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(x = 50.dp, density = rule.density)
+
+ rule.onNodeWithTag("1")
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag("2")
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag("3")
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag("4")
+ .assertDoesNotExist()
+ }
+
+ @Test
+ fun lazyRowScrollToHideFirstItem() {
+ val items = (1..4).map { it.toString() }
+
+ rule.setContent {
+ Box(Modifier.preferredWidth(200.dp)) {
+ LazyRow(Modifier.testTag(LazyListTag)) {
+ items(items) {
+ Spacer(Modifier.preferredWidth(101.dp).fillParentMaxHeight().testTag(it))
+ }
+ }
+ }
+ }
+
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(x = 102.dp, density = rule.density)
+
+ rule.onNodeWithTag("1")
+ .assertDoesNotExist()
+
+ rule.onNodeWithTag("2")
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag("3")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun lazyRowScrollToShowItems234() {
+ val items = (1..4).map { it.toString() }
+
+ rule.setContent {
+ Box(Modifier.preferredWidth(200.dp)) {
+ LazyRow(Modifier.testTag(LazyListTag)) {
+ items(items) {
+ Spacer(Modifier.preferredWidth(101.dp).fillParentMaxHeight().testTag(it))
+ }
+ }
+ }
+ }
+
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(x = 150.dp, density = rule.density)
+
+ rule.onNodeWithTag("1")
+ .assertDoesNotExist()
+
+ rule.onNodeWithTag("2")
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag("3")
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag("4")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun lazyRowWrapsContent() = with(rule.density) {
+ val itemInsideLazyRow = "itemInsideLazyRow"
+ val itemOutsideLazyRow = "itemOutsideLazyRow"
+ var sameSizeItems by mutableStateOf(true)
+
+ rule.setContent {
+ Column {
+ LazyRow(Modifier.testTag(LazyListTag)) {
+ items(listOf(1, 2)) {
+ if (it == 1) {
+ Spacer(Modifier.preferredSize(50.dp).testTag(itemInsideLazyRow))
+ } else {
+ Spacer(Modifier.preferredSize(if (sameSizeItems) 50.dp else 70.dp))
+ }
+ }
+ }
+ Spacer(Modifier.preferredSize(50.dp).testTag(itemOutsideLazyRow))
+ }
+ }
+
+ rule.onNodeWithTag(itemInsideLazyRow)
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag(itemOutsideLazyRow)
+ .assertIsDisplayed()
+
+ var lazyRowBounds = rule.onNodeWithTag(LazyListTag)
+ .getUnclippedBoundsInRoot()
+
+ assertThat(lazyRowBounds.left.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
+ assertThat(lazyRowBounds.right.toIntPx()).isWithin1PixelFrom(100.dp.toIntPx())
+ assertThat(lazyRowBounds.top.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
+ assertThat(lazyRowBounds.bottom.toIntPx()).isWithin1PixelFrom(50.dp.toIntPx())
+
+ rule.runOnIdle {
+ sameSizeItems = false
+ }
+
+ rule.waitForIdle()
+
+ rule.onNodeWithTag(itemInsideLazyRow)
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag(itemOutsideLazyRow)
+ .assertIsDisplayed()
+
+ lazyRowBounds = rule.onNodeWithTag(LazyListTag)
+ .getUnclippedBoundsInRoot()
+
+ assertThat(lazyRowBounds.left.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
+ assertThat(lazyRowBounds.right.toIntPx()).isWithin1PixelFrom(120.dp.toIntPx())
+ assertThat(lazyRowBounds.top.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
+ assertThat(lazyRowBounds.bottom.toIntPx()).isWithin1PixelFrom(70.dp.toIntPx())
+ }
+
+ private val firstItemTag = "firstItemTag"
+ private val secondItemTag = "secondItemTag"
+
+ private fun prepareLazyRowForAlignment(verticalGravity: Alignment.Vertical) {
+ rule.setContent {
+ LazyRow(
+ Modifier.testTag(LazyListTag).height(100.dp),
+ verticalAlignment = verticalGravity
+ ) {
+ items(listOf(1, 2)) {
+ if (it == 1) {
+ Spacer(Modifier.preferredSize(50.dp).testTag(firstItemTag))
+ } else {
+ Spacer(Modifier.preferredSize(70.dp).testTag(secondItemTag))
+ }
+ }
+ }
+ }
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertIsDisplayed()
+
+ rule.onNodeWithTag(secondItemTag)
+ .assertIsDisplayed()
+
+ val lazyRowBounds = rule.onNodeWithTag(LazyListTag)
+ .getUnclippedBoundsInRoot()
+
+ with(rule.density) {
+ // Verify the height of the row
+ assertThat(lazyRowBounds.top.toIntPx()).isWithin1PixelFrom(0.dp.toIntPx())
+ assertThat(lazyRowBounds.bottom.toIntPx()).isWithin1PixelFrom(100.dp.toIntPx())
+ }
+ }
+
+ @Test
+ fun lazyRowAlignmentCenterVertically() {
+ prepareLazyRowForAlignment(Alignment.CenterVertically)
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertPositionInRootIsEqualTo(0.dp, 25.dp)
+
+ rule.onNodeWithTag(secondItemTag)
+ .assertPositionInRootIsEqualTo(50.dp, 15.dp)
+ }
+
+ @Test
+ fun lazyRowAlignmentTop() {
+ prepareLazyRowForAlignment(Alignment.Top)
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+
+ rule.onNodeWithTag(secondItemTag)
+ .assertPositionInRootIsEqualTo(50.dp, 0.dp)
+ }
+
+ @Test
+ fun lazyRowAlignmentBottom() {
+ prepareLazyRowForAlignment(Alignment.Bottom)
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertPositionInRootIsEqualTo(0.dp, 50.dp)
+
+ rule.onNodeWithTag(secondItemTag)
+ .assertPositionInRootIsEqualTo(50.dp, 30.dp)
+ }
+
+ @Test
+ fun itemFillingParentWidth() {
+ rule.setContent {
+ LazyRow(Modifier.size(width = 100.dp, height = 150.dp)) {
+ items(listOf(0)) {
+ Spacer(Modifier.fillParentMaxWidth().height(50.dp).testTag(firstItemTag))
+ }
+ }
+ }
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertWidthIsEqualTo(100.dp)
+ .assertHeightIsEqualTo(50.dp)
+ }
+
+ @Test
+ fun itemFillingParentHeight() {
+ rule.setContent {
+ LazyRow(Modifier.size(width = 100.dp, height = 150.dp)) {
+ items(listOf(0)) {
+ Spacer(Modifier.width(50.dp).fillParentMaxHeight().testTag(firstItemTag))
+ }
+ }
+ }
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertWidthIsEqualTo(50.dp)
+ .assertHeightIsEqualTo(150.dp)
+ }
+
+ @Test
+ fun itemFillingParentSize() {
+ rule.setContent {
+ LazyRow(Modifier.size(width = 100.dp, height = 150.dp)) {
+ items(listOf(0)) {
+ Spacer(Modifier.fillParentMaxSize().testTag(firstItemTag))
+ }
+ }
+ }
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertWidthIsEqualTo(100.dp)
+ .assertHeightIsEqualTo(150.dp)
+ }
+
+ @Test
+ fun itemFillingParentWidthFraction() {
+ rule.setContent {
+ LazyRow(Modifier.size(width = 100.dp, height = 150.dp)) {
+ items(listOf(0)) {
+ Spacer(Modifier.fillParentMaxWidth(0.7f).height(50.dp).testTag(firstItemTag))
+ }
+ }
+ }
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertWidthIsEqualTo(70.dp)
+ .assertHeightIsEqualTo(50.dp)
+ }
+
+ @Test
+ fun itemFillingParentHeightFraction() {
+ rule.setContent {
+ LazyRow(Modifier.size(width = 100.dp, height = 150.dp)) {
+ items(listOf(0)) {
+ Spacer(Modifier.width(50.dp).fillParentMaxHeight(0.3f).testTag(firstItemTag))
+ }
+ }
+ }
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertWidthIsEqualTo(50.dp)
+ .assertHeightIsEqualTo(45.dp)
+ }
+
+ @Test
+ fun itemFillingParentSizeFraction() {
+ rule.setContent {
+ LazyRow(Modifier.size(width = 100.dp, height = 150.dp)) {
+ items(listOf(0)) {
+ Spacer(Modifier.fillParentMaxSize(0.5f).testTag(firstItemTag))
+ }
+ }
+ }
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertWidthIsEqualTo(50.dp)
+ .assertHeightIsEqualTo(75.dp)
+ }
+
+ @Test
+ fun itemFillingParentSizeParentResized() {
+ var parentSize by mutableStateOf(100.dp)
+ rule.setContent {
+ LazyRow(Modifier.size(parentSize)) {
+ items(listOf(0)) {
+ Spacer(Modifier.fillParentMaxSize().testTag(firstItemTag))
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ parentSize = 150.dp
+ }
+
+ rule.onNodeWithTag(firstItemTag)
+ .assertWidthIsEqualTo(150.dp)
+ .assertHeightIsEqualTo(150.dp)
+ }
+
+ @Test
+ fun scrollsLeftInRtl() {
+ val items = (1..4).map { it.toString() }
+
+ rule.setContent {
+ Providers(AmbientLayoutDirection provides LayoutDirection.Rtl) {
+ Box(Modifier.preferredWidth(100.dp)) {
+ LazyRow(Modifier.testTag(LazyListTag)) {
+ items(items) {
+ Spacer(
+ Modifier.preferredWidth(101.dp).fillParentMaxHeight().testTag(it)
+ )
+ }
+ }
+ }
+ }
+ }
+
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(x = (-150).dp, density = rule.density)
+
+ rule.onNodeWithTag("1")
+ .assertDoesNotExist()
+
+ rule.onNodeWithTag("2")
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun whenNotAnymoreAvailableItemWasDisplayed() {
+ var items by mutableStateOf((1..30).toList())
+ rule.setContent {
+ LazyRow(Modifier.size(100.dp).testTag(LazyListTag)) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ // after scroll we will display items 16-20
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(x = 300.dp, density = rule.density)
+
+ rule.runOnIdle {
+ items = (1..10).toList()
+ }
+
+ // there is no item 16 anymore so we will just display the last items 6-10
+ rule.onNodeWithTag("6")
+ .assertLeftPositionIsAlmost(0.dp)
+ }
+
+ @Test
+ fun whenFewDisplayedItemsWereRemoved() {
+ var items by mutableStateOf((1..10).toList())
+ rule.setContent {
+ LazyRow(Modifier.size(100.dp).testTag(LazyListTag)) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ // after scroll we will display items 6-10
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(x = 100.dp, density = rule.density)
+
+ rule.runOnIdle {
+ items = (1..8).toList()
+ }
+
+ // there are no more items 9 and 10, so we have to scroll back
+ rule.onNodeWithTag("4")
+ .assertLeftPositionIsAlmost(0.dp)
+ }
+
+ @Test
+ fun whenItemsBecameEmpty() {
+ var items by mutableStateOf((1..10).toList())
+ rule.setContent {
+ LazyRow(Modifier.sizeIn(maxHeight = 100.dp).testTag(LazyListTag)) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ // after scroll we will display items 2-6
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(x = 20.dp, density = rule.density)
+
+ rule.runOnIdle {
+ items = emptyList()
+ }
+
+ // there are no more items so the LazyRow is zero sized
+ rule.onNodeWithTag(LazyListTag)
+ .assertWidthIsEqualTo(0.dp)
+ .assertHeightIsEqualTo(0.dp)
+
+ // and has no children
+ rule.onNodeWithTag("1")
+ .assertDoesNotExist()
+ rule.onNodeWithTag("2")
+ .assertDoesNotExist()
+ }
+
+ @Test
+ fun scrollBackAndForth() {
+ val items by mutableStateOf((1..20).toList())
+ rule.setContent {
+ LazyRow(Modifier.size(100.dp).testTag(LazyListTag)) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ // after scroll we will display items 6-10
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(x = 100.dp, density = rule.density)
+
+ // and scroll back
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(x = (-100).dp, density = rule.density)
+
+ rule.onNodeWithTag("1")
+ .assertLeftPositionIsAlmost(0.dp)
+ }
+
+ @Test
+ fun tryToScrollBackwardWhenAlreadyOnTop() {
+ val items by mutableStateOf((1..20).toList())
+ rule.setContent {
+ LazyRow(Modifier.size(100.dp).testTag(LazyListTag)) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ // we already displaying the first item, so this should do nothing
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(x = (-50).dp, density = rule.density)
+
+ rule.onNodeWithTag("1")
+ .assertLeftPositionIsAlmost(0.dp)
+ rule.onNodeWithTag("5")
+ .assertLeftPositionIsAlmost(80.dp)
+ }
+
+ private fun SemanticsNodeInteraction.assertLeftPositionIsAlmost(expected: Dp) {
+ getUnclippedBoundsInRoot().left.assertIsEqualTo(expected, tolerance = 1.dp)
+ }
+
+ @Test
+ fun contentOfNotStableItemsIsNotRecomposedDuringScroll() {
+ val items = listOf(NotStable(1), NotStable(2))
+ var firstItemRecomposed = 0
+ var secondItemRecomposed = 0
+ rule.setContent {
+ LazyRow(Modifier.size(100.dp).testTag(LazyListTag)) {
+ items(items) {
+ if (it.count == 1) {
+ firstItemRecomposed++
+ } else {
+ secondItemRecomposed++
+ }
+ Spacer(Modifier.size(75.dp))
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertThat(firstItemRecomposed).isEqualTo(1)
+ assertThat(secondItemRecomposed).isEqualTo(1)
+ }
+
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(x = (50).dp, density = rule.density)
+
+ rule.runOnIdle {
+ assertThat(firstItemRecomposed).isEqualTo(1)
+ assertThat(secondItemRecomposed).isEqualTo(1)
+ }
+ }
+
+ @Test
+ fun onlyOneMeasurePassForScrollEvent() {
+ val items by mutableStateOf((1..20).toList())
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState()
+ LazyRow(Modifier.size(100.dp), state = state) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ val initialMeasurePasses = state.numMeasurePasses
+
+ rule.runOnIdle {
+ with(rule.density) {
+ state.onScroll(-110.dp.toPx())
+ }
+ }
+
+ rule.waitForIdle()
+
+ assertThat(state.numMeasurePasses).isEqualTo(initialMeasurePasses + 1)
+ }
+
+ @Test
+ fun stateUpdatedAfterScroll() {
+ val items by mutableStateOf((1..20).toList())
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState()
+ LazyRow(
+ Modifier.size(100.dp).testTag(LazyListTag),
+ state = state
+ ) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+ assertThat(state.firstVisibleItemScrollOffset).isEqualTo(0)
+ }
+
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(x = 30.dp, density = rule.density)
+
+ rule.runOnIdle {
+ assertThat(state.firstVisibleItemIndex).isEqualTo(1)
+
+ with(rule.density) {
+ // TODO(b/169232491): test scrolling doesn't appear to be scrolling exactly the right
+ // number of pixels
+ val expectedOffset = 10.dp.toIntPx()
+ val tolerance = 2.dp.toIntPx()
+ assertThat(state.firstVisibleItemScrollOffset).isEqualTo(expectedOffset, tolerance)
+ }
+ }
+ }
+
+ @Test
+ fun stateUpdatedAfterScrollWithinTheSameItem() {
+ val items by mutableStateOf((1..20).toList())
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState()
+ LazyRow(
+ Modifier.size(100.dp).testTag(LazyListTag),
+ state = state
+ ) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(x = 10.dp, density = rule.density)
+
+ rule.runOnIdle {
+ assertThat(state.firstVisibleItemIndex).isEqualTo(0)
+ with(rule.density) {
+ val expectedOffset = 10.dp.toIntPx()
+ val tolerance = 2.dp.toIntPx()
+ assertThat(state.firstVisibleItemScrollOffset)
+ .isEqualTo(expectedOffset, tolerance)
+ }
+ }
+ }
+
+ @Test
+ fun initialScrollIsApplied() {
+ val items by mutableStateOf((0..20).toList())
+ lateinit var state: LazyListState
+ val expectedOffset = with(rule.density) { 10.dp.toIntPx() }
+ rule.setContent {
+ state = rememberLazyListState(2, expectedOffset)
+ LazyRow(Modifier.size(100.dp).testTag(LazyListTag), state = state) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ assertThat(state.firstVisibleItemIndex).isEqualTo(2)
+ assertThat(state.firstVisibleItemScrollOffset).isEqualTo(expectedOffset)
+ }
+
+ rule.onNodeWithTag("2")
+ .assertLeftPositionInRootIsEqualTo((-10).dp)
+ }
+
+ @Test
+ fun stateIsRestored() {
+ val restorationTester = StateRestorationTester(rule)
+ val items by mutableStateOf((1..20).toList())
+ var state: LazyListState? = null
+ restorationTester.setContent {
+ state = rememberLazyListState()
+ LazyRow(
+ Modifier.size(100.dp).testTag(LazyListTag),
+ state = state!!
+ ) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(x = 30.dp, density = rule.density)
+
+ val (index, scrollOffset) = rule.runOnIdle {
+ state!!.firstVisibleItemIndex to state!!.firstVisibleItemScrollOffset
+ }
+
+ state = null
+
+ restorationTester.emulateSavedInstanceStateRestore()
+
+ rule.runOnIdle {
+ assertThat(state!!.firstVisibleItemIndex).isEqualTo(index)
+ assertThat(state!!.firstVisibleItemScrollOffset).isEqualTo(scrollOffset)
+ }
+ }
+
+ @Test
+ fun snapToItemIndex() {
+ val items by mutableStateOf((1..20).toList())
+ lateinit var state: LazyListState
+ rule.setContent {
+ state = rememberLazyListState()
+ LazyRow(
+ Modifier.size(100.dp).testTag(LazyListTag),
+ state = state
+ ) {
+ items(items) {
+ Spacer(Modifier.size(20.dp).testTag("$it"))
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ runBlocking {
+ state.snapToItemIndex(3, 10)
+ }
+ assertThat(state.firstVisibleItemIndex).isEqualTo(3)
+ assertThat(state.firstVisibleItemScrollOffset).isEqualTo(10)
+ }
+ }
+
+ @Test
+ fun itemsAreNotRedrawnDuringScroll() {
+ val items = (0..20).toList()
+ val redrawCount = Array(6) { 0 }
+ rule.setContent {
+ LazyRow(Modifier.size(100.dp).testTag(LazyListTag)) {
+ items(items) {
+ Spacer(
+ Modifier.size(20.dp)
+ .drawBehind { redrawCount[it]++ }
+ )
+ }
+ }
+ }
+
+ rule.onNodeWithTag(LazyListTag)
+ .scrollBy(x = 10.dp, density = rule.density)
+
+ rule.runOnIdle {
+ redrawCount.forEachIndexed { index, i ->
+ Truth.assertWithMessage("Item with index $index was redrawn $i times")
+ .that(i).isEqualTo(1)
+ }
+ }
+ }
+
+ @Test
+ fun itemInvalidationIsNotCausingAnotherItemToRedraw() {
+ val items = (0..1).toList()
+ val redrawCount = Array(2) { 0 }
+ var stateUsedInDrawScope by mutableStateOf(false)
+ rule.setContent {
+ LazyRow(Modifier.size(100.dp).testTag(LazyListTag)) {
+ items(items) {
+ Spacer(
+ Modifier.size(50.dp)
+ .drawBehind {
+ redrawCount[it]++
+ if (it == 1) {
+ stateUsedInDrawScope.hashCode()
+ }
+ }
+ )
+ }
+ }
+ }
+
+ rule.runOnIdle {
+ stateUsedInDrawScope = true
+ }
+
+ rule.runOnIdle {
+ Truth.assertWithMessage("First items is not expected to be redrawn")
+ .that(redrawCount[0]).isEqualTo(1)
+ Truth.assertWithMessage("Second items is expected to be redrawn")
+ .that(redrawCount[1]).isEqualTo(2)
+ }
+ }
+
+ @Test
+ fun notVisibleAnymoreItemNotAffectingCrossAxisSize() {
+ val items = (0..1).toList()
+ val itemSize = with(rule.density) { 30.toDp() }
+ val itemSizeMinusOne = with(rule.density) { 29.toDp() }
+ lateinit var state: LazyListState
+ rule.setContent {
+ LazyRow(
+ Modifier.width(itemSizeMinusOne).testTag(LazyListTag),
+ state = rememberLazyListState().also { state = it }
+ ) {
+ items(items) {
+ Spacer(
+ if (it == 0) {
+ Modifier.height(30.dp).width(itemSizeMinusOne)
+ } else {
+ Modifier.height(20.dp).width(itemSize)
+ }
+ )
+ }
+ }
+ }
+
+ state.scrollBy(itemSize)
+
+ rule.onNodeWithTag(LazyListTag)
+ .assertHeightIsEqualTo(20.dp)
+ }
+
+ @Test
+ fun itemStillVisibleAfterOverscrollIsAffectingCrossAxisSize() {
+ val items = (0..2).toList()
+ val itemSize = with(rule.density) { 30.toDp() }
+ lateinit var state: LazyListState
+ rule.setContent {
+ LazyRow(
+ Modifier.width(itemSize * 1.75f).testTag(LazyListTag),
+ state = rememberLazyListState().also { state = it }
+ ) {
+ items(items) {
+ Spacer(
+ if (it == 0) {
+ Modifier.height(30.dp).width(itemSize / 2)
+ } else if (it == 1) {
+ Modifier.height(20.dp).width(itemSize / 2)
+ } else {
+ Modifier.height(20.dp).width(itemSize)
+ }
+ )
+ }
+ }
+ }
+
+ state.scrollBy(itemSize)
+
+ rule.onNodeWithTag(LazyListTag)
+ .assertHeightIsEqualTo(30.dp)
+ }
+
+ private fun LazyListState.scrollBy(offset: Dp) {
+ runBlocking {
+ smoothScrollBy(with(rule.density) { offset.toIntPx().toFloat() }, snap())
+ }
+ }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyScrollTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyScrollTest.kt
index 8f60ce8c..d216574 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyScrollTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyScrollTest.kt
@@ -90,12 +90,16 @@
@Composable
private fun TestContent() {
if (vertical) {
- LazyColumnFor(items, Modifier.preferredHeight(300.dp), state) {
- ItemContent()
+ LazyColumn(Modifier.preferredHeight(300.dp), state) {
+ items(items) {
+ ItemContent()
+ }
}
} else {
- LazyRowFor(items, Modifier.preferredWidth(300.dp), state) {
- ItemContent()
+ LazyRow(Modifier.preferredWidth(300.dp), state) {
+ items(items) {
+ ItemContent()
+ }
}
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
index c30a7c6..aee281c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Scroll.kt
@@ -478,7 +478,7 @@
if (isVertical) {
check(maxHeight != Constraints.Infinity) {
"Nesting scrollable in the same direction layouts like ScrollableContainer and " +
- "LazyColumnFor is not allowed. If you want to add a header before the list of" +
+ "LazyColumn is not allowed. If you want to add a header before the list of" +
" items please take a look on LazyColumn component which has a DSL api which" +
" allows to first add a header via item() function and then the list of " +
"items via items()."
@@ -486,7 +486,7 @@
} else {
check(maxWidth != Constraints.Infinity) {
"Nesting scrollable in the same direction layouts like ScrollableRow and " +
- "LazyRowFor is not allowed. If you want to add a header before the list of " +
+ "LazyRow is not allowed. If you want to add a header before the list of " +
"items please take a look on LazyRow component which has a DSL api which " +
"allows to first add a fixed element via item() function and then the " +
"list of items via items()."
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyFor.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyFor.kt
index 2986425..89b9186 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyFor.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyFor.kt
@@ -31,8 +31,6 @@
* See [LazyColumnForIndexed] if you need to have both item and index params in [itemContent].
* See [LazyRowFor] if you are looking for a horizontally scrolling version.
*
- * @sample androidx.compose.foundation.samples.LazyColumnForSample
- *
* @param items the backing list of data to display
* @param modifier the modifier to apply to this layout
* @param state the state object to be used to control or observe the list's state
@@ -55,6 +53,14 @@
*/
@OptIn(InternalLayoutApi::class)
@Composable
+@Deprecated(
+ "Use LazyColumn instead",
+ ReplaceWith(
+ "LazyColumn(modifier, state, contentPadding, horizontalAlignment = " +
+ "horizontalAlignment) { \n items(items, itemContent) \n }",
+ "androidx.compose.foundation.lazy.LazyColumn"
+ )
+)
fun <T> LazyColumnFor(
items: List<T>,
modifier: Modifier = Modifier,
@@ -86,8 +92,6 @@
*
* See [LazyRowForIndexed] if you are looking for a horizontally scrolling version.
*
- * @sample androidx.compose.foundation.samples.LazyColumnForIndexedSample
- *
* @param items the backing list of data to display
* @param modifier the modifier to apply to this layout
* @param state the state object to be used to control or observe the list's state
@@ -111,6 +115,14 @@
*/
@OptIn(InternalLayoutApi::class)
@Composable
+@Deprecated(
+ "Use LazyColumn instead",
+ ReplaceWith(
+ "LazyColumn(modifier, state, contentPadding, horizontalAlignment = " +
+ "horizontalAlignment) { \n itemsIndexed(items, itemContent) \n }",
+ "androidx.compose.foundation.lazy.LazyColumn"
+ )
+)
fun <T> LazyColumnForIndexed(
items: List<T>,
modifier: Modifier = Modifier,
@@ -140,8 +152,6 @@
* See [LazyRowForIndexed] if you need to have both item and index params in [itemContent].
* See [LazyColumnFor] if you are looking for a vertically scrolling version.
*
- * @sample androidx.compose.foundation.samples.LazyRowForSample
- *
* @param items the backing list of data to display.
* @param modifier the modifier to apply to this layout.
* @param state the state object to be used to control or observe the list's state.
@@ -164,6 +174,14 @@
*/
@OptIn(InternalLayoutApi::class)
@Composable
+@Deprecated(
+ "Use LazyRow instead",
+ ReplaceWith(
+ "LazyRow(modifier, state, contentPadding, verticalAlignment = " +
+ "verticalAlignment) { \n items(items, itemContent) \n }",
+ "androidx.compose.foundation.lazy.LazyColumn"
+ )
+)
fun <T> LazyRowFor(
items: List<T>,
modifier: Modifier = Modifier,
@@ -194,8 +212,6 @@
*
* See [LazyColumnForIndexed] if you are looking for a vertically scrolling version.
*
- * @sample androidx.compose.foundation.samples.LazyRowForIndexedSample
- *
* @param items the backing list of data to display.
* @param modifier the modifier to apply to this layout.
* @param state the state object to be used to control or observe the list's state.
@@ -219,6 +235,14 @@
*/
@OptIn(InternalLayoutApi::class)
@Composable
+@Deprecated(
+ "Use LazyRow instead",
+ ReplaceWith(
+ "LazyRow(modifier, state, contentPadding, verticalAlignment = " +
+ "verticalAlignment) { \n itemsIndexed(items, itemContent) \n }",
+ "androidx.compose.foundation.lazy.LazyColumn"
+ )
+)
fun <T> LazyRowForIndexed(
items: List<T>,
modifier: Modifier = Modifier,
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt
index eb74118..2bcbed5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt
@@ -29,7 +29,7 @@
/**
* The DSL implementation of a lazy grid layout. It composes only visible rows of the grid.
- * This API is not stable, please consider using stable components like [LazyColumnFor] and [Row]
+ * This API is not stable, please consider using stable components like [LazyColumn] and [Row]
* to achieve the same result.
*
* @param columns a fixed number of columns of the grid
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.kt
index 96e0375..0384857 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Scrollbar.kt
@@ -88,7 +88,7 @@
/**
* Vertical scrollbar that can be attached to some scrollable
- * component (ScrollableColumn, LazyColumnFor) and share common state with it.
+ * component (ScrollableColumn, LazyColumn) and share common state with it.
*
* Can be placed independently.
*
@@ -128,7 +128,7 @@
/**
* Horizontal scrollbar that can be attached to some scrollable
- * component (ScrollableRow, LazyRowFor) and share common state with it.
+ * component (ScrollableRow, LazyRow) and share common state with it.
*
* Can be placed independently.
*
diff --git a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
index d2ed8a5..c2f7983 100644
--- a/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
+++ b/compose/foundation/foundation/src/desktopTest/kotlin/androidx/compose/foundation/ScrollbarTest.kt
@@ -22,7 +22,7 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.LazyColumnFor
+import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
@@ -436,12 +436,13 @@
scrollbarWidth: Dp,
) = withTestEnvironment {
Box(Modifier.size(size)) {
- LazyColumnFor(
- (0 until childCount).toList(),
+ LazyColumn(
Modifier.fillMaxSize().testTag("column"),
state
) {
- Box(Modifier.size(childSize).testTag("box$it"))
+ items((0 until childCount).toList()) {
+ Box(Modifier.size(childSize).testTag("box$it"))
+ }
}
VerticalScrollbar(
diff --git a/compose/integration-tests/benchmark/src/androidTest/java/androidx/compose/ui/lazy/LazyListScrollingBenchmark.kt b/compose/integration-tests/benchmark/src/androidTest/java/androidx/compose/ui/lazy/LazyListScrollingBenchmark.kt
index 4c14493..8e652ac 100644
--- a/compose/integration-tests/benchmark/src/androidTest/java/androidx/compose/ui/lazy/LazyListScrollingBenchmark.kt
+++ b/compose/integration-tests/benchmark/src/androidTest/java/androidx/compose/ui/lazy/LazyListScrollingBenchmark.kt
@@ -24,11 +24,7 @@
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.LazyColumnFor
-import androidx.compose.foundation.lazy.LazyColumnForIndexed
import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.foundation.lazy.LazyRowFor
-import androidx.compose.foundation.lazy.LazyRowForIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.emptyContent
@@ -92,13 +88,9 @@
LazyColumnWithItemAndItems,
LazyColumnWithItems,
LazyColumnWithItemsIndexed,
- LazyColumnFor,
- LazyColumnForIndexed,
LazyRowWithItemAndItems,
LazyRowWithItems,
- LazyRowWithItemsIndexed,
- LazyRowFor,
- LazyRowForIndexed
+ LazyRowWithItemsIndexed
)
}
}
@@ -147,26 +139,6 @@
}
}
-private val LazyColumnFor = LazyListScrollingTestCase("LazyColumnFor") {
- LazyColumnFor(items, modifier = Modifier.height(400.dp).fillMaxWidth()) {
- if (it.index == 0) {
- RemeasurableItem()
- } else {
- RegularItem()
- }
- }
-}
-
-private val LazyColumnForIndexed = LazyListScrollingTestCase("LazyColumnForIndexed") {
- LazyColumnForIndexed(items, modifier = Modifier.height(400.dp).fillMaxWidth()) { index, _ ->
- if (index == 0) {
- RemeasurableItem()
- } else {
- RegularItem()
- }
- }
-}
-
private val LazyRowWithItemAndItems = LazyListScrollingTestCase("LazyRowWithItemAndItems") {
LazyRow(modifier = Modifier.width(400.dp).fillMaxHeight()) {
item {
@@ -202,26 +174,6 @@
}
}
-private val LazyRowFor = LazyListScrollingTestCase("LazyRowFor") {
- LazyRowFor(items, modifier = Modifier.width(400.dp).fillMaxHeight()) {
- if (it.index == 0) {
- RemeasurableItem()
- } else {
- RegularItem()
- }
- }
-}
-
-private val LazyRowForIndexed = LazyListScrollingTestCase("LazyRowForIndexed") {
- LazyRowForIndexed(items, modifier = Modifier.width(400.dp).fillMaxHeight()) { index, _ ->
- if (index == 0) {
- RemeasurableItem()
- } else {
- RegularItem()
- }
- }
-}
-
// TODO(b/169852102 use existing public constructs instead)
private fun ComposeBenchmarkRule.toggleStateBenchmarkMeasure(
caseFactory: () -> ListRemeasureTestCase
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/layout/Layout.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/layout/Layout.kt
index dcab8ad..07e9583 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/layout/Layout.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/layout/Layout.kt
@@ -29,7 +29,7 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyColumnFor
+import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Button
import androidx.compose.material.Scaffold
import androidx.compose.material.Surface
@@ -137,8 +137,10 @@
onSelected: (Artist) -> Unit
) {
Surface(Modifier.fillMaxSize()) {
- LazyColumnFor(feedItems) { item ->
- ArtistCard(item, onSelected)
+ LazyColumn {
+ items(feedItems) { item ->
+ ArtistCard(item, onSelected)
+ }
}
}
}
diff --git a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/mentalmodel/MentalModel.kt b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/mentalmodel/MentalModel.kt
index e110e4d..635fbdf 100644
--- a/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/mentalmodel/MentalModel.kt
+++ b/compose/integration-tests/docs-snippets/src/main/java/androidx/compose/integration/docs/mentalmodel/MentalModel.kt
@@ -23,7 +23,7 @@
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.lazy.LazyColumnFor
+import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Button
import androidx.compose.material.Checkbox
import androidx.compose.material.Divider
@@ -118,12 +118,14 @@
Text(header, style = MaterialTheme.typography.h5)
Divider()
- // LazyColumnFor is the Compose version of a RecyclerView.
- // The lambda passed is similar to a RecyclerView.ViewHolder.
- LazyColumnFor(names) { name ->
- // When an item's [name] updates, the adapter for that item
- // will recompose. This will not recompose when [header] changes
- NamePickerItem(name, onNameClicked)
+ // LazyColumn is the Compose version of a RecyclerView.
+ // The lambda passed to items() is similar to a RecyclerView.ViewHolder.
+ LazyColumn {
+ items(names) { name ->
+ // When an item's [name] updates, the adapter for that item
+ // will recompose. This will not recompose when [header] changes
+ NamePickerItem(name, onNameClicked)
+ }
}
}
}
diff --git a/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/LazyColumnActivity.kt b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/LazyColumnActivity.kt
index 8fa8f88..d981bab 100644
--- a/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/LazyColumnActivity.kt
+++ b/compose/integration-tests/macrobenchmark-target/src/main/java/androidx/compose/integration/macrobenchmark/target/LazyColumnActivity.kt
@@ -22,7 +22,7 @@
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyColumnFor
+import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Card
import androidx.compose.material.Checkbox
import androidx.compose.material.Text
@@ -38,13 +38,11 @@
val itemCount = intent.getIntExtra(EXTRA_ITEM_COUNT, 1000)
setContent {
- LazyColumnFor(
- items = List(itemCount) {
- Entry("Item $it")
- },
- modifier = Modifier.fillMaxWidth(),
- itemContent = { ListRow(it) }
- )
+ LazyColumn(modifier = Modifier.fillMaxWidth()) {
+ items(List(itemCount) { Entry("Item $it") }) {
+ ListRow(it)
+ }
+ }
}
}
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BackdropScaffoldSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BackdropScaffoldSamples.kt
index b2a34de..0294cad 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BackdropScaffoldSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BackdropScaffoldSamples.kt
@@ -19,7 +19,6 @@
import androidx.annotation.Sampled
import androidx.compose.foundation.clickable
import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.LazyColumnFor
import androidx.compose.material.BackdropScaffold
import androidx.compose.material.BackdropValue
import androidx.compose.material.ExperimentalMaterialApi
@@ -86,14 +85,16 @@
)
},
backLayerContent = {
- LazyColumnFor((1..5).toList()) {
- ListItem(
- Modifier.clickable {
- selection.value = it
- scaffoldState.conceal()
- },
- text = { Text("Select $it") }
- )
+ LazyColumn {
+ for (i in 1..5) item {
+ ListItem(
+ Modifier.clickable {
+ selection.value = i
+ scaffoldState.conceal()
+ },
+ text = { Text("Select $i") }
+ )
+ }
}
},
frontLayerContent = {
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SwipeToDismissSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SwipeToDismissSamples.kt
index cfa1849..ca1b39d 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SwipeToDismissSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/SwipeToDismissSamples.kt
@@ -22,7 +22,7 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyColumnFor
+import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Card
import androidx.compose.material.DismissDirection.EndToStart
import androidx.compose.material.DismissDirection.StartToEnd
@@ -78,57 +78,63 @@
// will animate to red if you're swiping left or green if you're swiping right. When you let
// go, the item will animate out of the way if you're swiping left (like deleting an email) or
// back to its default position if you're swiping right (like marking an email as read/unread).
- LazyColumnFor(items) { item ->
- var unread by remember { mutableStateOf(false) }
- val dismissState = rememberDismissState(
- confirmStateChange = {
- if (it == DismissedToEnd) unread = !unread
- it != DismissedToEnd
- }
- )
- SwipeToDismiss(
- state = dismissState,
- modifier = Modifier.padding(vertical = 4.dp),
- directions = setOf(StartToEnd, EndToStart),
- dismissThresholds = { direction ->
- FractionalThreshold(if (direction == StartToEnd) 0.25f else 0.5f)
- },
- background = {
- val direction = dismissState.dismissDirection ?: return@SwipeToDismiss
- val color = animate(
- when (dismissState.targetValue) {
- Default -> Color.LightGray
- DismissedToEnd -> Color.Green
- DismissedToStart -> Color.Red
- }
- )
- val alignment = when (direction) {
- StartToEnd -> Alignment.CenterStart
- EndToStart -> Alignment.CenterEnd
+ LazyColumn {
+ items(items) { item ->
+ var unread by remember { mutableStateOf(false) }
+ val dismissState = rememberDismissState(
+ confirmStateChange = {
+ if (it == DismissedToEnd) unread = !unread
+ it != DismissedToEnd
}
- val icon = when (direction) {
- StartToEnd -> Icons.Default.Done
- EndToStart -> Icons.Default.Delete
- }
- val scale = animate(if (dismissState.targetValue == Default) 0.75f else 1f)
-
- Box(
- modifier = Modifier.fillMaxSize().background(color).padding(horizontal = 20.dp),
- contentAlignment = alignment
- ) {
- Icon(icon, Modifier.scale(scale))
- }
- },
- dismissContent = {
- Card(
- elevation = animate(if (dismissState.dismissDirection != null) 4.dp else 0.dp)
- ) {
- ListItem(
- text = { Text(item, fontWeight = if (unread) FontWeight.Bold else null) },
- secondaryText = { Text("Swipe me left or right!") }
+ )
+ SwipeToDismiss(
+ state = dismissState,
+ modifier = Modifier.padding(vertical = 4.dp),
+ directions = setOf(StartToEnd, EndToStart),
+ dismissThresholds = { direction ->
+ FractionalThreshold(if (direction == StartToEnd) 0.25f else 0.5f)
+ },
+ background = {
+ val direction = dismissState.dismissDirection ?: return@SwipeToDismiss
+ val color = animate(
+ when (dismissState.targetValue) {
+ Default -> Color.LightGray
+ DismissedToEnd -> Color.Green
+ DismissedToStart -> Color.Red
+ }
)
+ val alignment = when (direction) {
+ StartToEnd -> Alignment.CenterStart
+ EndToStart -> Alignment.CenterEnd
+ }
+ val icon = when (direction) {
+ StartToEnd -> Icons.Default.Done
+ EndToStart -> Icons.Default.Delete
+ }
+ val scale = animate(if (dismissState.targetValue == Default) 0.75f else 1f)
+
+ Box(
+ Modifier.fillMaxSize().background(color).padding(horizontal = 20.dp),
+ contentAlignment = alignment
+ ) {
+ Icon(icon, Modifier.scale(scale))
+ }
+ },
+ dismissContent = {
+ Card(
+ elevation = animate(
+ if (dismissState.dismissDirection != null) 4.dp else 0.dp
+ )
+ ) {
+ ListItem(
+ text = {
+ Text(item, fontWeight = if (unread) FontWeight.Bold else null)
+ },
+ secondaryText = { Text("Swipe me left or right!") }
+ )
+ }
}
- }
- )
+ )
+ }
}
}
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/PointerInputDuringSubCompDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/PointerInputDuringSubCompDemo.kt
index f6ea061..8901b03 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/PointerInputDuringSubCompDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/gestures/PointerInputDuringSubCompDemo.kt
@@ -23,7 +23,7 @@
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.foundation.lazy.LazyColumnFor
+import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
@@ -60,27 +60,28 @@
"it is actually a new item that has not been hit tested yet. If you keep " +
"your finger there and then add more fingers, it will track those new fingers."
)
- LazyColumnFor(
- List(100) { index -> index },
+ LazyColumn(
Modifier
.fillMaxSize()
.wrapContentSize(Alignment.Center)
.size(200.dp)
.background(color = Color.White)
) {
- val pointerCount = remember { mutableStateOf(0) }
+ items(List(100) { index -> index }) {
+ val pointerCount = remember { mutableStateOf(0) }
- Box(
- Modifier.fillParentMaxSize()
- .border(width = 1.dp, color = Color.Black)
- .pointerCounterGestureFilter { newCount -> pointerCount.value = newCount },
- contentAlignment = Alignment.Center
- ) {
- Text(
- "${pointerCount.value}",
- fontSize = TextUnit.Em(16),
- color = Color.Black
- )
+ Box(
+ Modifier.fillParentMaxSize()
+ .border(width = 1.dp, color = Color.Black)
+ .pointerCounterGestureFilter { newCount -> pointerCount.value = newCount },
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ "${pointerCount.value}",
+ fontSize = TextUnit.Em(16),
+ color = Color.Black
+ )
+ }
}
}
}
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/NestedScrollSamples.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/NestedScrollSamples.kt
index 6b4dccc..4a95ba4 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/NestedScrollSamples.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/NestedScrollSamples.kt
@@ -23,7 +23,6 @@
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.LazyColumnFor
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -134,11 +133,13 @@
.nestedScroll(connection, dispatcher = nestedScrollDispatcher)
) {
// hypothetical scrollable child which we will listen in connection above
- LazyColumnFor(listOf(1, 2, 3, 4, 5)) {
- Text(
- "Magenta text above will change first when you scroll me",
- modifier = Modifier.padding(5.dp)
- )
+ LazyColumn {
+ items(listOf(1, 2, 3, 4, 5)) {
+ Text(
+ "Magenta text above will change first when you scroll me",
+ modifier = Modifier.padding(5.dp)
+ )
+ }
}
// simply show our value. It will change when we scroll child list above, taking
// child's scroll delta until we reach maxValue or minValue
diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
index cc7f3c6..7f0aca2 100644
--- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
+++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/platform/DesktopOwnerTest.kt
@@ -29,7 +29,7 @@
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.lazy.LazyColumnFor
+import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.ExperimentalComposeApi
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -288,10 +288,12 @@
var height by mutableStateOf(10.dp)
setContent {
Box(Modifier.padding(10.dp)) {
- LazyColumnFor(
- listOf(Color.Red, Color.Green, Color.Blue, Color.Black, Color.Gray)
- ) { color ->
- Box(Modifier.size(width = 30.dp, height = height).background(color))
+ LazyColumn {
+ items(
+ listOf(Color.Red, Color.Green, Color.Blue, Color.Black, Color.Gray)
+ ) { color ->
+ Box(Modifier.size(width = 30.dp, height = height).background(color))
+ }
}
}
}