[go: nahoru, domu]

FlowLayout API Design and implementation

Relnote: A Flow Layout is a simple layout manager that arranges components at their preferred sizes, from left to right and top to bottom in the container. A flow layout arranges components in a directional flow, much like lines of text in a paragraph.

Bug: 123123123
Test: Tested with code and manually.
Change-Id: I3a7b26bff88ec172df7ab4acf62c2eefd5edb16d
diff --git a/compose/foundation/foundation-layout/api/current.txt b/compose/foundation/foundation-layout/api/current.txt
index 6537434..98b9343 100644
--- a/compose/foundation/foundation-layout/api/current.txt
+++ b/compose/foundation/foundation-layout/api/current.txt
@@ -111,6 +111,9 @@
     method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, float weight, optional boolean fill);
   }
 
+  public final class FlowLayoutKt {
+  }
+
   public final class IntrinsicKt {
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier height(androidx.compose.ui.Modifier, androidx.compose.foundation.layout.IntrinsicSize intrinsicSize);
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier requiredHeight(androidx.compose.ui.Modifier, androidx.compose.foundation.layout.IntrinsicSize intrinsicSize);
diff --git a/compose/foundation/foundation-layout/api/public_plus_experimental_current.txt b/compose/foundation/foundation-layout/api/public_plus_experimental_current.txt
index ec7aeca..05b6344 100644
--- a/compose/foundation/foundation-layout/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation-layout/api/public_plus_experimental_current.txt
@@ -114,6 +114,11 @@
   @kotlin.RequiresOptIn(message="The API of this layout is experimental and is likely to change in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalLayoutApi {
   }
 
+  public final class FlowLayoutKt {
+    method @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public static void FlowColumn(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.ui.Alignment.Horizontal horizontalAlignment, optional int maxItemsInEachColumn, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+    method @androidx.compose.foundation.layout.ExperimentalLayoutApi @androidx.compose.runtime.Composable public static void FlowRow(optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.ui.Alignment.Vertical verticalAlignment, optional int maxItemsInEachRow, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+  }
+
   public final class IntrinsicKt {
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier height(androidx.compose.ui.Modifier, androidx.compose.foundation.layout.IntrinsicSize intrinsicSize);
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier requiredHeight(androidx.compose.ui.Modifier, androidx.compose.foundation.layout.IntrinsicSize intrinsicSize);
diff --git a/compose/foundation/foundation-layout/api/restricted_current.txt b/compose/foundation/foundation-layout/api/restricted_current.txt
index ced4c05..f1576ee 100644
--- a/compose/foundation/foundation-layout/api/restricted_current.txt
+++ b/compose/foundation/foundation-layout/api/restricted_current.txt
@@ -114,6 +114,9 @@
     method @androidx.compose.runtime.Stable public androidx.compose.ui.Modifier weight(androidx.compose.ui.Modifier, float weight, optional boolean fill);
   }
 
+  public final class FlowLayoutKt {
+  }
+
   public final class IntrinsicKt {
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier height(androidx.compose.ui.Modifier, androidx.compose.foundation.layout.IntrinsicSize intrinsicSize);
     method @androidx.compose.runtime.Stable public static androidx.compose.ui.Modifier requiredHeight(androidx.compose.ui.Modifier, androidx.compose.foundation.layout.IntrinsicSize intrinsicSize);
diff --git a/compose/foundation/foundation-layout/benchmark/src/androidTest/java/androidx/compose/foundation/layout/benchmark/RectsInFlowColumnBenchmark.kt b/compose/foundation/foundation-layout/benchmark/src/androidTest/java/androidx/compose/foundation/layout/benchmark/RectsInFlowColumnBenchmark.kt
new file mode 100644
index 0000000..0a6072f
--- /dev/null
+++ b/compose/foundation/foundation-layout/benchmark/src/androidTest/java/androidx/compose/foundation/layout/benchmark/RectsInFlowColumnBenchmark.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2019 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.layout.benchmark
+
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.benchmarkDrawPerf
+import androidx.compose.testutils.benchmark.benchmarkFirstCompose
+import androidx.compose.testutils.benchmark.benchmarkFirstDraw
+import androidx.compose.testutils.benchmark.benchmarkFirstLayout
+import androidx.compose.testutils.benchmark.benchmarkFirstMeasure
+import androidx.compose.testutils.benchmark.benchmarkLayoutPerf
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkDraw
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkLayout
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkMeasure
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkRecompose
+import androidx.test.filters.LargeTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Benchmark that runs [RectsInFlowColumnTestCase].
+ */
+@LargeTest
+@RunWith(Parameterized::class)
+class RectsInFlowColumnBenchmark(private val numberOfRectangles: Int) {
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun initParameters(): Array<Any> = arrayOf(10, 100)
+    }
+
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    private val rectsInFlowColumnCaseFactory = { RectsInFlowColumnTestCase(numberOfRectangles) }
+
+    @Test
+    fun first_compose() {
+        benchmarkRule.benchmarkFirstCompose(rectsInFlowColumnCaseFactory)
+    }
+
+    @Test
+    fun first_measure() {
+        benchmarkRule.benchmarkFirstMeasure(rectsInFlowColumnCaseFactory)
+    }
+
+    @Test
+    fun first_layout() {
+        benchmarkRule.benchmarkFirstLayout(rectsInFlowColumnCaseFactory)
+    }
+
+    @Test
+    fun first_draw() {
+        benchmarkRule.benchmarkFirstDraw(rectsInFlowColumnCaseFactory)
+    }
+
+    @Test
+    fun toggleRectangleColor_recompose() {
+        benchmarkRule.toggleStateBenchmarkRecompose(rectsInFlowColumnCaseFactory)
+    }
+
+    @Test
+    fun toggleRectangleColor_measure() {
+        benchmarkRule.toggleStateBenchmarkMeasure(rectsInFlowColumnCaseFactory)
+    }
+
+    @Test
+    fun toggleRectangleColor_layout() {
+        benchmarkRule.toggleStateBenchmarkLayout(rectsInFlowColumnCaseFactory)
+    }
+
+    @Test
+    fun toggleRectangleColor_draw() {
+        benchmarkRule.toggleStateBenchmarkDraw(rectsInFlowColumnCaseFactory)
+    }
+
+    @Test
+    fun layout() {
+        benchmarkRule.benchmarkLayoutPerf(rectsInFlowColumnCaseFactory)
+    }
+
+    @Test
+    fun draw() {
+        benchmarkRule.benchmarkDrawPerf(rectsInFlowColumnCaseFactory)
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-layout/benchmark/src/androidTest/java/androidx/compose/foundation/layout/benchmark/RectsInFlowColumnTestCase.kt b/compose/foundation/foundation-layout/benchmark/src/androidTest/java/androidx/compose/foundation/layout/benchmark/RectsInFlowColumnTestCase.kt
new file mode 100644
index 0000000..223b9f5
--- /dev/null
+++ b/compose/foundation/foundation-layout/benchmark/src/androidTest/java/androidx/compose/foundation/layout/benchmark/RectsInFlowColumnTestCase.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 20 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.layout.benchmark
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.FlowColumn
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.ToggleableTestCase
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+
+/**
+ * Test case that puts the given amount of rectangles into a column layout and makes changes by
+ * modifying the color used in the model.
+ *
+ * Note: Each rectangle has its own model so changes should always affect only the first one.
+ */
+@OptIn(ExperimentalLayoutApi::class)
+class RectsInFlowColumnTestCase(
+    private val amountOfRectangles: Int
+) : LayeredComposeTestCase(), ToggleableTestCase {
+
+    private val states = mutableListOf<MutableState<Color>>()
+
+    @Composable
+    override fun MeasuredContent() {
+        FlowColumn(maxItemsInEachColumn = 3) {
+            repeat(amountOfRectangles) {
+                ColoredRectWithModel()
+            }
+        }
+    }
+
+    override fun toggleState() {
+        val state = states.first()
+        if (state.value == Color.Magenta) {
+            state.value = Color.Blue
+        } else {
+            state.value = Color.Magenta
+        }
+    }
+
+    @Composable
+    fun ColoredRectWithModel() {
+        val state = remember { mutableStateOf(Color.Black) }
+        states.add(state)
+        Box(
+            Modifier
+                .size(100.dp, 50.dp)
+                .background(color = state.value))
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/LayoutDemos.kt b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/LayoutDemos.kt
index e6a8e2a..104d27c 100644
--- a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/LayoutDemos.kt
+++ b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/LayoutDemos.kt
@@ -23,6 +23,8 @@
     "Layout",
     listOf(
         ComposableDemo("Row and column") { SimpleLayoutDemo() },
+        ComposableDemo("Flow Column") { SimpleFlowColumnDemo() },
+        ComposableDemo("Flow Row") { SimpleFlowRowDemo() },
         ComposableDemo("Rtl support") { RtlDemo() }
     )
 )
diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowColumnDemo.kt b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowColumnDemo.kt
new file mode 100644
index 0000000..4cdd0f9
--- /dev/null
+++ b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowColumnDemo.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2021 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.layout.demos
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.FlowColumn
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+@OptIn(ExperimentalLayoutApi::class)
+@Composable
+fun SimpleFlowColumnDemo() {
+    Column() {
+        FlowColumn(
+            Modifier
+                .fillMaxWidth(1f)
+                .wrapContentHeight(align = Alignment.Top)
+                .requiredHeight(200.dp)
+                .border(BorderStroke(2.dp, Color.Gray)),
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.Center,
+            maxItemsInEachColumn = 3
+        ) {
+
+            // handling overflow issues properly
+            // reversing the width and height aspect ratios will be buggy
+            repeat(9) {
+                Box(
+                    Modifier
+                        .padding(10.dp)
+                        .width(50.dp)
+                        .height(50.dp)
+                        .background(Color(0xFF6200ED))
+                        .weight(1f, true)
+                ) {
+                    Text(text = it.toString(), fontSize = 18.sp, modifier = Modifier.padding(3.dp))
+                }
+            }
+        }
+
+        FlowColumn(
+            Modifier
+                .fillMaxHeight(1f)
+                .wrapContentWidth(),
+            verticalArrangement = Arrangement.Top,
+            maxItemsInEachColumn = 5
+        ) {
+            repeat(10) { _ ->
+                Box(
+                    Modifier
+                        .size(20.dp)
+                )
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowRowDemo.kt b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowRowDemo.kt
new file mode 100644
index 0000000..460270d
--- /dev/null
+++ b/compose/foundation/foundation-layout/integration-tests/layout-demos/src/main/java/androidx/compose/foundation/layout/demos/SimpleFlowRowDemo.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2021 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.layout.demos
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.FlowRow
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+@OptIn(ExperimentalLayoutApi::class)
+@Composable
+fun SimpleFlowRowDemo() {
+    Column() {
+        FlowRow(
+            Modifier
+                .wrapContentWidth(align = Alignment.Start)
+                .wrapContentHeight(align = Alignment.Top)
+                .requiredHeight(150.dp)
+                .border(BorderStroke(2.dp, Color.Gray)),
+            verticalAlignment = Alignment.CenterVertically,
+            horizontalArrangement = Arrangement.Center,
+            maxItemsInEachRow = Int.MAX_VALUE
+        ) {
+            repeat(50) {
+                Text("Heldo")
+            }
+        }
+        FlowRow(
+            Modifier
+                .fillMaxWidth(1f)
+                .wrapContentHeight(align = Alignment.Top)
+                .border(BorderStroke(2.dp, Color.Gray)),
+            verticalAlignment = Alignment.CenterVertically,
+            horizontalArrangement = Arrangement.End,
+            maxItemsInEachRow = 3
+        ) {
+            repeat(10) {
+                Box(
+                    Modifier
+                        .padding(10.dp)
+                        .width(50.dp)
+                        .height(
+                            if (it % 2 == 0) {
+                                30.dp
+                            } else {
+                                50.dp
+                            }
+                        )
+                        .background(Color(0xFF6200ED))
+                        .weight(1f, false)
+                ) {
+                    Text(text = it.toString(), fontSize = 18.sp, modifier = Modifier.padding(3.dp))
+                }
+            }
+        }
+
+        FlowRow(
+            Modifier
+                .wrapContentHeight()
+                .width(200.dp),
+            horizontalArrangement = Arrangement.Start,
+            maxItemsInEachRow = 5
+        ) {
+            repeat(6) { _ ->
+                Box(
+                    Modifier
+                        .size(20.dp)
+                )
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/FlowRowColumnTest.kt b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/FlowRowColumnTest.kt
new file mode 100644
index 0000000..4ac0eb0
--- /dev/null
+++ b/compose/foundation/foundation-layout/src/androidAndroidTest/kotlin/androidx/compose/foundation/layout/FlowRowColumnTest.kt
@@ -0,0 +1,1271 @@
+/*
+ * Copyright 2022 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.layout
+
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.layout.positionInParent
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import kotlin.math.roundToInt
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalLayoutApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FlowRowColumnTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun testFlowRow_wrapsToTheNextLine() {
+        var height = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(100.toDp())) {
+                    FlowRow(
+                        Modifier
+                            .wrapContentHeight()
+                            .onSizeChanged {
+                                height = it.height
+                            }) {
+                        repeat(6) {
+                            Box(Modifier.size(20.toDp()))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(40)
+    }
+
+    @Test
+    fun testFlowColumn_wrapsToTheNextLine() {
+        var width = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(100.toDp())) {
+                    FlowColumn(
+                        Modifier
+                            .wrapContentHeight()
+                            .onSizeChanged {
+                                width = it.width
+                            }) {
+                        repeat(6) {
+                            Box(Modifier.size(20.toDp()))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(width).isEqualTo(40)
+    }
+
+    @Test
+    fun testFlowRow_wrapsToTheNextLine_withExactSpaceNeeded() {
+        var height = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(100.toDp())) {
+                    FlowRow(
+                        Modifier
+                            .wrapContentHeight()
+                            .onSizeChanged {
+                                height = it.height
+                            }) {
+                        repeat(10) {
+                            Box(Modifier.size(20.toDp()))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(40)
+    }
+
+    @Test
+    fun testFlowColumn_wrapsToTheNextLine_withExactSpaceNeeded() {
+        var width = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(100.toDp())) {
+                    FlowColumn(
+                        Modifier
+                            .wrapContentHeight()
+                            .onSizeChanged {
+                                width = it.width
+                            }) {
+                        repeat(10) {
+                            Box(Modifier.size(20.toDp()))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(width).isEqualTo(40)
+    }
+
+    @Test
+    fun testFlowRow_wrapsToTheNextLineMultipleTimes() {
+        var height = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(60.toDp())) {
+                    FlowRow(
+                        Modifier
+                            .wrapContentHeight()
+                            .onSizeChanged {
+                                height = it.height
+                            }) {
+                        repeat(6) {
+                            Box(Modifier.size(20.toDp()))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(40)
+    }
+
+    @Test
+    fun testFlowColumn_wrapsToTheNextLineMultipleTimes() {
+        var width = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(60.toDp())) {
+                    FlowColumn(
+                        Modifier
+                            .wrapContentHeight()
+                            .onSizeChanged {
+                                width = it.width
+                            }) {
+                        repeat(6) {
+                            Box(Modifier.size(20.toDp()))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(width).isEqualTo(40)
+    }
+
+    @Test
+    fun testFlowRow_wrapsWithMaxItems() {
+        var height = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(60.toDp())) {
+                    FlowRow(
+                        Modifier
+                            .wrapContentHeight()
+                            .onSizeChanged {
+                                height = it.height
+                            }, maxItemsInEachRow = 2) {
+                        repeat(6) {
+                            Box(Modifier.size(20.toDp()))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(60)
+    }
+
+    @Test
+    fun testFlowColumn_wrapsWithMaxItems() {
+        var width = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(60.toDp())) {
+                    FlowColumn(
+                        Modifier
+                            .wrapContentHeight()
+                            .onSizeChanged {
+                                width = it.width
+                            }, maxItemsInEachColumn = 2) {
+                        repeat(6) {
+                            Box(Modifier.size(20.toDp()))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(width).isEqualTo(60)
+    }
+
+    @Test
+    fun testFlowRow_wrapsWithWeights() {
+        var height = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(60.toDp())) {
+                    FlowRow(
+                        Modifier
+                            .wrapContentHeight()
+                            .onSizeChanged {
+                                height = it.height
+                            }, maxItemsInEachRow = 2) {
+                        repeat(6) {
+                            Box(
+                                Modifier
+                                    .size(20.toDp())
+                                    .weight(1f, true))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(60)
+    }
+
+    @Test
+    fun testFlowColumn_wrapsWithWeights() {
+        var width = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(60.toDp())) {
+                    FlowColumn(
+                        Modifier
+                            .wrapContentHeight()
+                            .onSizeChanged {
+                                width = it.width
+                            }, maxItemsInEachColumn = 2) {
+                        repeat(6) {
+                            Box(
+                                Modifier
+                                    .size(20.toDp())
+                                    .weight(1f, true))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(width).isEqualTo(60)
+    }
+
+    @Test
+    fun testFlowRow_staysInOneRow() {
+        var height = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(50.toDp())) {
+                    FlowRow(
+                        Modifier
+                            .wrapContentHeight()
+                            .onSizeChanged {
+                                height = it.height
+                            }) {
+                        repeat(2) {
+                            Box(Modifier.size(20.toDp()))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(20)
+    }
+
+    @Test
+    fun testFlowColumn_staysInOneRow() {
+        var width = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(50.toDp())) {
+                    FlowColumn(
+                        Modifier
+                            .wrapContentHeight()
+                            .onSizeChanged {
+                                width = it.width
+                            }) {
+                        repeat(2) {
+                            Box(Modifier.size(20.toDp()))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(width).isEqualTo(20)
+    }
+
+    @Test
+    fun testFlowRow_wrapsToTheNextLine_Rounding() {
+        var height = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(50.toDp())) {
+                    FlowRow(
+                        Modifier
+                            .wrapContentHeight()
+                            .onSizeChanged {
+                                height = it.height
+                            }) {
+                        repeat(3) {
+                            Box(Modifier.size(20.toDp()))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(40)
+    }
+
+    @Test
+    fun testFlowColumn_wrapsToTheNextLine_Rounding() {
+        var width = 0
+
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(50.toDp())) {
+                    FlowColumn(
+                        Modifier
+                            .wrapContentHeight()
+                            .onSizeChanged {
+                                width = it.width
+                            }) {
+                        repeat(3) {
+                            Box(Modifier.size(20.toDp()))
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(width).isEqualTo(40)
+    }
+
+    @Test
+    fun testFlowRow_centerVertically() {
+
+        val totalRowHeight = 20
+        val shorterHeight = 10
+        val expectedResult = (totalRowHeight - shorterHeight) / 2
+        var positionInParentY = 0f
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    FlowRow(
+                        Modifier.wrapContentHeight(),
+                        verticalAlignment = Alignment.CenterVertically) {
+                        repeat(5) { index ->
+                            Box(
+                                Modifier
+                                    .size(
+                                        20.toDp(),
+                                        if (index == 4) {
+                                            shorterHeight.toDp()
+                                        } else {
+                                            totalRowHeight.toDp()
+                                        }
+                                    )
+                                    .onPlaced {
+                                        if (index == 4) {
+                                            val positionInParent = it.positionInParent()
+                                            positionInParentY = positionInParent.y
+                                        }
+                                    })
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(positionInParentY).isEqualTo(expectedResult)
+    }
+
+    @Test
+    fun testFlowColumn_centerHorizontally() {
+
+        val totalColumnWidth = 20
+        val shorterWidth = 10
+        val expectedResult = (totalColumnWidth - shorterWidth) / 2
+        var positionInParentX = 0f
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    FlowColumn(
+                        Modifier.wrapContentHeight(),
+                        horizontalAlignment = Alignment.CenterHorizontally) {
+                        repeat(5) { index ->
+                            Box(
+                                Modifier
+                                    .size(
+                                        if (index == 4) {
+                                            shorterWidth.toDp()
+                                        } else {
+                                            totalColumnWidth.toDp()
+                                        },
+                                        20.toDp()
+                                    )
+                                    .onPlaced {
+                                        if (index == 4) {
+                                            val positionInParent = it.positionInParent()
+                                            positionInParentX = positionInParent.x
+                                        }
+                                    })
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(positionInParentX).isEqualTo(expectedResult)
+    }
+
+    @Test
+    fun testFlowRow_horizontalArrangementSpaceAround() {
+        val size = 200f
+        val noOfItemsPerRow = 5
+        val eachSize = 20
+        val spaceAvailable = size - (noOfItemsPerRow * eachSize) // 100
+        val eachItemSpaceGiven = spaceAvailable / noOfItemsPerRow
+        val gapSize = (eachItemSpaceGiven / 2).roundToInt()
+        //  ----
+        //      * Visually: #1##2##3# for LTR and #3##2##1# for RTL
+        // --(front) - (back) --
+
+        val xPositions = FloatArray(noOfItemsPerRow)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    FlowRow(Modifier.wrapContentHeight().fillMaxWidth(1f),
+                        horizontalArrangement = Arrangement.SpaceAround) {
+                        repeat(5) { index ->
+                            Box(
+                                Modifier
+                                    .size(20.toDp())
+                                    .onPlaced {
+                                        val positionInParent = it.positionInParent()
+                                        val xPosition = positionInParent.x
+                                        xPositions[index] = xPosition
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedXPosition = 0
+        xPositions.forEach {
+            val xPosition = it
+            expectedXPosition += gapSize
+            Truth
+                .assertThat(xPosition)
+                .isEqualTo(expectedXPosition)
+            expectedXPosition += eachSize
+            expectedXPosition += gapSize
+        }
+    }
+
+    @Test
+    fun testFlowColumn_verticalArrangementSpaceAround() {
+        val size = 200f
+        val noOfItemsPerRow = 5
+        val eachSize = 20
+        val spaceAvailable = size - (noOfItemsPerRow * eachSize) // 100
+        val eachItemSpaceGiven = spaceAvailable / noOfItemsPerRow
+        val gapSize = (eachItemSpaceGiven / 2).roundToInt()
+
+        val yPositions = FloatArray(noOfItemsPerRow)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    FlowColumn(Modifier.wrapContentWidth().fillMaxHeight(1f),
+                        verticalArrangement = Arrangement.SpaceAround) {
+                        repeat(5) { index ->
+                            Box(
+                                Modifier
+                                    .size(20.toDp())
+                                    .onPlaced {
+                                        val positionInParent = it.positionInParent()
+                                        val yPosition = positionInParent.y
+                                        yPositions[index] = yPosition
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedYPosition = 0
+        yPositions.forEach {
+            val yPosition = it
+            expectedYPosition += gapSize
+            Truth
+                .assertThat(yPosition)
+                .isEqualTo(expectedYPosition)
+            expectedYPosition += eachSize
+            expectedYPosition += gapSize
+        }
+    }
+
+    @Test
+    fun testFlowRow_horizontalArrangementSpaceAround_withTwoRows() {
+        val size = 200f
+        val noOfItemsPerRow = 5
+        val eachSize = 20
+        val spaceAvailable = size - (noOfItemsPerRow * eachSize) // 100
+        val eachItemSpaceGiven = spaceAvailable / noOfItemsPerRow
+        val gapSize = (eachItemSpaceGiven / 2).roundToInt()
+        //  ----
+        //      * Visually: #1##2##3# for LTR and #3##2##1# for RTL
+        // --(front) - (back) --
+
+        val xPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    FlowRow(Modifier.wrapContentHeight().fillMaxWidth(1f),
+                        horizontalArrangement = Arrangement.SpaceAround,
+                        maxItemsInEachRow = 5
+                    ) {
+                        repeat(10) { index ->
+                            Box(
+                                Modifier
+                                    .size(20.toDp())
+                                    .onPlaced {
+                                        val positionInParent = it.positionInParent()
+                                        val xPosition = positionInParent.x
+                                        xPositions[index] = xPosition
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedXPosition = 0
+        xPositions.forEachIndexed { index, xPosition ->
+            if (index % 5 == 0) {
+                expectedXPosition = 0
+            }
+            expectedXPosition += gapSize
+            Truth
+                .assertThat(xPosition)
+                .isEqualTo(expectedXPosition)
+            expectedXPosition += eachSize
+            expectedXPosition += gapSize
+        }
+    }
+
+    @Test
+    fun testFlowColumn_verticalArrangementSpaceAround_withTwoColumns() {
+        val size = 200f
+        val noOfItemsPerRow = 5
+        val eachSize = 20
+        val spaceAvailable = size - (noOfItemsPerRow * eachSize) // 100
+        val eachItemSpaceGiven = spaceAvailable / noOfItemsPerRow
+        val gapSize = (eachItemSpaceGiven / 2).roundToInt()
+
+        val yPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    FlowColumn(Modifier.wrapContentWidth().fillMaxHeight(1f),
+                        verticalArrangement = Arrangement.SpaceAround,
+                        maxItemsInEachColumn = 5
+                    ) {
+                        repeat(10) { index ->
+                            Box(
+                                Modifier
+                                    .size(20.toDp())
+                                    .onPlaced {
+                                        val positionInParent = it.positionInParent()
+                                        val yPosition = positionInParent.y
+                                        yPositions[index] = yPosition
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedYPosition = 0
+        yPositions.forEachIndexed { index, position ->
+            if (index % 5 == 0) {
+                expectedYPosition = 0
+            }
+            expectedYPosition += gapSize
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedYPosition)
+            expectedYPosition += eachSize
+            expectedYPosition += gapSize
+        }
+    }
+
+    @Test
+    fun testFlowRow_horizontalArrangementEnd() {
+        val size = 200f
+        val noOfItemsPerRow = 5
+        val eachSize = 20
+        val spaceAvailable = size - (noOfItemsPerRow * eachSize) // 100
+        val gapSize = spaceAvailable.roundToInt()
+       //  * Visually: ####123
+
+        val xPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    FlowRow(Modifier.wrapContentHeight().fillMaxWidth(1f),
+                        horizontalArrangement = Arrangement.End,
+                        maxItemsInEachRow = 5
+                    ) {
+                        repeat(10) { index ->
+                            Box(
+                                Modifier
+                                    .size(20.toDp())
+                                    .onPlaced {
+                                        val positionInParent = it.positionInParent()
+                                        val xPosition = positionInParent.x
+                                        xPositions[index] = xPosition
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedXPosition = gapSize
+        xPositions.forEachIndexed { index, position ->
+            if (index % 5 == 0) {
+                expectedXPosition = gapSize
+            }
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedXPosition)
+            expectedXPosition += eachSize
+        }
+    }
+
+    @Test
+    fun testFlowColumn_verticalArrangementBottom() {
+        val size = 200f
+        val noOfItemsPerRow = 5
+        val eachSize = 20
+        val spaceAvailable = size - (noOfItemsPerRow * eachSize) // 100
+        val gapSize = spaceAvailable.roundToInt()
+
+        val yPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    FlowColumn(Modifier.fillMaxHeight(1f).wrapContentWidth(),
+                        verticalArrangement = Arrangement.Bottom,
+                        maxItemsInEachColumn = 5
+                    ) {
+                        repeat(10) { index ->
+                            Box(
+                                Modifier
+                                    .size(20.toDp())
+                                    .onPlaced {
+                                        val positionInParent = it.positionInParent()
+                                        val yPosition = positionInParent.y
+                                        yPositions[index] = yPosition
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedYPosition = gapSize
+        yPositions.forEachIndexed { index, position ->
+            if (index % 5 == 0) {
+                expectedYPosition = gapSize
+            }
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedYPosition)
+            expectedYPosition += eachSize
+        }
+    }
+    @Test
+    fun testFlowRow_horizontalArrangementStart() {
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+        //  * Visually: 123####
+
+        val xPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    FlowRow(Modifier.wrapContentHeight(),
+                        horizontalArrangement = Arrangement.Start,
+                        maxItemsInEachRow = maxItemsInMainAxis
+                    ) {
+                        repeat(10) { index ->
+                            Box(
+                                Modifier
+                                    .size(eachSize.toDp())
+                                    .onPlaced {
+                                        val positionInParent = it.positionInParent()
+                                        val xPosition = positionInParent.x
+                                        xPositions[index] = xPosition
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedXPosition = 0
+        xPositions.forEachIndexed { index, position ->
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedXPosition)
+            if (index == (maxItemsInMainAxis - 1)) {
+                expectedXPosition = 0
+            } else {
+                expectedXPosition += eachSize
+            }
+        }
+    }
+
+    @Test
+    fun testFlowRow_SpaceAligned() {
+        val eachSize = 10
+        val maxItemsInMainAxis = 5
+        val spaceAligned = 10
+
+        val xPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    FlowRow(Modifier.wrapContentHeight(),
+                        horizontalArrangement = Arrangement.spacedBy(spaceAligned.toDp()),
+                        maxItemsInEachRow = maxItemsInMainAxis
+                    ) {
+                        repeat(10) { index ->
+                            Box(
+                                Modifier
+                                    .size(eachSize.toDp())
+                                    .onPlaced {
+                                        val positionInParent = it.positionInParent()
+                                        val xPosition = positionInParent.x
+                                        xPositions[index] = xPosition
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedXPosition = 0
+        xPositions.forEachIndexed { index, position ->
+            if (index % maxItemsInMainAxis == 0) {
+                expectedXPosition = 0
+            } else {
+                expectedXPosition += eachSize
+                expectedXPosition += spaceAligned
+            }
+
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedXPosition)
+        }
+    }
+
+    @Test
+    fun testFlowColumn_SpaceAligned() {
+        val eachSize = 10
+        val maxItemsInMainAxis = 5
+        val spaceAligned = 10
+
+        val yPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(200.toDp())) {
+                    FlowColumn(Modifier.wrapContentHeight(),
+                        verticalArrangement = Arrangement.spacedBy(spaceAligned.toDp()),
+                        maxItemsInEachColumn = maxItemsInMainAxis
+                    ) {
+                        repeat(10) { index ->
+                            Box(
+                                Modifier
+                                    .size(eachSize.toDp())
+                                    .onPlaced {
+                                        val positionInParent = it.positionInParent()
+                                        val position = positionInParent.y
+                                        yPositions[index] = position
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedYPosition = 0
+        yPositions.forEachIndexed { index, position ->
+            if (index % maxItemsInMainAxis == 0) {
+                expectedYPosition = 0
+            } else {
+                expectedYPosition += eachSize
+                expectedYPosition += spaceAligned
+            }
+
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedYPosition)
+        }
+    }
+
+    @Test
+    fun testFlowRow_SpaceAligned_notExact() {
+        val eachSize = 10
+        val maxItemsInMainAxis = 5
+        val spaceAligned = 10
+        val noOfItemsThatCanFit = 2
+
+        var width = 0
+        val expectedWidth = 30
+        val xPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.wrapContentHeight().widthIn(30.toDp(), 40.toDp())) {
+                    FlowRow(Modifier.wrapContentHeight().onSizeChanged {
+                           width = it.width
+                    },
+                        horizontalArrangement = Arrangement.spacedBy(spaceAligned.toDp()),
+                        maxItemsInEachRow = maxItemsInMainAxis
+                    ) {
+                        repeat(10) { index ->
+                            Box(
+                                Modifier
+                                    .size(eachSize.toDp())
+                                    .onPlaced {
+                                        val positionInParent = it.positionInParent()
+                                        val xPosition = positionInParent.x
+                                        xPositions[index] = xPosition
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(width).isEqualTo(expectedWidth)
+        var expectedXPosition = 0
+        xPositions.forEachIndexed { index, position ->
+            if (index % noOfItemsThatCanFit == 0) {
+                expectedXPosition = 0
+            } else {
+                expectedXPosition += eachSize
+                expectedXPosition += spaceAligned
+            }
+
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedXPosition)
+        }
+    }
+
+    @Test
+    fun testFlowColumn_SpaceAligned_notExact() {
+        val eachSize = 10
+        val maxItemsInMainAxis = 5
+        val spaceAligned = 10
+        val noOfItemsThatCanFit = 2
+
+        var height = 0
+        val expectedHeight = 30
+        val yPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.heightIn(30.toDp(), 40.toDp()).wrapContentWidth()) {
+                    FlowColumn(Modifier.wrapContentHeight().onSizeChanged {
+                        height = it.height
+                    },
+                        verticalArrangement = Arrangement.spacedBy(spaceAligned.toDp()),
+                        maxItemsInEachColumn = maxItemsInMainAxis
+                    ) {
+                        repeat(10) { index ->
+                            Box(
+                                Modifier
+                                    .size(eachSize.toDp())
+                                    .onPlaced {
+                                        val positionInParent = it.positionInParent()
+                                        val yPosition = positionInParent.y
+                                        yPositions[index] = yPosition
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        Truth.assertThat(height).isEqualTo(expectedHeight)
+        var expectedYPosition = 0
+        yPositions.forEachIndexed { index, position ->
+            if (index % noOfItemsThatCanFit == 0) {
+                expectedYPosition = 0
+            } else {
+                expectedYPosition += eachSize
+                expectedYPosition += spaceAligned
+            }
+
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedYPosition)
+        }
+    }
+
+    @Test
+    fun testFlowColumn_verticalArrangementTop() {
+        val size = 200f
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+
+        val yPositions = FloatArray(10)
+        rule.setContent {
+            with(LocalDensity.current) {
+                Box(Modifier.size(size.toDp())) {
+                    FlowColumn(Modifier.fillMaxHeight(1f).wrapContentWidth(),
+                        verticalArrangement = Arrangement.Top,
+                        maxItemsInEachColumn = maxItemsInMainAxis
+                    ) {
+                        repeat(10) { index ->
+                            Box(
+                                Modifier
+                                    .size(20.toDp())
+                                    .onPlaced {
+                                        val positionInParent = it.positionInParent()
+                                        val yPosition = positionInParent.y
+                                        yPositions[index] = yPosition
+                                    }
+                            )
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedYPosition = 0
+        yPositions.forEachIndexed { index, position ->
+            if (index % 5 == 0) {
+                expectedYPosition = 0
+            }
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedYPosition)
+            expectedYPosition += eachSize
+        }
+    }
+
+    @Test
+    fun testFlowRow_horizontalArrangementStart_rtl_fillMaxWidth() {
+        val size = 200f
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+        //  * Visually:
+        //  #54321
+        //  ####6
+
+        val xPositions = FloatArray(6)
+        rule.setContent {
+            CompositionLocalProvider(values = arrayOf(
+                LocalLayoutDirection provides LayoutDirection.Rtl,
+            )) {
+                with(LocalDensity.current) {
+                    Box(Modifier.size(size.toDp())) {
+                        FlowRow(Modifier.wrapContentHeight().fillMaxWidth(1f),
+                            horizontalArrangement = Arrangement.Start,
+                            maxItemsInEachRow = maxItemsInMainAxis
+                        ) {
+                            repeat(6) { index ->
+                                Box(
+                                    Modifier
+                                        .size(eachSize.toDp())
+                                        .onPlaced {
+                                            val positionInParent = it.positionInParent()
+                                            val xPosition = positionInParent.x
+                                            xPositions[index] = xPosition
+                                        }
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedXPosition = size.toInt() - eachSize
+        xPositions.forEachIndexed { index, position ->
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedXPosition)
+            if (index == (maxItemsInMainAxis - 1)) {
+                expectedXPosition = size.toInt() - eachSize
+            } else {
+                expectedXPosition -= eachSize
+            }
+        }
+    }
+
+    @Test
+    fun testFlowColumn_verticalArrangementTop_rtl_fillMaxWidth() {
+        val size = 200f
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+
+        val xYPositions = Array<Pair<Float, Float>>(10) { Pair(0f, 0f) }
+        rule.setContent {
+            CompositionLocalProvider(values = arrayOf(
+                LocalLayoutDirection provides LayoutDirection.Rtl,
+            )) {
+                with(LocalDensity.current) {
+                    Box(Modifier.size(size.toDp())) {
+                        FlowColumn(Modifier.fillMaxHeight(1f).fillMaxWidth(1f),
+                            verticalArrangement = Arrangement.Top,
+                            maxItemsInEachColumn = maxItemsInMainAxis
+                        ) {
+                            repeat(10) { index ->
+                                Box(
+                                    Modifier
+                                        .size(20.toDp())
+                                        .onPlaced {
+                                            val positionInParent = it.positionInParent()
+                                            val yPosition = positionInParent.y
+                                            val xPosition = positionInParent.x
+                                            xYPositions[index] = Pair(xPosition, yPosition)
+                                        }
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        var expectedYPosition = 0
+        var expectedXPosition = size.toInt() - eachSize
+        for (index in xYPositions.indices) {
+            val xPosition = xYPositions[index].first
+            val yPosition = xYPositions[index].second
+            if (index % 5 == 0) {
+                expectedYPosition = 0
+            }
+            Truth
+                .assertThat(yPosition)
+                .isEqualTo(expectedYPosition)
+            Truth
+                .assertThat(xPosition)
+                .isEqualTo(expectedXPosition)
+            if (index == (maxItemsInMainAxis - 1)) {
+                expectedXPosition -= eachSize
+            }
+            expectedYPosition += eachSize
+        }
+    }
+
+    @Test
+    fun testFlowColumn_verticalArrangementTop_rtl_wrapContentWidth() {
+        val size = 200f
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+
+        var itemsThatCanFit = 0
+        var width = 0
+        val xYPositions = Array<Pair<Float, Float>>(10) { Pair(0f, 0f) }
+        rule.setContent {
+            CompositionLocalProvider(values = arrayOf(
+                LocalLayoutDirection provides LayoutDirection.Rtl,
+            )) {
+                with(LocalDensity.current) {
+                    Box(Modifier.size(size.toDp())) {
+                        FlowColumn(Modifier.fillMaxHeight(1f).wrapContentWidth()
+                            .onSizeChanged {
+                                width = it.width
+                                itemsThatCanFit = it.height / eachSize
+                        },
+                            verticalArrangement = Arrangement.Top,
+                            maxItemsInEachColumn = maxItemsInMainAxis
+                        ) {
+                            repeat(10) { index ->
+                                Box(
+                                    Modifier
+                                        .size(20.toDp())
+                                        .onPlaced {
+                                            val positionInParent = it.positionInParent()
+                                            val xPosition = positionInParent.x
+                                            val yPosition = positionInParent.y
+                                            xYPositions[index] = Pair(xPosition, yPosition)
+                                        }
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+        var expectedYPosition = 0
+        var expectedXPosition = width
+        var fittedItems = 0
+        for (index in xYPositions.indices) {
+            val pair = xYPositions[index]
+            val xPosition = pair.first
+            val yPosition = pair.second
+            if (index % maxItemsInMainAxis == 0 ||
+                fittedItems == itemsThatCanFit) {
+                expectedYPosition = 0
+                expectedXPosition -= eachSize
+                fittedItems = 0
+            }
+            Truth
+                .assertThat(yPosition)
+                .isEqualTo(expectedYPosition)
+            Truth
+                .assertThat(xPosition)
+                .isEqualTo(expectedXPosition)
+            expectedYPosition += eachSize
+            fittedItems++
+        }
+    }
+
+    @Test
+    fun testFlowRow_horizontalArrangementStart_rtl_wrap() {
+        val eachSize = 20
+        val maxItemsInMainAxis = 5
+        val maxMainAxisSize = 100
+        //  * Visually:
+        //  #54321
+        //  ####6
+
+        val xPositions = FloatArray(6)
+        rule.setContent {
+            CompositionLocalProvider(
+                values = arrayOf(
+                    LocalLayoutDirection provides LayoutDirection.Rtl,
+                )
+            ) {
+                with(LocalDensity.current) {
+                    Box(Modifier.size(200.toDp())) {
+                        FlowRow(
+                            Modifier.wrapContentHeight().wrapContentWidth(),
+                            horizontalArrangement = Arrangement.Start,
+                            maxItemsInEachRow = 5
+                        ) {
+                            repeat(6) { index ->
+                                Box(
+                                    Modifier
+                                        .size(20.toDp())
+                                        .onPlaced {
+                                            val positionInParent = it.positionInParent()
+                                            val xPosition = positionInParent.x
+                                            xPositions[index] = xPosition
+                                        }
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        rule.waitForIdle()
+        var expectedXPosition = maxMainAxisSize - eachSize
+        xPositions.forEachIndexed { index, position ->
+            Truth
+                .assertThat(position)
+                .isEqualTo(expectedXPosition)
+            if (index == (maxItemsInMainAxis - 1)) {
+                expectedXPosition = maxMainAxisSize - eachSize
+            } else {
+                expectedXPosition -= eachSize
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
new file mode 100644
index 0000000..15666d0
--- /dev/null
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/FlowLayout.kt
@@ -0,0 +1,422 @@
+package androidx.compose.foundation.layout
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collection.MutableVector
+import androidx.compose.runtime.collection.mutableVectorOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasurePolicy
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.constrainHeight
+import androidx.compose.ui.unit.constrainWidth
+import kotlin.math.ceil
+
+/**
+ * [FlowRow] is a layout that fills items from left to right (ltr) in LTR layouts
+ * or right to left (rtl) in RTL layouts and when it runs out of space, moves to
+ * the next "row" or "line" positioned on the bottom, and then continues filling items
+ * until the items run out.
+ *
+ * When a Modifier [RowColumnParentData.weight] is provided, it scales the item
+ * based on the number items that fall on the row it was placed in.
+ *
+ * Example:
+ * ```
+ * 1 2 3 4
+ * 5 6 7 8
+ * ```
+ * @param modifier The modifier to be applied to the Row.
+ * @param horizontalArrangement The horizontal arrangement of the layout's children.
+ * @param verticalAlignment The vertical alignment of the layout's children.
+ * @param maxItemsInEachRow The maximum number of items per row
+ * @param content The content as a [RowScope]
+ *
+ * @see FlowColumn
+ * @see [androidx.compose.foundation.layout.Row]
+ */
+@Composable
+@ExperimentalLayoutApi
+fun FlowRow(
+    modifier: Modifier = Modifier,
+    horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
+    verticalAlignment: Alignment.Vertical = Alignment.Top,
+    maxItemsInEachRow: Int = Int.MAX_VALUE,
+    content: @Composable RowScope.() -> Unit
+) {
+    val measurePolicy = rowMeasurementHelper(
+        horizontalArrangement,
+        verticalAlignment,
+        maxItemsInEachRow
+    )
+    Layout(
+        content = { RowScopeInstance.content() },
+        measurePolicy = measurePolicy,
+        modifier = modifier
+    )
+}
+
+/**
+ * [FlowColumn] is a layout that fills items from top to bottom, and when it runs out of space
+ * on the bottom, moves to the next "column" or "line"
+ * on the right or left based on ltr or rtl layouts,
+ * and then continues filling items from top to bottom.
+ *
+ * It supports ltr in LTR layouts, by placing the first column to the left, and then moving
+ * to the right
+ * It supports rtl in RTL layouts, by placing the first column to the right, and then moving
+ * to the left
+ *
+ * When a Modifier [RowColumnParentData.weight] is provided, it scales the item
+ * based on the number items that fall on the column it was placed in.
+ *
+ * Example:
+ * ```
+ * 1 4
+ * 2 5
+ * 3 6
+ * ```
+ * @param modifier The modifier to be applied to the Row.
+ * @param verticalArrangement The vertical arrangement of the layout's children.
+ * @param horizontalAlignment The horizontal alignment of the layout's children.
+ * @param maxItemsInEachColumn The maximum number of items per column
+ * @param content The content as a [ColumnScope]
+ *
+ * @see FlowRow
+ * @see [androidx.compose.foundation.layout.Column]
+ */
+@Composable
+@ExperimentalLayoutApi
+fun FlowColumn(
+    modifier: Modifier = Modifier,
+    verticalArrangement: Arrangement.Vertical = Arrangement.Top,
+    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
+    maxItemsInEachColumn: Int = Int.MAX_VALUE,
+    content: @Composable ColumnScope.() -> Unit
+) {
+    val measurePolicy = columnMeasurementHelper(
+        verticalArrangement,
+        horizontalAlignment,
+        maxItemsInEachColumn
+    )
+    Layout(
+        content = { ColumnScopeInstance.content() },
+        measurePolicy = measurePolicy,
+        modifier = modifier
+    )
+}
+
+@Composable
+private fun mainAxisRowArrangement(horizontalArrangement: Arrangement.Horizontal):
+        (Int, IntArray, LayoutDirection, Density, IntArray) -> Unit =
+    remember(horizontalArrangement) {
+        { totalSize, size, layoutDirection, density, outPosition ->
+            with(horizontalArrangement) {
+                density.arrange(totalSize, size, layoutDirection, outPosition)
+            }
+        }
+    }
+
+@Composable
+private fun mainAxisColumnArrangement(verticalArrangement: Arrangement.Vertical):
+        (Int, IntArray, LayoutDirection, Density, IntArray) -> Unit =
+    remember(verticalArrangement) {
+        { totalSize, size, _, density, outPosition ->
+            with(verticalArrangement) {
+                density.arrange(totalSize, size, outPosition)
+            }
+        }
+    }
+
+private val crossAxisRowArrangement = { totalSize: Int, size: IntArray,
+    measureScope: MeasureScope,
+    outPosition: IntArray ->
+    with(Arrangement.Top) { measureScope.arrange(totalSize, size, outPosition) }
+}
+
+private val crossAxisColumnArrangement = { totalSize: Int,
+    size: IntArray, measureScope: MeasureScope, outPosition: IntArray ->
+    with(Arrangement.Start) {
+        measureScope.arrange(totalSize, size, measureScope.layoutDirection, outPosition)
+    }
+}
+
+@Composable
+private fun rowMeasurementHelper(
+    horizontalArrangement: Arrangement.Horizontal = Arrangement.End,
+    verticalAlignment: Alignment.Vertical = Alignment.Top,
+    maxItemsInMainAxis: Int,
+): MeasurePolicy {
+    val mainAxisArrangement = mainAxisRowArrangement(horizontalArrangement)
+    val crossAxisAlignment = remember(verticalAlignment) {
+        CrossAxisAlignment.vertical(verticalAlignment)
+    }
+    return remember(horizontalArrangement, verticalAlignment, maxItemsInMainAxis) {
+        flowMeasurePolicy(
+            orientation = LayoutOrientation.Horizontal,
+            mainAxisArrangement = mainAxisArrangement,
+            arrangementSpacing = horizontalArrangement.spacing,
+            crossAxisAlignment = crossAxisAlignment,
+            crossAxisSize = SizeMode.Wrap,
+            crossAxisArrangement = crossAxisRowArrangement,
+            maxItemsInMainAxis = maxItemsInMainAxis,
+        )
+    }
+}
+
+@Composable
+private fun columnMeasurementHelper(
+    verticalArrangement: Arrangement.Vertical = Arrangement.Top,
+    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
+    maxItemsInMainAxis: Int,
+): MeasurePolicy {
+    val mainAxisArrangement = mainAxisColumnArrangement(verticalArrangement)
+    val crossAxisAlignment = remember(horizontalAlignment) {
+        CrossAxisAlignment.horizontal(horizontalAlignment)
+    }
+    return remember(verticalArrangement, horizontalAlignment, maxItemsInMainAxis) {
+        flowMeasurePolicy(
+            orientation = LayoutOrientation.Vertical,
+            mainAxisArrangement = mainAxisArrangement,
+            arrangementSpacing = verticalArrangement.spacing,
+            crossAxisAlignment = crossAxisAlignment,
+            crossAxisArrangement = crossAxisColumnArrangement,
+            maxItemsInMainAxis = maxItemsInMainAxis,
+            crossAxisSize = SizeMode.Wrap
+        )
+    }
+}
+
+/**
+ * Returns a Flow Measure Policy
+ */
+private fun flowMeasurePolicy(
+    orientation: LayoutOrientation,
+    mainAxisArrangement: (Int, IntArray, LayoutDirection, Density, IntArray) -> Unit,
+    arrangementSpacing: Dp,
+    crossAxisSize: SizeMode,
+    crossAxisAlignment: CrossAxisAlignment,
+    crossAxisArrangement: (Int, IntArray, MeasureScope, IntArray) -> Unit,
+    maxItemsInMainAxis: Int,
+) = MeasurePolicy { measurables, constraints ->
+    val placeables: Array<Placeable?> = arrayOfNulls(measurables.size)
+    val measureHelper = RowColumnMeasurementHelper(
+        orientation,
+        mainAxisArrangement,
+        arrangementSpacing,
+        crossAxisSize,
+        crossAxisAlignment,
+        measurables,
+        placeables,
+    )
+    val orientationIndependentConstraints =
+        OrientationIndependentConstraints(constraints, orientation)
+    val flowResult = breakDownItems(
+        measureHelper,
+        orientation,
+        orientationIndependentConstraints,
+        maxItemsInMainAxis,
+    )
+    val totalCrossAxisSize = flowResult.crossAxisTotalSize
+    val items = flowResult.items
+    val crossAxisSizes = IntArray(items.size) { index ->
+        items[index].crossAxisSize
+    }
+    val outPosition = IntArray(crossAxisSizes.size)
+    crossAxisArrangement(
+        totalCrossAxisSize,
+        crossAxisSizes, this@MeasurePolicy, outPosition
+    )
+
+    var layoutWidth: Int
+    var layoutHeight: Int
+    if (orientation == LayoutOrientation.Horizontal) {
+        layoutWidth = flowResult.mainAxisTotalSize
+        layoutHeight = flowResult.crossAxisTotalSize
+    } else {
+        layoutWidth = flowResult.crossAxisTotalSize
+        layoutHeight = flowResult.mainAxisTotalSize
+    }
+    layoutWidth = constraints.constrainWidth(layoutWidth)
+    layoutHeight = constraints.constrainHeight(layoutHeight)
+
+    layout(layoutWidth, layoutHeight) {
+        flowResult.items.forEachIndexed { currentRowOrColumnIndex,
+            measureResult ->
+            measureHelper.placeHelper(
+                this,
+                measureResult,
+                outPosition[currentRowOrColumnIndex],
+                this@MeasurePolicy.layoutDirection
+            )
+        }
+    }
+}
+
+/**
+ * Breaks down items based on space, size and maximum items in main axis.
+ * When items run out of space or the maximum items to fit in the main axis is reached,
+ * it moves to the next "line" and moves the next batch of items to a new list of items
+ */
+internal fun MeasureScope.breakDownItems(
+    measureHelper: RowColumnMeasurementHelper,
+    orientation: LayoutOrientation,
+    constraints: OrientationIndependentConstraints,
+    maxItemsInMainAxis: Int,
+): FlowResult {
+    val items = mutableVectorOf<RowColumnMeasureHelperResult>()
+    val mainAxisMax = constraints.mainAxisMax
+    val mainAxisMin = constraints.mainAxisMin
+    val crossAxisMax = constraints.crossAxisMax
+    val measurables = measureHelper.measurables
+    val placeables = measureHelper.placeables
+
+    val spacing = ceil(measureHelper.arrangementSpacing.toPx()).toInt()
+    val subsetConstraints = OrientationIndependentConstraints(
+        mainAxisMin,
+        mainAxisMax,
+        0,
+        crossAxisMax
+    )
+
+    // nextSize of the list, pre-calculated
+    var nextSize: Int? = measurables.getOrNull(0)?.measureAndCache(
+        subsetConstraints, orientation
+    ) { placeable ->
+        placeables[0] = placeable
+    }
+
+    var startBreakLineIndex = 0
+    val endBreakLineList = arrayOfNulls<Int>(measurables.size)
+    var endBreakLineIndex = 0
+
+    var leftOver = mainAxisMax
+    // figure out the mainAxisTotalSize which will be minMainAxis when measuring the row/column
+    var mainAxisTotalSize = mainAxisMin
+    var currentLineMainAxisSize = 0
+    for (index in measurables.indices) {
+        val itemMainAxisSize = nextSize!!
+        currentLineMainAxisSize += itemMainAxisSize
+        leftOver -= itemMainAxisSize
+        nextSize = measurables.getOrNull(index + 1)?.measureAndCache(
+            subsetConstraints, orientation
+        ) { placeable ->
+            placeables[index + 1] = placeable
+        }?.plus(spacing)
+        if (index + 1 >= measurables.size ||
+            (index + 1) - startBreakLineIndex >= maxItemsInMainAxis ||
+            leftOver - (nextSize ?: 0) < 0
+        ) {
+            mainAxisTotalSize = maxOf(mainAxisTotalSize, currentLineMainAxisSize)
+            currentLineMainAxisSize = 0
+            leftOver = mainAxisMax
+            startBreakLineIndex = index + 1
+            endBreakLineList[endBreakLineIndex] = index + 1
+            endBreakLineIndex++
+            // only add spacing for next items in the row or column, not the starting indexes
+            nextSize = nextSize?.minus(spacing)
+        }
+    }
+
+    val subsetBoxConstraints = subsetConstraints.copy(
+        mainAxisMin = mainAxisTotalSize
+    ).toBoxConstraints(orientation)
+
+    startBreakLineIndex = 0
+    var crossAxisTotalSize = 0
+
+    endBreakLineIndex = 0
+    var endIndex = endBreakLineList.getOrNull(endBreakLineIndex)
+    while (endIndex != null) {
+        val result = measureHelper.measureWithoutPlacing(
+            this,
+            subsetBoxConstraints,
+            startBreakLineIndex,
+            endIndex
+        )
+        crossAxisTotalSize += result.crossAxisSize
+        mainAxisTotalSize = maxOf(mainAxisTotalSize, result.mainAxisSize)
+        items.add(
+            result
+        )
+        startBreakLineIndex = endIndex
+        endBreakLineIndex++
+        endIndex = endBreakLineList.getOrNull(endBreakLineIndex)
+    }
+
+    crossAxisTotalSize = maxOf(crossAxisTotalSize, constraints.crossAxisMin)
+    mainAxisTotalSize = maxOf(mainAxisTotalSize, constraints.mainAxisMin)
+    return FlowResult(
+        mainAxisTotalSize,
+        crossAxisTotalSize,
+        items,
+    )
+}
+
+internal fun Measurable.mainAxisMin(orientation: LayoutOrientation, crossAxisSize: Int) =
+    if (orientation == LayoutOrientation.Horizontal) {
+        minIntrinsicWidth(crossAxisSize)
+    } else {
+        minIntrinsicHeight(crossAxisSize)
+    }
+
+internal fun Measurable.crossAxisMin(orientation: LayoutOrientation, mainAxisSize: Int) =
+    if (orientation == LayoutOrientation.Horizontal) {
+        minIntrinsicHeight(mainAxisSize)
+    } else {
+        minIntrinsicWidth(mainAxisSize)
+    }
+
+internal fun Placeable.mainAxisSize(orientation: LayoutOrientation) =
+    if (orientation == LayoutOrientation.Horizontal) width else height
+
+internal fun Placeable.crossAxisSize(orientation: LayoutOrientation) =
+    if (orientation == LayoutOrientation.Horizontal) height else width
+
+internal val Measurable.weight
+    get() = (parentData as? RowColumnParentData)?.weight ?: 0f
+
+// We measure and cache to improve performance dramatically, instead of using intrinsics
+// This only works so far for fixed size items.
+// For weighted items, we continue to use their intrinsic widths.
+// This is because their fixed sizes are only determined after we determine
+// the number of items that can fit in the row/column it only lies on.
+private fun Measurable.measureAndCache(
+    constraints: OrientationIndependentConstraints,
+    orientation: LayoutOrientation,
+    storePlaceable: (Placeable?) -> Unit
+): Int {
+    val itemSize: Int = if (weight == 0f) {
+        // fixed sizes: measure once
+        val placeable = measure(
+            constraints.copy(
+                mainAxisMin = 0,
+            ).toBoxConstraints(orientation)
+        ).also(storePlaceable)
+        placeable.mainAxisSize(orientation)
+    } else {
+        mainAxisMin(orientation, Constraints.Infinity)
+    }
+    return itemSize
+}
+
+/**
+ * FlowResult when broken down to multiple rows or columns based on [breakDownItems] algorithm
+ *
+ * @param mainAxisTotalSize the total size of the main axis
+ * @param crossAxisTotalSize the total size of the cross axis when taken into account
+ * the cross axis sizes of all items
+ * @param items the row or column measurements for each row or column
+ */
+internal class FlowResult(
+    val mainAxisTotalSize: Int,
+    val crossAxisTotalSize: Int,
+    val items: MutableVector<RowColumnMeasureHelperResult>,
+)
\ No newline at end of file
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
index 3bed06c..d14e5f6 100644
--- a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnImpl.kt
@@ -41,7 +41,6 @@
 import kotlin.math.max
 import kotlin.math.min
 import kotlin.math.roundToInt
-import kotlin.math.sign
 
 internal fun rowColumnMeasurePolicy(
     orientation: LayoutOrientation,
@@ -50,215 +49,44 @@
     crossAxisSize: SizeMode,
     crossAxisAlignment: CrossAxisAlignment
 ): MeasurePolicy {
-    fun Placeable.mainAxisSize() =
-        if (orientation == LayoutOrientation.Horizontal) width else height
-
-    fun Placeable.crossAxisSize() =
-        if (orientation == LayoutOrientation.Horizontal) height else width
-
     return object : MeasurePolicy {
         override fun MeasureScope.measure(
             measurables: List<Measurable>,
             constraints: Constraints
         ): MeasureResult {
-            @Suppress("NAME_SHADOWING")
-            val constraints = OrientationIndependentConstraints(constraints, orientation)
-            val arrangementSpacingPx = arrangementSpacing.roundToPx()
-
-            var totalWeight = 0f
-            var fixedSpace = 0
-            var crossAxisSpace = 0
-            var weightChildrenCount = 0
-
-            var anyAlignBy = false
-            val placeables = arrayOfNulls<Placeable>(measurables.size)
-            val rowColumnParentData = Array(measurables.size) { measurables[it].data }
-
-            // First measure children with zero weight.
-            var spaceAfterLastNoWeight = 0
-            for (i in measurables.indices) {
-                val child = measurables[i]
-                val parentData = rowColumnParentData[i]
-                val weight = parentData.weight
-
-                if (weight > 0f) {
-                    totalWeight += weight
-                    ++weightChildrenCount
-                } else {
-                    val mainAxisMax = constraints.mainAxisMax
-                    val placeable = child.measure(
-                        // Ask for preferred main axis size.
-                        constraints.copy(
-                            mainAxisMin = 0,
-                            mainAxisMax = if (mainAxisMax == Constraints.Infinity) {
-                                Constraints.Infinity
-                            } else {
-                                mainAxisMax - fixedSpace
-                            },
-                            crossAxisMin = 0
-                        ).toBoxConstraints(orientation)
-                    )
-                    spaceAfterLastNoWeight = min(
-                        arrangementSpacingPx,
-                        mainAxisMax - fixedSpace - placeable.mainAxisSize()
-                    )
-                    fixedSpace += placeable.mainAxisSize() + spaceAfterLastNoWeight
-                    crossAxisSpace = max(crossAxisSpace, placeable.crossAxisSize())
-                    anyAlignBy = anyAlignBy || parentData.isRelative
-                    placeables[i] = placeable
-                }
-            }
-
-            var weightedSpace = 0
-            if (weightChildrenCount == 0) {
-                // fixedSpace contains an extra spacing after the last non-weight child.
-                fixedSpace -= spaceAfterLastNoWeight
-            } else {
-                // Measure the rest according to their weights in the remaining main axis space.
-                val targetSpace =
-                    if (totalWeight > 0f && constraints.mainAxisMax != Constraints.Infinity) {
-                        constraints.mainAxisMax
-                    } else {
-                        constraints.mainAxisMin
-                    }
-                val remainingToTarget =
-                    targetSpace - fixedSpace - arrangementSpacingPx * (weightChildrenCount - 1)
-
-                val weightUnitSpace = if (totalWeight > 0) remainingToTarget / totalWeight else 0f
-                var remainder = remainingToTarget - rowColumnParentData.sumOf {
-                    (weightUnitSpace * it.weight).roundToInt()
-                }
-
-                for (i in measurables.indices) {
-                    if (placeables[i] == null) {
-                        val child = measurables[i]
-                        val parentData = rowColumnParentData[i]
-                        val weight = parentData.weight
-                        check(weight > 0) { "All weights <= 0 should have placeables" }
-                        // After the weightUnitSpace rounding, the total space going to be occupied
-                        // can be smaller or larger than remainingToTarget. Here we distribute the
-                        // loss or gain remainder evenly to the first children.
-                        val remainderUnit = remainder.sign
-                        remainder -= remainderUnit
-                        val childMainAxisSize = max(
-                            0,
-                            (weightUnitSpace * weight).roundToInt() + remainderUnit
-                        )
-                        val placeable = child.measure(
-                            OrientationIndependentConstraints(
-                                if (parentData.fill && childMainAxisSize != Constraints.Infinity) {
-                                    childMainAxisSize
-                                } else {
-                                    0
-                                },
-                                childMainAxisSize,
-                                0,
-                                constraints.crossAxisMax
-                            ).toBoxConstraints(orientation)
-                        )
-                        weightedSpace += placeable.mainAxisSize()
-                        crossAxisSpace = max(crossAxisSpace, placeable.crossAxisSize())
-                        anyAlignBy = anyAlignBy || parentData.isRelative
-                        placeables[i] = placeable
-                    }
-                }
-                weightedSpace = (weightedSpace + arrangementSpacingPx * (weightChildrenCount - 1))
-                    .coerceAtMost(constraints.mainAxisMax - fixedSpace)
-            }
-
-            var beforeCrossAxisAlignmentLine = 0
-            var afterCrossAxisAlignmentLine = 0
-            if (anyAlignBy) {
-                for (i in placeables.indices) {
-                    val placeable = placeables[i]!!
-                    val parentData = rowColumnParentData[i]
-                    val alignmentLinePosition = parentData.crossAxisAlignment
-                        ?.calculateAlignmentLinePosition(placeable)
-                    if (alignmentLinePosition != null) {
-                        beforeCrossAxisAlignmentLine = max(
-                            beforeCrossAxisAlignmentLine,
-                            alignmentLinePosition.let {
-                                if (it != AlignmentLine.Unspecified) it else 0
-                            }
-                        )
-                        afterCrossAxisAlignmentLine = max(
-                            afterCrossAxisAlignmentLine,
-                            placeable.crossAxisSize() -
-                                (
-                                    alignmentLinePosition.let {
-                                        if (it != AlignmentLine.Unspecified) {
-                                            it
-                                        } else {
-                                            placeable.crossAxisSize()
-                                        }
-                                    }
-                                    )
-                        )
-                    }
-                }
-            }
-
-            // Compute the Row or Column size and position the children.
-            val mainAxisLayoutSize = max(fixedSpace + weightedSpace, constraints.mainAxisMin)
-            val crossAxisLayoutSize = if (constraints.crossAxisMax != Constraints.Infinity &&
-                crossAxisSize == SizeMode.Expand
-            ) {
-                constraints.crossAxisMax
-            } else {
-                max(
-                    crossAxisSpace,
-                    max(
-                        constraints.crossAxisMin,
-                        beforeCrossAxisAlignmentLine + afterCrossAxisAlignmentLine
-                    )
+            val placeables = arrayOfNulls<Placeable?>(measurables.size)
+            val rowColumnMeasureHelper =
+                RowColumnMeasurementHelper(
+                    orientation,
+                    arrangement,
+                    arrangementSpacing,
+                    crossAxisSize,
+                    crossAxisAlignment,
+                    measurables,
+                    placeables
                 )
-            }
-            val layoutWidth = if (orientation == Horizontal) {
-                mainAxisLayoutSize
-            } else {
-                crossAxisLayoutSize
-            }
-            val layoutHeight = if (orientation == Horizontal) {
-                crossAxisLayoutSize
-            } else {
-                mainAxisLayoutSize
-            }
 
-            val mainAxisPositions = IntArray(measurables.size) { 0 }
+            val measureResult = rowColumnMeasureHelper
+                .measureWithoutPlacing(this,
+                    constraints, 0, measurables.size
+                )
+
+            val layoutWidth: Int
+            val layoutHeight: Int
+            if (orientation == LayoutOrientation.Horizontal) {
+                layoutWidth = measureResult.mainAxisSize
+                layoutHeight = measureResult.crossAxisSize
+            } else {
+                layoutWidth = measureResult.crossAxisSize
+                layoutHeight = measureResult.mainAxisSize
+            }
             return layout(layoutWidth, layoutHeight) {
-                val childrenMainAxisSize = IntArray(measurables.size) { index ->
-                    placeables[index]!!.mainAxisSize()
-                }
-                arrangement(
-                    mainAxisLayoutSize,
-                    childrenMainAxisSize,
-                    layoutDirection,
-                    this@measure,
-                    mainAxisPositions
+                rowColumnMeasureHelper.placeHelper(
+                    this,
+                    measureResult,
+                    0,
+                    layoutDirection
                 )
-
-                placeables.forEachIndexed { index, placeable ->
-                    placeable!!
-                    val parentData = rowColumnParentData[index]
-                    val childCrossAlignment = parentData.crossAxisAlignment ?: crossAxisAlignment
-
-                    val crossAxis = childCrossAlignment.align(
-                        size = crossAxisLayoutSize - placeable.crossAxisSize(),
-                        layoutDirection = if (orientation == Horizontal) {
-                            LayoutDirection.Ltr
-                        } else {
-                            layoutDirection
-                        },
-                        placeable = placeable,
-                        beforeCrossAxisAlignmentLine = beforeCrossAxisAlignmentLine
-                    )
-
-                    if (orientation == Horizontal) {
-                        placeable.place(mainAxisPositions[index], crossAxis)
-                    } else {
-                        placeable.place(crossAxis, mainAxisPositions[index])
-                    }
-                }
             }
         }
 
@@ -349,12 +177,14 @@
          */
         @Stable
         val Center: CrossAxisAlignment = CenterCrossAxisAlignment
+
         /**
          * Place children such that their start edge is aligned to the start edge of the cross
          * axis. TODO(popam): Consider rtl directionality.
          */
         @Stable
         val Start: CrossAxisAlignment = StartCrossAxisAlignment
+
         /**
          * Place children such that their end edge is aligned to the end edge of the cross
          * axis. TODO(popam): Consider rtl directionality.
@@ -528,19 +358,19 @@
         }
 }
 
-private val IntrinsicMeasurable.data: RowColumnParentData?
+internal val IntrinsicMeasurable.rowColumnParentData: RowColumnParentData?
     get() = parentData as? RowColumnParentData
 
-private val RowColumnParentData?.weight: Float
+internal val RowColumnParentData?.weight: Float
     get() = this?.weight ?: 0f
 
-private val RowColumnParentData?.fill: Boolean
+internal val RowColumnParentData?.fill: Boolean
     get() = this?.fill ?: true
 
-private val RowColumnParentData?.crossAxisAlignment: CrossAxisAlignment?
+internal val RowColumnParentData?.crossAxisAlignment: CrossAxisAlignment?
     get() = this?.crossAxisAlignment
 
-private val RowColumnParentData?.isRelative: Boolean
+internal val RowColumnParentData?.isRelative: Boolean
     get() = this.crossAxisAlignment?.isRelative ?: false
 
 private fun MinIntrinsicWidthMeasureBlock(orientation: LayoutOrientation) =
@@ -700,7 +530,7 @@
     var fixedSpace = 0
     var totalWeight = 0f
     children.fastForEach { child ->
-        val weight = child.data.weight
+        val weight = child.rowColumnParentData.weight
         val size = child.mainAxisSize(crossAxisAvailable)
         if (weight == 0f) {
             fixedSpace += size
@@ -724,7 +554,7 @@
     var crossAxisMax = 0
     var totalWeight = 0f
     children.fastForEach { child ->
-        val weight = child.data.weight
+        val weight = child.rowColumnParentData.weight
         if (weight == 0f) {
             // Ask the child how much main axis space it wants to occupy. This cannot be more
             // than the remaining available space.
@@ -750,7 +580,7 @@
     }
 
     children.fastForEach { child ->
-        val weight = child.data.weight
+        val weight = child.rowColumnParentData.weight
         // Now the main axis for weighted children is known, so ask about the cross axis space.
         if (weight > 0f) {
             crossAxisMax = max(
@@ -877,6 +707,7 @@
             it.crossAxisAlignment = CrossAxisAlignment.vertical(vertical)
         }
     }
+
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
         val otherModifier = other as? VerticalAlignModifier ?: return false
@@ -928,6 +759,7 @@
      * subject to the incoming layout constraints.
      */
     Wrap,
+
     /**
      * Maximize the amount of free space by expanding to fill the available space,
      * subject to the incoming layout constraints.
diff --git a/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurementHelper.kt b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurementHelper.kt
new file mode 100644
index 0000000..a828f9f
--- /dev/null
+++ b/compose/foundation/foundation-layout/src/commonMain/kotlin/androidx/compose/foundation/layout/RowColumnMeasurementHelper.kt
@@ -0,0 +1,325 @@
+/*
+ * Copyright 2022 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.layout
+
+import androidx.compose.ui.layout.AlignmentLine
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.Placeable
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.roundToInt
+import kotlin.math.sign
+
+/**
+ * This is a data class that holds the determined width, height of a row,
+ * and information on how to retrieve main axis and cross axis positions.
+ */
+internal class RowColumnMeasureHelperResult(
+    val crossAxisSize: Int,
+    val mainAxisSize: Int,
+    val startIndex: Int,
+    val endIndex: Int,
+    val beforeCrossAxisAlignmentLine: Int,
+    val mainAxisPositions: IntArray,
+)
+
+/**
+ * RowColumnMeasurementHelper
+ * Measures the row and column without placing, useful for reusing row/column logic
+ */
+internal class RowColumnMeasurementHelper(
+    val orientation: LayoutOrientation,
+    val arrangement: (Int, IntArray, LayoutDirection, Density, IntArray) -> Unit,
+    val arrangementSpacing: Dp,
+    val crossAxisSize: SizeMode,
+    val crossAxisAlignment: CrossAxisAlignment,
+    val measurables: List<Measurable>,
+    val placeables: Array<Placeable?>
+) {
+
+    private val rowColumnParentData = Array(measurables.size) {
+        measurables[it].rowColumnParentData
+    }
+
+    fun Placeable.mainAxisSize() =
+        if (orientation == LayoutOrientation.Horizontal) width else height
+
+    fun Placeable.crossAxisSize() =
+        if (orientation == LayoutOrientation.Horizontal) height else width
+
+    /**
+     * Measures the row and column without placing, useful for reusing row/column logic
+     *
+     * @param measureScope The measure scope to retrieve density
+     * @param constraints The desired constraints for the startIndex and endIndex
+     * can hold null items if not measured.
+     * @param startIndex The startIndex (inclusive) when examining measurables, placeable
+     * and parentData
+     * @param endIndex The ending index (exclusive) when examinning measurable, placeable
+     * and parentData
+     */
+    fun measureWithoutPlacing(
+        measureScope: MeasureScope,
+        constraints: Constraints,
+        startIndex: Int,
+        endIndex: Int
+    ): RowColumnMeasureHelperResult {
+        @Suppress("NAME_SHADOWING")
+        val constraints = OrientationIndependentConstraints(constraints, orientation)
+        val arrangementSpacingPx = with(measureScope) {
+            arrangementSpacing.roundToPx()
+        }
+
+        var totalWeight = 0f
+        var fixedSpace = 0
+        var crossAxisSpace = 0
+        var weightChildrenCount = 0
+
+        var anyAlignBy = false
+        val subSize = endIndex - startIndex
+
+        // First measure children with zero weight.
+        var spaceAfterLastNoWeight = 0
+        for (i in startIndex until endIndex) {
+            val child = measurables[i]
+            val parentData = rowColumnParentData[i]
+            val weight = parentData.weight
+
+            if (weight > 0f) {
+                totalWeight += weight
+                ++weightChildrenCount
+            } else {
+                val mainAxisMax = constraints.mainAxisMax
+                val placeable = placeables[i] ?: child.measure(
+                    // Ask for preferred main axis size.
+                    constraints.copy(
+                        mainAxisMin = 0,
+                        mainAxisMax = if (mainAxisMax == Constraints.Infinity) {
+                            Constraints.Infinity
+                        } else {
+                            mainAxisMax - fixedSpace
+                        },
+                        crossAxisMin = 0
+                    ).toBoxConstraints(orientation)
+                )
+                spaceAfterLastNoWeight = min(
+                    arrangementSpacingPx,
+                    mainAxisMax - fixedSpace - placeable.mainAxisSize()
+                )
+                fixedSpace += placeable.mainAxisSize() + spaceAfterLastNoWeight
+                crossAxisSpace = max(crossAxisSpace, placeable.crossAxisSize())
+                anyAlignBy = anyAlignBy || parentData.isRelative
+                placeables[i] = placeable
+            }
+        }
+
+        var weightedSpace = 0
+        if (weightChildrenCount == 0) {
+            // fixedSpace contains an extra spacing after the last non-weight child.
+            fixedSpace -= spaceAfterLastNoWeight
+        } else {
+            // Measure the rest according to their weights in the remaining main axis space.
+            val targetSpace =
+                if (totalWeight > 0f && constraints.mainAxisMax != Constraints.Infinity) {
+                    constraints.mainAxisMax
+                } else {
+                    constraints.mainAxisMin
+                }
+            val remainingToTarget =
+                targetSpace - fixedSpace - arrangementSpacingPx * (weightChildrenCount - 1)
+
+            val weightUnitSpace = if (totalWeight > 0) remainingToTarget / totalWeight else 0f
+            var remainder = remainingToTarget - (startIndex until endIndex).sumOf {
+                (weightUnitSpace * rowColumnParentData[it].weight).roundToInt()
+            }
+
+            for (i in startIndex until endIndex) {
+                if (placeables[i] == null) {
+                    val child = measurables[i]
+                    val parentData = rowColumnParentData[i]
+                    val weight = parentData.weight
+                    check(weight > 0) { "All weights <= 0 should have placeables" }
+                    // After the weightUnitSpace rounding, the total space going to be occupied
+                    // can be smaller or larger than remainingToTarget. Here we distribute the
+                    // loss or gain remainder evenly to the first children.
+                    val remainderUnit = remainder.sign
+                    remainder -= remainderUnit
+                    val childMainAxisSize = max(
+                        0,
+                        (weightUnitSpace * weight).roundToInt() + remainderUnit
+                    )
+                    val placeable = child.measure(
+                        OrientationIndependentConstraints(
+                            if (parentData.fill && childMainAxisSize != Constraints.Infinity) {
+                                childMainAxisSize
+                            } else {
+                                0
+                            },
+                            childMainAxisSize,
+                            0,
+                            constraints.crossAxisMax
+                        ).toBoxConstraints(orientation)
+                    )
+                    weightedSpace += placeable.mainAxisSize()
+                    crossAxisSpace = max(crossAxisSpace, placeable.crossAxisSize())
+                    anyAlignBy = anyAlignBy || parentData.isRelative
+                    placeables[i] = placeable
+                }
+            }
+            weightedSpace = (weightedSpace + arrangementSpacingPx * (weightChildrenCount - 1))
+                .coerceAtMost(constraints.mainAxisMax - fixedSpace)
+        }
+
+        var beforeCrossAxisAlignmentLine = 0
+        var afterCrossAxisAlignmentLine = 0
+        if (anyAlignBy) {
+            for (i in startIndex until endIndex) {
+                val placeable = placeables[i]!!
+                val parentData = rowColumnParentData[i]
+                val alignmentLinePosition = parentData.crossAxisAlignment
+                    ?.calculateAlignmentLinePosition(placeable)
+                if (alignmentLinePosition != null) {
+                    beforeCrossAxisAlignmentLine = max(
+                        beforeCrossAxisAlignmentLine,
+                        alignmentLinePosition.let {
+                            if (it != AlignmentLine.Unspecified) it else 0
+                        }
+                    )
+                    afterCrossAxisAlignmentLine = max(
+                        afterCrossAxisAlignmentLine,
+                        placeable.crossAxisSize() -
+                            (
+                                alignmentLinePosition.let {
+                                    if (it != AlignmentLine.Unspecified) {
+                                        it
+                                    } else {
+                                        placeable.crossAxisSize()
+                                    }
+                                }
+                                )
+                    )
+                }
+            }
+        }
+
+        // Compute the Row or Column size and position the children.
+        val mainAxisLayoutSize = max(fixedSpace + weightedSpace, constraints.mainAxisMin)
+        val crossAxisLayoutSize = if (constraints.crossAxisMax != Constraints.Infinity &&
+            crossAxisSize == SizeMode.Expand
+        ) {
+            constraints.crossAxisMax
+        } else {
+            max(
+                crossAxisSpace,
+                max(
+                    constraints.crossAxisMin,
+                    beforeCrossAxisAlignmentLine + afterCrossAxisAlignmentLine
+                )
+            )
+        }
+        val mainAxisPositions = IntArray(subSize) { 0 }
+        val childrenMainAxisSize = IntArray(subSize) { index ->
+            placeables[index + startIndex]!!.mainAxisSize()
+        }
+
+        return RowColumnMeasureHelperResult(
+            mainAxisSize = mainAxisLayoutSize,
+            crossAxisSize = crossAxisLayoutSize,
+            startIndex = startIndex,
+            endIndex = endIndex,
+            beforeCrossAxisAlignmentLine = beforeCrossAxisAlignmentLine,
+            mainAxisPositions = mainAxisPositions(
+                    mainAxisLayoutSize,
+                    childrenMainAxisSize,
+                    mainAxisPositions,
+                    measureScope
+                ))
+    }
+
+    private fun mainAxisPositions(
+        mainAxisLayoutSize: Int,
+        childrenMainAxisSize: IntArray,
+        mainAxisPositions: IntArray,
+        measureScope: MeasureScope
+    ): IntArray {
+        arrangement(
+            mainAxisLayoutSize,
+            childrenMainAxisSize,
+            measureScope.layoutDirection,
+            measureScope,
+            mainAxisPositions
+        )
+        return mainAxisPositions
+    }
+
+    private fun getCrossAxisPosition(
+        placeable: Placeable,
+        parentData: RowColumnParentData?,
+        crossAxisLayoutSize: Int,
+        layoutDirection: LayoutDirection,
+        beforeCrossAxisAlignmentLine: Int
+    ): Int {
+        val childCrossAlignment = parentData?.crossAxisAlignment ?: crossAxisAlignment
+        return childCrossAlignment.align(
+            size = crossAxisLayoutSize - placeable.crossAxisSize(),
+            layoutDirection = if (orientation == LayoutOrientation.Horizontal) {
+                LayoutDirection.Ltr
+            } else {
+                layoutDirection
+            },
+            placeable = placeable,
+            beforeCrossAxisAlignmentLine = beforeCrossAxisAlignmentLine
+        )
+    }
+    fun placeHelper(
+        placeableScope: Placeable.PlacementScope,
+        measureResult: RowColumnMeasureHelperResult,
+        crossAxisOffset: Int,
+        layoutDirection: LayoutDirection,
+    ) {
+        with(placeableScope) {
+            for (i in measureResult.startIndex until measureResult.endIndex) {
+                val placeable = placeables[i]
+                placeable!!
+                val mainAxisPositions = measureResult.mainAxisPositions
+                val crossAxisPosition = getCrossAxisPosition(
+                    placeable,
+                    (measurables[i].parentData as? RowColumnParentData),
+                    measureResult.crossAxisSize,
+                    layoutDirection,
+                    measureResult.beforeCrossAxisAlignmentLine
+                ) + crossAxisOffset
+                if (orientation == LayoutOrientation.Horizontal) {
+                    placeable.place(
+                        mainAxisPositions[i - measureResult.startIndex],
+                        crossAxisPosition
+                    )
+                } else {
+                    placeable.place(
+                        crossAxisPosition,
+                        mainAxisPositions[i - measureResult.startIndex]
+                    )
+                }
+            }
+        }
+    }
+}
\ No newline at end of file