Do not rerun placement block of layout modifiers when parent moves children

Currently we run all the user provided layout modifiers even when the node is not marked dirty. For example it happens on every scroll of lazy lists. We can skip this work in most of the conditions.
A few other small optimizations are added as part of this cl.

This cl improves LazyListScrollingBenchmark.scrollProgrammatically_noNewItems() by ≈ 11 percent from 1,643,646 ns to 1,477,123 ns.

Test: DrawReorderingTest, GraphicsLayerTest and PlacementLayoutCoordinatesTest
Change-Id: I6b849ea0ddc00110446998ee68023a9eeab0bdeb
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridBeyondBoundsTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridBeyondBoundsTest.kt
index f0315ad..107ee85 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridBeyondBoundsTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/grid/LazyGridBeyondBoundsTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.list.TrackPlacedElement
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -32,7 +33,6 @@
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Left
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Right
 import androidx.compose.ui.layout.ModifierLocalBeyondBoundsLayout
-import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.modifier.modifierLocalConsumer
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
@@ -97,7 +97,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
         }
@@ -117,7 +117,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
         }
@@ -137,7 +137,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
         }
@@ -191,13 +191,13 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
             item {
                 Box(Modifier
                     .size(10.toDp())
-                    .onPlaced { placedItems += 5 }
+                    .trackPlaced(5)
                     .modifierLocalConsumer {
                         beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                     }
@@ -207,11 +207,10 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index + 6 }
+                        .trackPlaced(index + 6)
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act.
         rule.runOnUiThread {
@@ -224,7 +223,6 @@
                     assertThat(placedItems).containsExactly(5, 6, 7, 8)
                     assertThat(visibleItems).containsExactly(5, 6, 7)
                 }
-                placedItems.clear()
                 // Just return true so that we stop as soon as we run this once.
                 // This should result in one extra item being added.
                 true
@@ -251,13 +249,13 @@
                 Box(
                     Modifier
                         .size(itemSizeDp)
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
             item {
                 Box(Modifier
                     .size(itemSizeDp)
-                    .onPlaced { placedItems += 11 }
+                    .trackPlaced(11)
                     .modifierLocalConsumer {
                         beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                     }
@@ -267,11 +265,10 @@
                 Box(
                     Modifier
                         .size(itemSizeDp)
-                        .onPlaced { placedItems += index + 12 }
+                        .trackPlaced(index + 12)
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act.
         rule.runOnUiThread {
@@ -284,7 +281,6 @@
                     assertThat(placedItems).containsExactly(10, 11, 12, 13, 14, 15, 16)
                     assertThat(visibleItems).containsExactly(10, 11, 12, 13, 14, 15)
                 }
-                placedItems.clear()
                 // Just return true so that we stop as soon as we run this once.
                 // This should result in one extra item being added.
                 true
@@ -307,14 +303,14 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
             item {
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += 5 }
+                        .trackPlaced(5)
                         .modifierLocalConsumer {
                             beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                         }
@@ -324,17 +320,15 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index + 6 }
+                        .trackPlaced(index + 6)
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act.
         rule.runOnUiThread {
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 if (--extraItemCount > 0) {
-                    placedItems.clear()
                     // Return null to continue the search.
                     null
                 } else {
@@ -346,7 +340,6 @@
                         assertThat(placedItems).containsExactly(5, 6, 7, 8, 9)
                         assertThat(visibleItems).containsExactly(5, 6, 7)
                     }
-                    placedItems.clear()
                     // Return true to stop the search.
                     true
                 }
@@ -368,7 +361,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
             item {
@@ -378,26 +371,22 @@
                         .modifierLocalConsumer {
                             beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                         }
-                        .onPlaced { placedItems += 5 }
+                        .trackPlaced(5)
                 )
             }
             items(5) { index ->
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced {
-                            placedItems += index + 6
-                        }
+                        .trackPlaced(index + 6)
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act.
         rule.runOnUiThread {
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 if (hasMoreContent) {
-                    placedItems.clear()
                     // Just return null so that we keep adding more items till we reach the end.
                     null
                 } else {
@@ -409,7 +398,6 @@
                         assertThat(placedItems).containsExactly(5, 6, 7, 8, 9, 10)
                         assertThat(visibleItems).containsExactly(5, 6, 7)
                     }
-                    placedItems.clear()
                     // Return true to end the search.
                     true
                 }
@@ -431,14 +419,14 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
             item {
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += 5 }
+                        .trackPlaced(5)
                         .modifierLocalConsumer {
                             beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                         }
@@ -448,14 +436,13 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index + 6 }
+                        .trackPlaced(index + 6)
                 )
             }
         }
         rule.runOnIdle {
             assertThat(placedItems).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
-            placedItems.clear()
         }
 
         // Act.
@@ -477,7 +464,6 @@
                         }
                     }
                 }
-                placedItems.clear()
                 // Just return true so that we stop as soon as we run this once.
                 // This should result in one extra item being added.
                 true
@@ -509,9 +495,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced {
-                            placedItems += index
-                        }
+                        .trackPlaced(index)
                 )
             }
             item {
@@ -521,20 +505,17 @@
                         .modifierLocalConsumer {
                             beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                         }
-                        .onPlaced { placedItems += 5 }
+                        .trackPlaced(5)
                 )
             }
             items(5) { index ->
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced {
-                            placedItems += index + 6
-                        }
+                        .trackPlaced(index + 6)
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act.
         var count = 0
@@ -542,7 +523,6 @@
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 // Assert that we don't keep iterating when there is no ending condition.
                 assertThat(count++).isLessThan(lazyGridState.layoutInfo.totalItemsCount)
-                placedItems.clear()
                 // Always return null to continue the search.
                 null
             }
@@ -636,4 +616,7 @@
     private fun unsupportedDirection(): Nothing = error(
         "Lazy list does not support beyond bounds layout for the specified direction"
     )
+
+    private fun Modifier.trackPlaced(index: Int): Modifier =
+        this then TrackPlacedElement(placedItems, index)
 }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsAndExtraItemsTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsAndExtraItemsTest.kt
index 559f3bb..3be7e19 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsAndExtraItemsTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsAndExtraItemsTest.kt
@@ -31,7 +31,6 @@
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Left
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Right
 import androidx.compose.ui.layout.ModifierLocalBeyondBoundsLayout
-import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.modifier.modifierLocalConsumer
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.unit.LayoutDirection
@@ -51,12 +50,12 @@
     private val beyondBoundsLayoutDirection = config.beyondBoundsLayoutDirection
     private val reverseLayout = config.reverseLayout
     private val layoutDirection = config.layoutDirection
+    private val placedItems = mutableSetOf<Int>()
 
     @OptIn(ExperimentalComposeUiApi::class)
     @Test
     fun verifyItemsArePlacedBeforeBeyondBoundsItems_oneBeyondBoundItem() {
         // Arrange
-        val placedItems = mutableSetOf<Int>()
         var beyondBoundsLayout: BeyondBoundsLayout? = null
         val lazyListState = LazyListState()
         rule.setContent {
@@ -71,14 +70,14 @@
                         Box(
                             Modifier
                                 .size(10.dp)
-                                .onPlaced { placedItems += index }
+                                .trackPlaced(index)
                         )
                     }
                     item {
                         Box(
                             Modifier
                                 .size(10.dp)
-                                .onPlaced { placedItems += 5 }
+                                .trackPlaced(5)
                                 .modifierLocalConsumer {
                                     beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                                 }
@@ -88,14 +87,13 @@
                         Box(
                             Modifier
                                 .size(10.dp)
-                                .onPlaced { placedItems += index + 6 }
+                                .trackPlaced(index + 6)
                         )
                     }
                 }
             }
         }
         rule.runOnIdle { runBlocking { lazyListState.scrollToItem(5) } }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act
         rule.runOnUiThread {
@@ -107,7 +105,6 @@
                     assertThat(placedItems).containsAtLeast(4, 5, 6, 7, 8, 9)
                 }
                 assertThat(lazyListState.visibleItems).containsAtLeast(5, 6, 7)
-                placedItems.clear()
                 true
             }
         }
@@ -123,7 +120,6 @@
     @Test
     fun verifyItemsArePlacedBeforeBeyondBoundsItems_twoBeyondBoundItem() {
         // Arrange
-        val placedItems = mutableSetOf<Int>()
         var beyondBoundsLayout: BeyondBoundsLayout? = null
         val lazyListState = LazyListState()
         var extraItemCount = 2
@@ -139,14 +135,14 @@
                         Box(
                             Modifier
                                 .size(10.dp)
-                                .onPlaced { placedItems += index }
+                                .trackPlaced(index)
                         )
                     }
                     item {
                         Box(
                             Modifier
                                 .size(10.dp)
-                                .onPlaced { placedItems += 5 }
+                                .trackPlaced(5)
                                 .modifierLocalConsumer {
                                     beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                                 }
@@ -156,20 +152,18 @@
                         Box(
                             Modifier
                                 .size(10.dp)
-                                .onPlaced { placedItems += index + 6 }
+                                .trackPlaced(index + 6)
                         )
                     }
                 }
             }
         }
         rule.runOnIdle { runBlocking { lazyListState.scrollToItem(5) } }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act
         rule.runOnUiThread {
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 if (--extraItemCount > 0) {
-                    placedItems.clear()
                     // Return null to continue the search.
                     null
                 } else {
@@ -180,7 +174,6 @@
                         assertThat(placedItems).containsAtLeast(4, 5, 6, 7, 8, 9, 10)
                     }
                     assertThat(lazyListState.visibleItems).containsAtLeast(5, 6, 7)
-                    placedItems.clear()
                     true
                 }
             }
@@ -249,4 +242,7 @@
         Before -> true
         else -> error("Unsupported BeyondBoundsDirection")
     }
+
+    private fun Modifier.trackPlaced(index: Int): Modifier =
+        this then TrackPlacedElement(placedItems, index)
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsTest.kt
index d431ea4..4e0a1ed 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/list/LazyListBeyondBoundsTest.kt
@@ -36,9 +36,12 @@
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Below
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Left
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Right
+import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.ModifierLocalBeyondBoundsLayout
-import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.modifier.modifierLocalConsumer
+import androidx.compose.ui.node.LayoutAwareModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -102,7 +105,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
         }
@@ -122,7 +125,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
         }
@@ -142,7 +145,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
         }
@@ -196,13 +199,13 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
             item {
                 Box(Modifier
                     .size(10.toDp())
-                    .onPlaced { placedItems += 5 }
+                    .trackPlaced(5)
                     .modifierLocalConsumer {
                         beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                     }
@@ -212,11 +215,10 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index + 6 }
+                        .trackPlaced(index + 6)
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act.
         rule.runOnUiThread {
@@ -229,7 +231,6 @@
                     assertThat(placedItems).containsExactly(5, 6, 7, 8)
                     assertThat(visibleItems).containsExactly(5, 6, 7)
                 }
-                placedItems.clear()
                 // Just return true so that we stop as soon as we run this once.
                 // This should result in one extra item being added.
                 true
@@ -252,14 +253,14 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
             item {
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += 5 }
+                        .trackPlaced(5)
                         .modifierLocalConsumer {
                             beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                         }
@@ -269,17 +270,15 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index + 6 }
+                        .trackPlaced(index + 6)
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act.
         rule.runOnUiThread {
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 if (--extraItemCount > 0) {
-                    placedItems.clear()
                     // Return null to continue the search.
                     null
                 } else {
@@ -291,7 +290,6 @@
                         assertThat(placedItems).containsExactly(5, 6, 7, 8, 9)
                         assertThat(visibleItems).containsExactly(5, 6, 7)
                     }
-                    placedItems.clear()
                     // Return true to stop the search.
                     true
                 }
@@ -313,7 +311,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
             item {
@@ -323,26 +321,22 @@
                         .modifierLocalConsumer {
                             beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                         }
-                        .onPlaced { placedItems += 5 }
+                        .trackPlaced(5)
                 )
             }
             items(5) { index ->
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced {
-                            placedItems += index + 6
-                        }
+                        .trackPlaced(index + 6)
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act.
         rule.runOnUiThread {
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 if (hasMoreContent) {
-                    placedItems.clear()
                     // Just return null so that we keep adding more items till we reach the end.
                     null
                 } else {
@@ -354,7 +348,6 @@
                         assertThat(placedItems).containsExactly(5, 6, 7, 8, 9, 10)
                         assertThat(visibleItems).containsExactly(5, 6, 7)
                     }
-                    placedItems.clear()
                     // Return true to end the search.
                     true
                 }
@@ -376,14 +369,14 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
             item {
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += 5 }
+                        .trackPlaced(5)
                         .modifierLocalConsumer {
                             beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                         }
@@ -393,14 +386,13 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index + 6 }
+                        .trackPlaced(index + 6)
                 )
             }
         }
         rule.runOnIdle {
             assertThat(placedItems).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
-            placedItems.clear()
         }
 
         // Act.
@@ -422,7 +414,6 @@
                         }
                     }
                 }
-                placedItems.clear()
                 // Just return true so that we stop as soon as we run this once.
                 // This should result in one extra item being added.
                 true
@@ -454,9 +445,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced {
-                            placedItems += index
-                        }
+                        .trackPlaced(index)
                 )
             }
             item {
@@ -466,20 +455,17 @@
                         .modifierLocalConsumer {
                             beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                         }
-                        .onPlaced { placedItems += 5 }
+                        .trackPlaced(5)
                 )
             }
             items(5) { index ->
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced {
-                            placedItems += index + 6
-                        }
+                        .trackPlaced(index + 6)
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act.
         var count = 0
@@ -487,7 +473,6 @@
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 // Assert that we don't keep iterating when there is no ending condition.
                 assertThat(count++).isLessThan(lazyListState.layoutInfo.totalItemsCount)
-                placedItems.clear()
                 // Always return null to continue the search.
                 null
             }
@@ -576,4 +561,37 @@
     private fun unsupportedDirection(): Nothing = error(
         "Lazy list does not support beyond bounds layout for the specified direction"
     )
+
+    private fun Modifier.trackPlaced(index: Int): Modifier =
+        this then TrackPlacedElement(placedItems, index)
+}
+
+internal data class TrackPlacedElement(
+    var placedItems: MutableSet<Int>,
+    var index: Int
+) : ModifierNodeElement<TrackPlacedNode>() {
+    override fun create() = TrackPlacedNode(placedItems, index)
+
+    override fun update(node: TrackPlacedNode) {
+        node.placedItems = placedItems
+        node.index = index
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "trackPlaced"
+        properties["index"] = index
+    }
+}
+
+internal class TrackPlacedNode(
+    var placedItems: MutableSet<Int>,
+    var index: Int
+) : LayoutAwareModifierNode, Modifier.Node() {
+    override fun onPlaced(coordinates: LayoutCoordinates) {
+        placedItems += index
+    }
+
+    override fun onDetach() {
+        placedItems -= index
+    }
 }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridBeyondBoundsTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridBeyondBoundsTest.kt
index 1b82ba8..116e9ae 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridBeyondBoundsTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridBeyondBoundsTest.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.list.TrackPlacedElement
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -32,7 +33,6 @@
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Left
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Right
 import androidx.compose.ui.layout.ModifierLocalBeyondBoundsLayout
-import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.modifier.modifierLocalConsumer
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
@@ -97,7 +97,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
         }
@@ -117,7 +117,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
         }
@@ -137,7 +137,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
         }
@@ -191,13 +191,13 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
             item {
                 Box(Modifier
                     .size(10.toDp())
-                    .onPlaced { placedItems += 5 }
+                    .trackPlaced(5)
                     .modifierLocalConsumer {
                         beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                     }
@@ -207,11 +207,10 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index + 6 }
+                        .trackPlaced(index + 6)
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act.
         rule.runOnUiThread {
@@ -224,7 +223,6 @@
                     assertThat(placedItems).containsExactly(5, 6, 7, 8)
                     assertThat(visibleItems).containsExactly(5, 6, 7)
                 }
-                placedItems.clear()
                 // Just return true so that we stop as soon as we run this once.
                 // This should result in one extra item being added.
                 true
@@ -251,13 +249,13 @@
                 Box(
                     Modifier
                         .size(itemSizeDp)
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
             item {
                 Box(Modifier
                     .size(itemSizeDp)
-                    .onPlaced { placedItems += 11 }
+                    .trackPlaced(11)
                     .modifierLocalConsumer {
                         beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                     }
@@ -267,11 +265,10 @@
                 Box(
                     Modifier
                         .size(itemSizeDp)
-                        .onPlaced { placedItems += index + 12 }
+                        .trackPlaced(index + 12)
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act.
         rule.runOnUiThread {
@@ -284,7 +281,6 @@
                     assertThat(placedItems).containsExactly(10, 11, 12, 13, 14, 15, 16)
                     assertThat(visibleItems).containsExactly(10, 11, 12, 13, 14, 15)
                 }
-                placedItems.clear()
                 // Just return true so that we stop as soon as we run this once.
                 // This should result in one extra item being added.
                 true
@@ -319,13 +315,13 @@
                 Box(
                     Modifier
                         .size(itemSizeDp * if (index % 2 == 0) 2f else 1f)
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
             item(span = StaggeredGridItemSpan.FullLine) {
                 Box(Modifier
                     .size(itemSizeDp)
-                    .onPlaced { placedItems += 4 }
+                    .trackPlaced(4)
                     .modifierLocalConsumer {
                         beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                     }
@@ -335,11 +331,10 @@
                 Box(
                     Modifier
                         .size(itemSizeDp * if (index % 2 == 0) 2f else 1f)
-                        .onPlaced { placedItems += index + 5 }
+                        .trackPlaced(index + 5)
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act.
         rule.runOnUiThread {
@@ -352,7 +347,6 @@
                     assertThat(placedItems).containsExactly(4, 5, 6, 7, 8)
                     assertThat(visibleItems).containsExactly(4, 5, 6, 7)
                 }
-                placedItems.clear()
                 // Just return true so that we stop as soon as we run this once.
                 // This should result in one extra item being added.
                 true
@@ -375,14 +369,14 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
             item {
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += 5 }
+                        .trackPlaced(5)
                         .modifierLocalConsumer {
                             beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                         }
@@ -392,17 +386,15 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index + 6 }
+                        .trackPlaced(index + 6)
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act.
         rule.runOnUiThread {
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 if (--extraItemCount > 0) {
-                    placedItems.clear()
                     // Return null to continue the search.
                     null
                 } else {
@@ -414,7 +406,6 @@
                         assertThat(placedItems).containsExactly(5, 6, 7, 8, 9)
                         assertThat(visibleItems).containsExactly(5, 6, 7)
                     }
-                    placedItems.clear()
                     // Return true to stop the search.
                     true
                 }
@@ -436,7 +427,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
             item {
@@ -446,26 +437,22 @@
                         .modifierLocalConsumer {
                             beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                         }
-                        .onPlaced { placedItems += 5 }
+                        .trackPlaced(5)
                 )
             }
             items(5) { index ->
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced {
-                            placedItems += index + 6
-                        }
+                        .trackPlaced(index + 6)
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act.
         rule.runOnUiThread {
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 if (hasMoreContent) {
-                    placedItems.clear()
                     // Just return null so that we keep adding more items till we reach the end.
                     null
                 } else {
@@ -477,7 +464,6 @@
                         assertThat(placedItems).containsExactly(5, 6, 7, 8, 9, 10)
                         assertThat(visibleItems).containsExactly(5, 6, 7)
                     }
-                    placedItems.clear()
                     // Return true to end the search.
                     true
                 }
@@ -499,14 +485,14 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
             item {
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += 5 }
+                        .trackPlaced(5)
                         .modifierLocalConsumer {
                             beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                         }
@@ -516,14 +502,13 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index + 6 }
+                        .trackPlaced(index + 6)
                 )
             }
         }
         rule.runOnIdle {
             assertThat(placedItems).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
-            placedItems.clear()
         }
 
         // Act.
@@ -545,7 +530,6 @@
                         }
                     }
                 }
-                placedItems.clear()
                 // Just return true so that we stop as soon as we run this once.
                 // This should result in one extra item being added.
                 true
@@ -577,9 +561,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced {
-                            placedItems += index
-                        }
+                        .trackPlaced(index)
                 )
             }
             item {
@@ -589,20 +571,17 @@
                         .modifierLocalConsumer {
                             beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                         }
-                        .onPlaced { placedItems += 5 }
+                        .trackPlaced(5)
                 )
             }
             items(5) { index ->
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced {
-                            placedItems += index + 6
-                        }
+                        .trackPlaced(index + 6)
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act.
         var count = 0
@@ -610,7 +589,6 @@
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 // Assert that we don't keep iterating when there is no ending condition.
                 assertThat(count++).isLessThan(lazyStaggeredGridState.layoutInfo.totalItemsCount)
-                placedItems.clear()
                 // Always return null to continue the search.
                 null
             }
@@ -704,4 +682,7 @@
     private fun unsupportedDirection(): Nothing = error(
         "Lazy list does not support beyond bounds layout for the specified direction"
     )
+
+    private fun Modifier.trackPlaced(index: Int): Modifier =
+        this then TrackPlacedElement(placedItems, index)
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawReorderingTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawReorderingTest.kt
index 57531e5..ebea073 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawReorderingTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/DrawReorderingTest.kt
@@ -43,6 +43,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
 import org.junit.Assert.assertNotNull
@@ -1040,20 +1041,28 @@
 
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun placingInDifferentOrderTriggersRedraw() {
+    fun changingPlaceOrderInLayout() {
         var reverseOrder by mutableStateOf(false)
+        var childRelayoutCount = 0
+        val childRelayoutModifier = Modifier.layout { measurable, constraints ->
+            val placeable = measurable.measure(constraints)
+            layout(placeable.width, placeable.height) {
+                childRelayoutCount++
+                placeable.place(0, 0)
+            }
+        }
         rule.runOnUiThread {
             activity.setContent {
                 Layout(
                     content = {
-                        FixedSize(30) {
+                        FixedSize(30, childRelayoutModifier) {
                             FixedSize(
                                 10,
                                 Modifier.padding(10)
                                     .background(Color.White)
                             )
                         }
-                        FixedSize(30) {
+                        FixedSize(30, childRelayoutModifier) {
                             FixedSize(
                                 30,
                                 Modifier.background(Color.Red)
@@ -1084,6 +1093,7 @@
         rule.runOnUiThread {
             drawLatch = CountDownLatch(1)
             reverseOrder = true
+            childRelayoutCount = 0
         }
 
         rule.validateSquareColors(
@@ -1092,6 +1102,74 @@
             size = 10,
             drawLatch = drawLatch
         )
+        rule.runOnUiThread {
+            // changing drawing order doesn't require child's layer block rerun
+            assertThat(childRelayoutCount).isEqualTo(0)
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun changingZIndexInLayout() {
+        var zIndex by mutableStateOf(1f)
+        var childRelayoutCount = 0
+        val childRelayoutModifier = Modifier.layout { measurable, constraints ->
+            val placeable = measurable.measure(constraints)
+            layout(placeable.width, placeable.height) {
+                childRelayoutCount++
+                placeable.place(0, 0)
+            }
+        }
+        rule.runOnUiThread {
+            activity.setContent {
+                Layout(
+                    content = {
+                        FixedSize(30, childRelayoutModifier) {
+                            FixedSize(
+                                10,
+                                Modifier.padding(10)
+                                    .background(Color.White)
+                            )
+                        }
+                        FixedSize(30, childRelayoutModifier) {
+                            FixedSize(
+                                30,
+                                Modifier.background(Color.Red)
+                            )
+                        }
+                    },
+                    modifier = Modifier.drawLatchModifier()
+                ) { measurables, _ ->
+                    val newConstraints = Constraints.fixed(30, 30)
+                    val placeables = measurables.map { m ->
+                        m.measure(newConstraints)
+                    }
+                    layout(newConstraints.maxWidth, newConstraints.maxWidth) {
+                        placeables[0].place(0, 0)
+                        placeables[1].place(0, 0, zIndex)
+                    }
+                }
+            }
+        }
+
+        assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
+
+        rule.runOnUiThread {
+            drawLatch = CountDownLatch(1)
+            zIndex = -1f
+            childRelayoutCount = 0
+        }
+
+        rule.validateSquareColors(
+            outerColor = Color.Red,
+            innerColor = Color.White,
+            size = 10,
+            drawLatch = drawLatch
+        )
+        rule.runOnUiThread {
+            // changing zIndex doesn't require child's layer block rerun
+            assertThat(childRelayoutCount).isEqualTo(0)
+        }
     }
 
     fun Modifier.drawLatchModifier() = drawBehind { drawLatch.countDown() }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/GraphicsLayerTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/GraphicsLayerTest.kt
index ba80c91..cff9158 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/GraphicsLayerTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/GraphicsLayerTest.kt
@@ -121,9 +121,12 @@
         rule.setContent {
             FixedSize(
                 30,
-                Modifier.padding(10).graphicsLayer().onGloballyPositioned {
-                    coords = it
-                }
+                Modifier
+                    .padding(10)
+                    .graphicsLayer()
+                    .onGloballyPositioned {
+                        coords = it
+                    }
             ) { /* no-op */ }
         }
 
@@ -148,9 +151,11 @@
             Padding(10) {
                 FixedSize(
                     10,
-                    Modifier.graphicsLayer(scaleX = 2f, scaleY = 3f).onGloballyPositioned {
-                        coords = it
-                    }
+                    Modifier
+                        .graphicsLayer(scaleX = 2f, scaleY = 3f)
+                        .onGloballyPositioned {
+                            coords = it
+                        }
                 ) {
                 }
             }
@@ -171,9 +176,11 @@
             Padding(10) {
                 FixedSize(
                     10,
-                    Modifier.scale(scaleX = 2f, scaleY = 3f).onGloballyPositioned {
-                        coords = it
-                    }
+                    Modifier
+                        .scale(scaleX = 2f, scaleY = 3f)
+                        .onGloballyPositioned {
+                            coords = it
+                        }
                 ) {
                 }
             }
@@ -194,9 +201,11 @@
             Padding(10) {
                 FixedSize(
                     10,
-                    Modifier.scale(scale = 2f).onGloballyPositioned {
-                        coords = it
-                    }
+                    Modifier
+                        .scale(scale = 2f)
+                        .onGloballyPositioned {
+                            coords = it
+                        }
                 ) {
                 }
             }
@@ -217,9 +226,11 @@
             Padding(10) {
                 FixedSize(
                     10,
-                    Modifier.graphicsLayer(scaleY = 3f, rotationZ = 90f).onGloballyPositioned {
-                        coords = it
-                    }
+                    Modifier
+                        .graphicsLayer(scaleY = 3f, rotationZ = 90f)
+                        .onGloballyPositioned {
+                            coords = it
+                        }
                 ) {
                 }
             }
@@ -240,9 +251,11 @@
             Padding(10) {
                 FixedSize(
                     10,
-                    Modifier.rotate(90f).onGloballyPositioned {
-                        coords = it
-                    }
+                    Modifier
+                        .rotate(90f)
+                        .onGloballyPositioned {
+                            coords = it
+                        }
                 ) {
                 }
             }
@@ -263,12 +276,14 @@
             Padding(10) {
                 FixedSize(
                     10,
-                    Modifier.graphicsLayer(
-                        rotationZ = 90f,
-                        transformOrigin = TransformOrigin(1.0f, 1.0f)
-                    ).onGloballyPositioned {
-                        coords = it
-                    }
+                    Modifier
+                        .graphicsLayer(
+                            rotationZ = 90f,
+                            transformOrigin = TransformOrigin(1.0f, 1.0f)
+                        )
+                        .onGloballyPositioned {
+                            coords = it
+                        }
                 )
             }
         }
@@ -288,12 +303,14 @@
             Padding(10) {
                 FixedSize(
                     10,
-                    Modifier.graphicsLayer(
-                        translationX = 5.0f,
-                        translationY = 8.0f
-                    ).onGloballyPositioned {
-                        coords = it
-                    }
+                    Modifier
+                        .graphicsLayer(
+                            translationX = 5.0f,
+                            translationY = 8.0f
+                        )
+                        .onGloballyPositioned {
+                            coords = it
+                        }
                 )
             }
         }
@@ -314,9 +331,11 @@
                 FixedSize(10, Modifier.graphicsLayer(clip = true)) {
                     FixedSize(
                         10,
-                        Modifier.graphicsLayer(scaleX = 2f).onGloballyPositioned {
-                            coords = it
-                        }
+                        Modifier
+                            .graphicsLayer(scaleX = 2f)
+                            .onGloballyPositioned {
+                                coords = it
+                            }
                     ) {
                     }
                 }
@@ -339,18 +358,20 @@
         rule.setContent {
             with(LocalDensity.current) {
                 Box(
-                    Modifier.requiredSize(25.toDp())
+                    Modifier
+                        .requiredSize(25.toDp())
                         .graphicsLayer(
                             rotationZ = 30f,
                             clip = true
                         )
                 ) {
                     Box(
-                        Modifier.graphicsLayer(
-                            rotationZ = 90f,
-                            transformOrigin = TransformOrigin(0f, 1f),
-                            clip = true
-                        )
+                        Modifier
+                            .graphicsLayer(
+                                rotationZ = 90f,
+                                transformOrigin = TransformOrigin(0f, 1f),
+                                clip = true
+                            )
                             .requiredSize(20.toDp(), 10.toDp())
                             .align(AbsoluteAlignment.TopLeft)
                             .onGloballyPositioned {
@@ -395,7 +416,9 @@
         if (Build.VERSION.SDK_INT == 28) return // b/260095151
         val testTag = "parent"
         rule.setContent {
-            Box(modifier = Modifier.testTag(testTag).wrapContentSize()) {
+            Box(modifier = Modifier
+                .testTag(testTag)
+                .wrapContentSize()) {
                 Box(
                     modifier = Modifier
                         .requiredSize(100.dp)
@@ -431,7 +454,10 @@
         }
         val tag = "testTag"
         rule.setContent {
-            Box(modifier = Modifier.testTag(tag).requiredSize(100.dp).background(Color.Blue)) {
+            Box(modifier = Modifier
+                .testTag(tag)
+                .requiredSize(100.dp)
+                .background(Color.Blue)) {
                 Box(
                     modifier = Modifier
                         .matchParentSize()
@@ -454,9 +480,11 @@
                 FixedSize(10, Modifier.graphicsLayer(clip = true)) {
                     FixedSize(
                         10,
-                        Modifier.padding(20).onGloballyPositioned {
-                            coords = it
-                        }
+                        Modifier
+                            .padding(20)
+                            .onGloballyPositioned {
+                                coords = it
+                            }
                     ) {
                     }
                 }
@@ -493,7 +521,8 @@
         drawBlock: DrawScope.() -> Unit
     ) {
         Box(
-            Modifier.testTag(tag)
+            Modifier
+                .testTag(tag)
                 .size(size)
                 .background(Color.Black)
                 .graphicsLayer {
@@ -606,7 +635,11 @@
             val size = (sizePx / density)
             val squareSize = (squarePx / density)
             offset = (20f / density).roundToInt()
-            Box(Modifier.size(size.dp).background(Color.LightGray).testTag(testTag)) {
+            Box(
+                Modifier
+                    .size(size.dp)
+                    .background(Color.LightGray)
+                    .testTag(testTag)) {
                 Box(
                     Modifier
                         .layout { measurable, constraints ->
@@ -664,7 +697,11 @@
             val size = (sizePx / density)
             val squareSize = (squarePx / density)
             offset = (20f / density).roundToInt()
-            Box(Modifier.size(size.dp).background(Color.LightGray).testTag(testTag)) {
+            Box(
+                Modifier
+                    .size(size.dp)
+                    .background(Color.LightGray)
+                    .testTag(testTag)) {
                 Box(
                     Modifier
                         .layout { measurable, constraints ->
@@ -689,7 +726,8 @@
                             drawRect(
                                 color = Color.Red,
                                 topLeft = Offset(-width, -height),
-                                size = Size(width * 2, height * 2))
+                                size = Size(width * 2, height * 2)
+                            )
                         }
                 )
             }
@@ -738,7 +776,11 @@
             val size = (sizePx / density)
             val squareSize = (squarePx / density)
             offset = (20f / density).roundToInt()
-            Box(Modifier.size(size.dp).background(Color.LightGray).testTag(testTag)) {
+            Box(
+                Modifier
+                    .size(size.dp)
+                    .background(Color.LightGray)
+                    .testTag(testTag)) {
                 Box(
                     Modifier
                         .layout { measurable, constraints ->
@@ -764,7 +806,8 @@
                             drawRect(
                                 color = Color.Red,
                                 topLeft = Offset(-width, -height),
-                                size = Size(width * 2, height * 2))
+                                size = Size(width * 2, height * 2)
+                            )
                         }
                 )
             }
@@ -815,7 +858,11 @@
             val size = (sizePx / density)
             val squareSize = (squarePx / density)
             offset = (20f / density).roundToInt()
-            Box(Modifier.size(size.dp).background(Color.LightGray).testTag(testTag)) {
+            Box(
+                Modifier
+                    .size(size.dp)
+                    .background(Color.LightGray)
+                    .testTag(testTag)) {
                 Box(
                     Modifier
                         .layout { measurable, constraints ->
@@ -845,7 +892,8 @@
                             drawRect(
                                 color = Color.Red,
                                 topLeft = Offset(-width, -height),
-                                size = Size(width * 2, height * 2))
+                                size = Size(width * 2, height * 2)
+                            )
                         }
                 )
             }
@@ -896,7 +944,11 @@
             val size = (sizePx / density)
             val squareSize = (squarePx / density)
             offset = (20f / density).roundToInt()
-            Box(Modifier.size(size.dp).background(Color.LightGray).testTag(testTag)) {
+            Box(
+                Modifier
+                    .size(size.dp)
+                    .background(Color.LightGray)
+                    .testTag(testTag)) {
                 Box(
                     Modifier
                         .layout { measurable, constraints ->
@@ -971,7 +1023,11 @@
             val size = (sizePx / density)
             val squareSize = (squarePx / density)
             offset = (20f / density).roundToInt()
-            Box(Modifier.size(size.dp).background(Color.LightGray).testTag(testTag)) {
+            Box(
+                Modifier
+                    .size(size.dp)
+                    .background(Color.LightGray)
+                    .testTag(testTag)) {
                 Box(
                     Modifier
                         .layout { measurable, constraints ->
@@ -1044,7 +1100,10 @@
         rule.setContent {
             FixedSize(
                 5,
-                Modifier.graphicsLayer().testTag("tag").background(color)
+                Modifier
+                    .graphicsLayer()
+                    .testTag("tag")
+                    .background(color)
             )
         }
 
@@ -1092,14 +1151,18 @@
             Layout(
                 content = {
                     Box(
-                        Modifier.fillMaxSize().clickable {
-                            firstClicked = true
-                        }
+                        Modifier
+                            .fillMaxSize()
+                            .clickable {
+                                firstClicked = true
+                            }
                     )
                     Box(
-                        Modifier.fillMaxSize().clickable {
-                            secondClicked = true
-                        }
+                        Modifier
+                            .fillMaxSize()
+                            .clickable {
+                                secondClicked = true
+                            }
                     )
                 },
                 modifier = Modifier.testTag("layout")
@@ -1167,7 +1230,8 @@
         rule.setContent {
             Canvas(
                 modifier =
-                Modifier.testTag(tag)
+                Modifier
+                    .testTag(tag)
                     .size((dimen / LocalDensity.current.density).dp)
                     .background(Color.Black)
                     .graphicsLayer(
@@ -1209,7 +1273,8 @@
         rule.setContent {
             Canvas(
                 modifier =
-                Modifier.testTag(tag)
+                Modifier
+                    .testTag(tag)
                     .size((dimen / LocalDensity.current.density).dp)
                     .background(Color.LightGray)
                     .graphicsLayer(
@@ -1244,7 +1309,8 @@
         rule.setContent {
             Canvas(
                 modifier =
-                Modifier.testTag(tag)
+                Modifier
+                    .testTag(tag)
                     .size((dimen / LocalDensity.current.density).dp)
                     .background(Color.Black)
                     .graphicsLayer(
@@ -1360,7 +1426,10 @@
         val size = 100
         rule.setContent {
             val sizeDp = with(LocalDensity.current) { size.toDp() }
-            LazyColumn(Modifier.testTag("lazy").background(Color.Blue)) {
+            LazyColumn(
+                Modifier
+                    .testTag("lazy")
+                    .background(Color.Blue)) {
                 items(4) {
                     Box(
                         Modifier
@@ -1512,7 +1581,10 @@
             }
         }
         rule.setContent {
-            Box(Modifier.graphicsLayer(translationX = translationX).then(layoutModifier)) {
+            Box(
+                Modifier
+                    .graphicsLayer(translationX = translationX)
+                    .then(layoutModifier)) {
                 Layout(Modifier.onGloballyPositioned { coordinates = it }) { _, _ ->
                     layout(10, 10) {}
                 }
@@ -1550,7 +1622,10 @@
             }
         }
         rule.setContent {
-            Box(Modifier.graphicsLayer(lambda).then(layoutModifier)) {
+            Box(
+                Modifier
+                    .graphicsLayer(lambda)
+                    .then(layoutModifier)) {
                 Layout(Modifier.onGloballyPositioned { coordinates = it }) { _, _ ->
                     layout(10, 10) {}
                 }
@@ -1572,4 +1647,92 @@
             assertEquals(0, relayoutCount)
         }
     }
+
+    @Test
+    fun addingLayerForChildDoesntTriggerChildRelayout() {
+        var relayoutCount = 0
+        var modifierRelayoutCount = 0
+        var needLayer by mutableStateOf(false)
+        var layerBlockCalled = false
+        rule.setContent {
+            Layout(content = {
+                Layout(
+                    modifier = Modifier.layout { measurable, constraints ->
+                        val placeable = measurable.measure(constraints)
+                        layout(placeable.width, placeable.height) {
+                            modifierRelayoutCount++
+                            placeable.place(0, 0)
+                        }
+                    }
+                ) { _, _ ->
+                    layout(10, 10) {
+                        relayoutCount++
+                    }
+                }
+            }) { measurables, constraints ->
+                val placeable = measurables[0].measure(constraints)
+                layout(placeable.width, placeable.height) {
+                    if (needLayer) {
+                        placeable.placeWithLayer(0, 0) {
+                            layerBlockCalled = true
+                        }
+                    } else {
+                        placeable.place(0, 0)
+                    }
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            relayoutCount = 0
+            modifierRelayoutCount = 0
+            needLayer = true
+        }
+
+        rule.runOnIdle {
+            assertEquals(0, relayoutCount)
+            assertTrue(layerBlockCalled)
+            assertEquals(0, modifierRelayoutCount)
+        }
+    }
+
+    @Test
+    fun movingChildsLayerDoesntTriggerChildRelayout() {
+        var relayoutCount = 0
+        var modifierRelayoutCount = 0
+        var position by mutableStateOf(0)
+        rule.setContent {
+            Layout(content = {
+                Layout(
+                    modifier = Modifier.layout { measurable, constraints ->
+                        val placeable = measurable.measure(constraints)
+                        layout(placeable.width, placeable.height) {
+                            modifierRelayoutCount++
+                            placeable.place(0, 0)
+                        }
+                    }
+                ) { _, _ ->
+                    layout(10, 10) {
+                        relayoutCount++
+                    }
+                }
+            }) { measurables, constraints ->
+                val placeable = measurables[0].measure(constraints)
+                layout(placeable.width, placeable.height) {
+                    placeable.placeWithLayer(position, 0)
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            relayoutCount = 0
+            modifierRelayoutCount = 0
+            position = 10
+        }
+
+        rule.runOnIdle {
+            assertEquals(0, relayoutCount)
+            assertEquals(0, modifierRelayoutCount)
+        }
+    }
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LayoutCooperationTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LayoutCooperationTest.kt
index 154f1fd..e91f316 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LayoutCooperationTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LayoutCooperationTest.kt
@@ -29,9 +29,12 @@
 import androidx.compose.ui.background
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
 import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntSize
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
@@ -52,8 +55,14 @@
         val size = 48
         var initialOuterSize by mutableStateOf((size / 2).toDp())
         rule.setContent {
-            Box(Modifier.size(initialOuterSize).testTag("outer")) {
-                Box(Modifier.requiredSize(size.toDp()).background(Color.Yellow))
+            Box(
+                Modifier
+                    .size(initialOuterSize)
+                    .testTag("outer")) {
+                Box(
+                    Modifier
+                        .requiredSize(size.toDp())
+                        .background(Color.Yellow))
             }
         }
 
@@ -65,4 +74,45 @@
             Color.Yellow
         }
     }
+
+    @Test
+    fun relayoutSkippingModifiersDoesntBreakCooperation() {
+        with(rule.density) {
+            val containerSize = 100
+            val width = 50
+            val widthDp = width.toDp()
+            val height = 40
+            val heightDp = height.toDp()
+            var offset by mutableStateOf(0)
+            rule.setContent {
+                Layout(content = {
+                    Box(Modifier.requiredSize(widthDp, heightDp)) {
+                        Box(Modifier.testTag("child"))
+                    }
+                }) { measurables, _ ->
+                    val placeable =
+                        measurables.first().measure(Constraints.fixed(containerSize, containerSize))
+                    layout(containerSize, containerSize) {
+                        placeable.place(offset, offset)
+                    }
+                }
+            }
+
+            var expectedTop = ((containerSize - height) / 2).toDp()
+            var expectedLeft = ((containerSize - width) / 2).toDp()
+            rule.onNodeWithTag("child")
+                .assertTopPositionInRootIsEqualTo(expectedTop)
+                .assertLeftPositionInRootIsEqualTo(expectedLeft)
+
+            rule.runOnIdle {
+                offset = 10
+            }
+
+            expectedTop += offset.toDp()
+            expectedLeft += offset.toDp()
+            rule.onNodeWithTag("child")
+                .assertTopPositionInRootIsEqualTo(expectedTop)
+                .assertLeftPositionInRootIsEqualTo(expectedLeft)
+        }
+    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
index 942c141..a1e73ac 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/LookaheadScopeTest.kt
@@ -1716,6 +1716,7 @@
                     repeat(3) { id ->
                         subcompose(id) {
                             Box(Modifier.trackMainPassPlacement {
+                                iteration.toString() // state read to make callback called
                                 actualPlacementOrder.add(id)
                             })
                         }.fastMap { it.measure(constraints) }.let { placeables.addAll(it) }
@@ -1726,6 +1727,7 @@
                             val id = index + 3
                             subcompose(id) {
                                 Box(Modifier.trackMainPassPlacement {
+                                    iteration.toString() // state read to make callback called
                                     actualPlacementOrder.add(id)
                                 })
                             }.fastMap { it.measure(constraints) }.let { allPlaceables.addAll(it) }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/PlacementLayoutCoordinatesTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/PlacementLayoutCoordinatesTest.kt
index f655381..ca5805c 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/PlacementLayoutCoordinatesTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/PlacementLayoutCoordinatesTest.kt
@@ -257,7 +257,7 @@
                 Layout(content, Modifier.alignByBaseline()) { measurables, constraints ->
                     val p = measurables[0].measure(constraints)
                     layout(p.width, p.height) {
-                        locations += coordinates
+                        locations += coordinates.use()
                         p.place(0, 0)
                     }
                 }
@@ -287,7 +287,7 @@
                     Layout(content, Modifier.alignByBaseline()) { measurables, constraints ->
                         val p = measurables[0].measure(constraints)
                         layout(p.width, p.height) {
-                            locations += coordinates
+                            locations += coordinates.use()
                             p.place(0, 0)
                         }
                     }
@@ -329,7 +329,7 @@
                         }) { measurables, constraints ->
                     val p = measurables[0].measure(constraints)
                     layout(p.width, p.height) {
-                        locations += coordinates
+                        locations += coordinates.use()
                         p.place(0, 0)
                     }
                 }
@@ -343,7 +343,7 @@
     }
 
     @Test
-    fun parentCoordateChangeCausesRelayout() {
+    fun parentCoordinateChangeCausesRelayout() {
         val locations = mutableStateListOf<LayoutCoordinates?>()
         var offset by mutableStateOf(DpOffset(0.dp, 0.dp))
         rule.setContent {
@@ -354,7 +354,7 @@
                             .layout { measurable, constraints ->
                                 val p = measurable.measure(constraints)
                                 layout(p.width, p.height) {
-                                    locations += coordinates
+                                    locations += coordinates.use()
                                     p.place(0, 0)
                                 }
                             }
@@ -386,7 +386,7 @@
                                 .layout { measurable, constraints ->
                                     val p = measurable.measure(constraints)
                                     layout(p.width, p.height) {
-                                        locations += coordinates
+                                        locations += coordinates.use()
                                         p.place(0, 0)
                                     }
                                 }
@@ -422,7 +422,7 @@
                                     .layout { measurable, constraints ->
                                         val p = measurable.measure(constraints)
                                         layout(p.width, p.height) {
-                                            locations += coordinates
+                                            locations += coordinates.use()
                                             p.place(0, 0)
                                         }
                                     }
@@ -459,7 +459,8 @@
                                 .layout { measurable, constraints ->
                                     val p = measurable.measure(constraints)
                                     layout(p.width, p.height) {
-                                        layoutCalls += if (readCoordinates) coordinates else null
+                                        layoutCalls +=
+                                            if (readCoordinates) coordinates.use() else null
                                         p.place(0, 0)
                                     }
                                 }
@@ -507,7 +508,7 @@
                             .layout { measurable, constraints ->
                                 val p = measurable.measure(constraints)
                                 layout(p.width, p.height) {
-                                    locations += coordinates
+                                    locations += coordinates.use()
                                     p.place(0, 0)
                                 }
                             }
@@ -568,7 +569,7 @@
                             .layout { measurable, constraints ->
                                 val p = measurable.measure(constraints)
                                 layout(p.width, p.height) {
-                                    locations += coordinates
+                                    locations += coordinates.use()
                                     p.place(0, 0)
                                 }
                             }
@@ -604,7 +605,7 @@
                         .layout { measurable, constraints ->
                             val p = measurable.measure(constraints)
                             layout(p.width, p.height) {
-                                locations += coordinates
+                                locations += coordinates.use()
                                 p.place(0, 0)
                             }
                         }
@@ -634,7 +635,7 @@
                             .layout { measurable, constraints ->
                                 val p = measurable.measure(constraints)
                                 layout(p.width, p.height) {
-                                    locations += coordinates
+                                    locations += coordinates.use()
                                     p.place(0, 0)
                                 }
                             }
@@ -668,4 +669,235 @@
         rule.waitForIdle()
         assertEquals(1, locations.size)
     }
+
+    @Test
+    fun readingFromMainLayoutPolicyAfterMultipleMoves() {
+        var offset by mutableStateOf(0)
+        var layoutBlockCalls = 0
+        rule.setContent {
+            Layout(content = {
+                Layout { _, _ ->
+                    layout(10, 10) {
+                        coordinates?.positionInParent()
+                        layoutBlockCalls++
+                    }
+                }
+            }) { measurables, constraints ->
+                val placeable = measurables.first().measure(constraints)
+                layout(placeable.width, placeable.height) {
+                    placeable.place(offset, 0)
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            layoutBlockCalls = 0
+            offset = 1
+        }
+
+        rule.runOnIdle {
+            assertEquals(1, layoutBlockCalls)
+            layoutBlockCalls = 0
+            offset = 2
+        }
+
+        rule.runOnIdle {
+            assertEquals(1, layoutBlockCalls)
+        }
+    }
+
+    @Test
+    fun onlyRealPositionReadsTriggerRelayout() {
+        var offset by mutableStateOf(0)
+        var coordinatesAction: (LayoutCoordinates) -> Unit by mutableStateOf({})
+        var layoutBlockCalls = 0
+        rule.setContent {
+            Layout(content = {
+                Layout { _, _ ->
+                    layout(10, 10) {
+                        coordinates?.let(coordinatesAction)
+                        layoutBlockCalls++
+                    }
+                }
+            }) { measurables, constraints ->
+                val placeable = measurables.first().measure(constraints)
+                layout(placeable.width, placeable.height) {
+                    placeable.place(offset, 0)
+                }
+            }
+        }
+
+        fun assert(
+            relayoutExpected: Boolean,
+            description: String,
+            action: (LayoutCoordinates) -> Unit
+        ) {
+            coordinatesAction = action
+            rule.runOnIdle {
+                layoutBlockCalls = 0
+                offset = if (offset == 0) 10 else 0
+            }
+            rule.runOnIdle {
+                assertEquals(
+                    "Relayout because of `$description` read was " +
+                        "${if (!relayoutExpected) " not" else ""} expected, but " +
+                        "$layoutBlockCalls calls happened",
+                    if (relayoutExpected) 1 else 0,
+                    layoutBlockCalls
+                )
+            }
+        }
+
+        assert(relayoutExpected = true, "positionInParent()") { it.positionInParent() }
+        assert(relayoutExpected = true, "positionInRoot()") { it.positionInRoot() }
+        assert(relayoutExpected = true, "positionInWindow()") { it.positionInWindow() }
+        assert(relayoutExpected = true, "boundsInParent()") { it.boundsInParent() }
+        assert(relayoutExpected = true, "boundsInRoot()") { it.boundsInRoot() }
+        assert(relayoutExpected = true, "boundsInWindow()") { it.boundsInWindow() }
+
+        assert(relayoutExpected = false, "empty") { }
+        assert(relayoutExpected = false, "size") { it.size }
+        assert(relayoutExpected = false, "isAttached") { it.isAttached }
+        assert(relayoutExpected = false, "providedAlignmentLines") { it.providedAlignmentLines }
+    }
+
+    @Test
+    fun onlyRealPositionReadsTriggerRelayout_inModifier() {
+        var offset by mutableStateOf(0)
+        var coordinatesAction: (LayoutCoordinates) -> Unit by mutableStateOf({})
+        var layoutBlockCalls = 0
+        rule.setContent {
+            Layout(content = {
+                Box(
+                    Modifier
+                        .layout { measurable, constraints ->
+                            val p = measurable.measure(constraints)
+                            layout(p.width, p.height) {
+                                coordinates?.let(coordinatesAction)
+                                layoutBlockCalls++
+                                p.place(0, 0)
+                            }
+                        }
+                )
+            }) { measurables, constraints ->
+                val placeable = measurables.first().measure(constraints)
+                layout(placeable.width, placeable.height) {
+                    placeable.place(offset, 0)
+                }
+            }
+        }
+
+        fun assert(
+            relayoutExpected: Boolean,
+            description: String,
+            action: (LayoutCoordinates) -> Unit
+        ) {
+            coordinatesAction = action
+            rule.runOnIdle {
+                layoutBlockCalls = 0
+                offset = if (offset == 0) 10 else 0
+            }
+            rule.runOnIdle {
+                assertEquals(
+                    "Relayout because of `$description` read was " +
+                        "${if (!relayoutExpected) " not" else ""} expected, but " +
+                        "$layoutBlockCalls calls happened",
+                    if (relayoutExpected) 1 else 0,
+                    layoutBlockCalls
+                )
+            }
+        }
+
+        assert(relayoutExpected = true, "positionInParent()") { it.positionInParent() }
+        assert(relayoutExpected = true, "positionInRoot()") { it.positionInRoot() }
+        assert(relayoutExpected = true, "positionInWindow()") { it.positionInWindow() }
+        assert(relayoutExpected = true, "boundsInParent()") { it.boundsInParent() }
+        assert(relayoutExpected = true, "boundsInRoot()") { it.boundsInRoot() }
+        assert(relayoutExpected = true, "boundsInWindow()") { it.boundsInWindow() }
+
+        assert(relayoutExpected = false, "empty") { }
+        assert(relayoutExpected = false, "size") { it.size }
+        assert(relayoutExpected = false, "isAttached") { it.isAttached }
+        assert(relayoutExpected = false, "providedAlignmentLines") { it.providedAlignmentLines }
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun onlyRealPositionReadsTriggerRelayout_inLookahead() {
+        var offset by mutableStateOf(0)
+        var coordinatesAction: (LayoutCoordinates) -> Unit by mutableStateOf({})
+        var intermediateLayoutBlockCalls = 0
+        rule.setContent {
+            LookaheadScope {
+                Layout(content = {
+                    Box(
+                        Modifier
+                            .intermediateLayout { measurable, constraints ->
+                                val p = measurable.measure(constraints)
+                                layout(p.width, p.height) {
+                                    coordinates?.let(coordinatesAction)
+                                    intermediateLayoutBlockCalls++
+                                    p.place(0, 0)
+                                }
+                            }
+                            .layout { measurable, constraints ->
+                                val p = measurable.measure(constraints)
+                                layout(10, 10) {
+                                    // if we don't read the coordinates here as well
+                                    // the read of coordinates in intermediate layout could be
+                                    // skipped as both passes share the same
+                                    // coordinatesAccessedDuringPlacement property.
+                                    // filed b/284153462 to track this issue
+                                    coordinates?.let(coordinatesAction)
+                                    p.place(0, 0)
+                                }
+                            }
+                    )
+                }) { measurables, constraints ->
+                    val placeable = measurables.first().measure(constraints)
+                    layout(placeable.width, placeable.height) {
+                        placeable.place(offset, 0)
+                    }
+                }
+            }
+        }
+
+        fun assert(
+            relayoutExpected: Boolean,
+            description: String,
+            action: (LayoutCoordinates) -> Unit
+        ) {
+            coordinatesAction = action
+            rule.runOnIdle {
+                intermediateLayoutBlockCalls = 0
+                offset = if (offset == 0) 10 else 0
+            }
+            rule.runOnIdle {
+                assertEquals(
+                    "Relayout because of `$description` read was " +
+                        "${if (!relayoutExpected) " not" else ""} expected, but " +
+                        "$intermediateLayoutBlockCalls calls happened",
+                    if (relayoutExpected) 1 else 0,
+                    intermediateLayoutBlockCalls
+                )
+            }
+        }
+
+        assert(relayoutExpected = true, "positionInParent()") { it.positionInParent() }
+        assert(relayoutExpected = true, "positionInRoot()") { it.positionInRoot() }
+        assert(relayoutExpected = true, "positionInWindow()") { it.positionInWindow() }
+        assert(relayoutExpected = true, "boundsInParent()") { it.boundsInParent() }
+        assert(relayoutExpected = true, "boundsInRoot()") { it.boundsInRoot() }
+        assert(relayoutExpected = true, "boundsInWindow()") { it.boundsInWindow() }
+
+        assert(relayoutExpected = false, "empty") { }
+        assert(relayoutExpected = false, "size") { it.size }
+        assert(relayoutExpected = false, "isAttached") { it.isAttached }
+        assert(relayoutExpected = false, "providedAlignmentLines") { it.providedAlignmentLines }
+    }
+}
+
+private fun LayoutCoordinates?.use(): LayoutCoordinates? {
+    this?.parentCoordinates
+    return this
 }
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index a195828..4902ab9 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -880,20 +880,30 @@
     }
 
     override fun measureAndLayout(sendPointerUpdate: Boolean) {
-        trace("AndroidOwner:measureAndLayout") {
-            val resend = if (sendPointerUpdate) resendMotionEventOnLayout else null
-            val rootNodeResized = measureAndLayoutDelegate.measureAndLayout(resend)
-            if (rootNodeResized) {
-                requestLayout()
+        // only run the logic when we have something pending
+        if (measureAndLayoutDelegate.hasPendingMeasureOrLayout ||
+            measureAndLayoutDelegate.hasPendingOnPositionedCallbacks
+        ) {
+            trace("AndroidOwner:measureAndLayout") {
+                val resend = if (sendPointerUpdate) resendMotionEventOnLayout else null
+                val rootNodeResized = measureAndLayoutDelegate.measureAndLayout(resend)
+                if (rootNodeResized) {
+                    requestLayout()
+                }
+                measureAndLayoutDelegate.dispatchOnPositionedCallbacks()
             }
-            measureAndLayoutDelegate.dispatchOnPositionedCallbacks()
         }
     }
 
     override fun measureAndLayout(layoutNode: LayoutNode, constraints: Constraints) {
         trace("AndroidOwner:measureAndLayout") {
             measureAndLayoutDelegate.measureAndLayout(layoutNode, constraints)
-            measureAndLayoutDelegate.dispatchOnPositionedCallbacks()
+            // only dispatch the callbacks if we don't have other nodes to process as otherwise
+            // we will have one more measureAndLayout() pass anyway in the same frame.
+            // it allows us to not traverse the hierarchy twice.
+            if (!measureAndLayoutDelegate.hasPendingMeasureOrLayout) {
+                measureAndLayoutDelegate.dispatchOnPositionedCallbacks()
+            }
         }
     }
 
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.android.kt
index 11b681a..2476012 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/RenderNodeLayer.android.kt
@@ -208,8 +208,12 @@
         val newLeft = position.x
         val newTop = position.y
         if (oldLeft != newLeft || oldTop != newTop) {
-            renderNode.offsetLeftAndRight(newLeft - oldLeft)
-            renderNode.offsetTopAndBottom(newTop - oldTop)
+            if (oldLeft != newLeft) {
+                renderNode.offsetLeftAndRight(newLeft - oldLeft)
+            }
+            if (oldTop != newTop) {
+                renderNode.offsetTopAndBottom(newTop - oldTop)
+            }
             triggerRepaint()
             matrixCache.invalidate()
         }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutCoordinates.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutCoordinates.kt
index af49d34..e674eed 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutCoordinates.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LayoutCoordinates.kt
@@ -24,7 +24,7 @@
 import androidx.compose.ui.unit.IntSize
 
 /**
- * A holder of the measured bounds for the layout (MeasureBox).
+ * A holder of the measured bounds for the [Layout].
  */
 @JvmDefaultWithCompatibility
 interface LayoutCoordinates {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LookaheadLayoutCoordinates.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LookaheadLayoutCoordinates.kt
index 354fe56..a674790 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LookaheadLayoutCoordinates.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/LookaheadLayoutCoordinates.kt
@@ -89,6 +89,7 @@
     ): Offset {
         if (sourceCoordinates is LookaheadLayoutCoordinatesImpl) {
             val source = sourceCoordinates.lookaheadDelegate
+            source.coordinator.onCoordinatesUsed()
             val commonAncestor = coordinator.findCommonAncestor(source.coordinator)
 
             return commonAncestor.lookaheadDelegate?.let { ancestor ->
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Placeable.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Placeable.kt
index 94893a6..b20aa16 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Placeable.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Placeable.kt
@@ -71,11 +71,11 @@
         set(value) {
             if (field != value) {
                 field = value
-                recalculateWidthAndHeight()
+                onMeasuredSizeChanged()
             }
         }
 
-    private fun recalculateWidthAndHeight() {
+    private fun onMeasuredSizeChanged() {
         width = measuredSize.width.coerceIn(
             measurementConstraints.minWidth,
             measurementConstraints.maxWidth
@@ -84,6 +84,8 @@
             measurementConstraints.minHeight,
             measurementConstraints.maxHeight
         )
+        apparentToRealOffset =
+            IntOffset((width - measuredSize.width) / 2, (height - measuredSize.height) / 2)
     }
 
     /**
@@ -110,7 +112,7 @@
         set(value) {
             if (field != value) {
                 field = value
-                recalculateWidthAndHeight()
+                onMeasuredSizeChanged()
             }
         }
 
@@ -119,8 +121,8 @@
      * The real layout will be centered on the space assigned by the parent, which computed the
      * child's position only seeing its apparent size.
      */
-    protected val apparentToRealOffset: IntOffset
-        get() = IntOffset((width - measuredSize.width) / 2, (height - measuredSize.height) / 2)
+    protected var apparentToRealOffset: IntOffset = IntOffset.Zero
+        private set
 
     /**
      * Receiver scope that permits explicit placement of a [Placeable].
@@ -158,6 +160,10 @@
          * When [coordinates] is `null`, there will always be a follow-up placement call in which
          * [coordinates] is not-`null`.
          *
+         * If you read a position from the coordinates during the placement block the block
+         * will be automatically re-executed when the parent layout changes a position. If you
+         * don't read it the placement block execution can be skipped as an optimization.
+         *
          * @sample androidx.compose.ui.samples.PlacementScopeCoordinatesSample
          */
         open val coordinates: LayoutCoordinates?
@@ -340,7 +346,11 @@
 
             override val coordinates: LayoutCoordinates?
                 get() {
-                    layoutDelegate?.coordinatesAccessedDuringPlacement = true
+                    // if coordinates are not null we will only set this flag when the inner
+                    // coordinate values are read. see NodeCoordinator.onCoordinatesUsed()
+                    if (_coordinates == null) {
+                        layoutDelegate?.onCoordinatesUsed()
+                    }
                     return _coordinates
                 }
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeAlignmentLines.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeAlignmentLines.kt
index fc84004..b682397 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeAlignmentLines.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeAlignmentLines.kt
@@ -203,7 +203,7 @@
             alignmentLinesOwner.requestMeasure()
         }
         if (usedByModifierLayout) {
-            parent.requestLayout()
+            alignmentLinesOwner.requestLayout()
         }
         parent.alignmentLines.onAlignmentsChanged()
     }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt
index e66dc1c..9e079fe 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNodeLayoutDelegate.kt
@@ -26,7 +26,6 @@
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.util.fastForEach
 
 /**
  * This class works as a layout delegate for [LayoutNode]. It delegates all the measure/layout
@@ -173,9 +172,30 @@
             val oldValue = field
             if (oldValue != value) {
                 field = value
-                if (value) {
+                if (value && !coordinatesAccessedDuringModifierPlacement) {
+                    // if first out of both flags changes to true increment
                     childrenAccessingCoordinatesDuringPlacement++
-                } else {
+                } else if (!value && !coordinatesAccessedDuringModifierPlacement) {
+                    // if both flags changes to false decrement
+                    childrenAccessingCoordinatesDuringPlacement--
+                }
+            }
+        }
+
+    /**
+     * Similar to [coordinatesAccessedDuringPlacement], but tracks the coordinates read happening
+     * during the modifier layout blocks run.
+     */
+    var coordinatesAccessedDuringModifierPlacement = false
+        set(value) {
+            val oldValue = field
+            if (oldValue != value) {
+                field = value
+                if (value && !coordinatesAccessedDuringPlacement) {
+                    // if first out of both flags changes to true increment
+                    childrenAccessingCoordinatesDuringPlacement++
+                } else if (!value && !coordinatesAccessedDuringPlacement) {
+                    // if both flags changes to false decrement
                     childrenAccessingCoordinatesDuringPlacement--
                 }
             }
@@ -216,6 +236,25 @@
     internal var lookaheadPassDelegate: LookaheadPassDelegate? = null
         private set
 
+    fun onCoordinatesUsed() {
+        val state = layoutNode.layoutState
+        if (state == LayoutState.LayingOut || state == LayoutState.LookaheadLayingOut) {
+            if (measurePassDelegate.layingOutChildren) {
+                coordinatesAccessedDuringPlacement = true
+            } else {
+                coordinatesAccessedDuringModifierPlacement = true
+            }
+        }
+        if (state == LayoutState.LookaheadLayingOut) {
+            // TODO lookahead should have its own flags b/284153462
+            if (lookaheadPassDelegate?.layingOutChildren == true) {
+                coordinatesAccessedDuringPlacement = true
+            } else {
+                coordinatesAccessedDuringModifierPlacement = true
+            }
+        }
+    }
+
     /**
      * [MeasurePassDelegate] manages the measure/layout and alignmentLine related queries for the
      * actual measure/layout pass.
@@ -293,7 +332,11 @@
                 return _childDelegates.asMutableList()
             }
 
+        var layingOutChildren = false
+            private set
+
         override fun layoutChildren() {
+            layingOutChildren = true
             alignmentLines.recalculateQueryOwner()
 
             if (layoutPending) {
@@ -308,6 +351,7 @@
                 layoutPending = false
                 val oldLayoutState = layoutState
                 layoutState = LayoutState.LayingOut
+                coordinatesAccessedDuringPlacement = false
                 with(layoutNode) {
                     val owner = requireOwner()
                     owner.snapshotObserver.observeLayoutSnapshotReads(
@@ -341,6 +385,8 @@
                 alignmentLines.previousUsedDuringParentLayout = true
             }
             if (alignmentLines.dirty && alignmentLines.required) alignmentLines.recalculate()
+
+            layingOutChildren = false
         }
 
         private fun checkChildrenPlaceOrderForUpdates() {
@@ -463,7 +509,7 @@
         }
 
         private inline fun forEachChildDelegate(block: (MeasurePassDelegate) -> Unit) {
-            layoutNode.children.fastForEach {
+            layoutNode.forEachChild {
                 block(it.measurePassDelegate)
             }
         }
@@ -581,6 +627,10 @@
             layerBlock: (GraphicsLayerScope.() -> Unit)?
         ) {
             if (position != lastPosition) {
+                if (coordinatesAccessedDuringModifierPlacement ||
+                    coordinatesAccessedDuringPlacement) {
+                    layoutPending = true
+                }
                 notifyChildrenUsingCoordinatesWhilePlacing()
             }
             // This can actually be called as soon as LookaheadMeasure is done, but devs may expect
@@ -603,9 +653,7 @@
             }
 
             // Post-lookahead (if any) placement
-            layoutState = LayoutState.LayingOut
             placeOuterCoordinator(position, zIndex, layerBlock)
-            layoutState = LayoutState.Idle
         }
 
         private fun placeOuterCoordinator(
@@ -613,26 +661,34 @@
             zIndex: Float,
             layerBlock: (GraphicsLayerScope.() -> Unit)?
         ) {
+            layoutState = LayoutState.LayingOut
+
             lastPosition = position
             lastZIndex = zIndex
             lastLayerBlock = layerBlock
-
             placedOnce = true
-            alignmentLines.usedByModifierLayout = false
-            coordinatesAccessedDuringPlacement = false
+
             val owner = layoutNode.requireOwner()
-            owner.snapshotObserver.observeLayoutModifierSnapshotReads(
-                layoutNode,
-                affectsLookahead = false
-            ) {
-                with(PlacementScope) {
-                    if (layerBlock == null) {
-                        outerCoordinator.place(position, zIndex)
-                    } else {
-                        outerCoordinator.placeWithLayer(position, zIndex, layerBlock)
+            if (!layoutPending && isPlaced) {
+                outerCoordinator.placeSelfApparentToRealOffset(position, zIndex, layerBlock)
+                onNodePlaced()
+            } else {
+                alignmentLines.usedByModifierLayout = false
+                coordinatesAccessedDuringModifierPlacement = false
+                owner.snapshotObserver.observeLayoutModifierSnapshotReads(
+                    layoutNode, affectsLookahead = false
+                ) {
+                    with(PlacementScope) {
+                        if (layerBlock == null) {
+                            outerCoordinator.place(position, zIndex)
+                        } else {
+                            outerCoordinator.placeWithLayer(position, zIndex, layerBlock)
+                        }
                     }
                 }
             }
+
+            layoutState = LayoutState.Idle
         }
 
         /**
@@ -730,7 +786,7 @@
             get() = layoutNode.parent?.layoutDelegate?.alignmentLinesOwner
 
         override fun forEachChildAlignmentLinesOwner(block: (AlignmentLinesOwner) -> Unit) {
-            layoutNode.children.fastForEach {
+            layoutNode.forEachChild {
                 block(it.layoutDelegate.alignmentLinesOwner)
             }
         }
@@ -756,11 +812,11 @@
          */
         fun notifyChildrenUsingCoordinatesWhilePlacing() {
             if (childrenAccessingCoordinatesDuringPlacement > 0) {
-                layoutNode.children.fastForEach { child ->
+                layoutNode.forEachChild { child ->
                     val childLayoutDelegate = child.layoutDelegate
-                    if (childLayoutDelegate.coordinatesAccessedDuringPlacement &&
-                        !childLayoutDelegate.layoutPending
-                    ) {
+                    val accessed = childLayoutDelegate.coordinatesAccessedDuringPlacement ||
+                        childLayoutDelegate.coordinatesAccessedDuringModifierPlacement
+                    if (accessed && !childLayoutDelegate.layoutPending) {
                         child.requestRelayout()
                     }
                     childLayoutDelegate.measurePassDelegate
@@ -939,12 +995,16 @@
                 return _childDelegates.asMutableList()
             }
 
+        var layingOutChildren = false
+            private set
+
         private inline fun forEachChildDelegate(block: (LookaheadPassDelegate) -> Unit) =
             layoutNode.forEachChild {
                 block(it.layoutDelegate.lookaheadPassDelegate!!)
             }
 
         override fun layoutChildren() {
+            layingOutChildren = true
             alignmentLines.recalculateQueryOwner()
 
             if (lookaheadLayoutPending) {
@@ -961,6 +1021,7 @@
                 val oldLayoutState = layoutState
                 layoutState = LayoutState.LookaheadLayingOut
                 val owner = layoutNode.requireOwner()
+                coordinatesAccessedDuringPlacement = false
                 owner.snapshotObserver.observeLayoutSnapshotReads(layoutNode) {
                     clearPlaceOrder()
                     forEachChildAlignmentLinesOwner { child ->
@@ -985,6 +1046,8 @@
                 alignmentLines.previousUsedDuringParentLayout = true
             }
             if (alignmentLines.dirty && alignmentLines.required) alignmentLines.recalculate()
+
+            layingOutChildren = false
         }
 
         private fun checkChildrenPlaceOrderForUpdates() {
@@ -1030,7 +1093,7 @@
             get() = layoutNode.parent?.layoutDelegate?.lookaheadAlignmentLinesOwner
 
         override fun forEachChildAlignmentLinesOwner(block: (AlignmentLinesOwner) -> Unit) {
-            layoutNode.children.fastForEach {
+            layoutNode.forEachChild {
                 block(it.layoutDelegate.lookaheadAlignmentLinesOwner!!)
             }
         }
@@ -1056,11 +1119,11 @@
          */
         fun notifyChildrenUsingCoordinatesWhilePlacing() {
             if (childrenAccessingCoordinatesDuringPlacement > 0) {
-                layoutNode.children.fastForEach { child ->
+                layoutNode.forEachChild { child ->
                     val childLayoutDelegate = child.layoutDelegate
-                    if (childLayoutDelegate.coordinatesAccessedDuringPlacement &&
-                        !childLayoutDelegate.layoutPending
-                    ) {
+                    val accessed = childLayoutDelegate.coordinatesAccessedDuringPlacement ||
+                        childLayoutDelegate.coordinatesAccessedDuringModifierPlacement
+                    if (accessed && !childLayoutDelegate.layoutPending) {
                         child.requestLookaheadRelayout()
                     }
                     childLayoutDelegate.lookaheadPassDelegate
@@ -1160,14 +1223,23 @@
             layoutState = LayoutState.LookaheadLayingOut
             placedOnce = true
             if (position != lastPosition) {
+                if (coordinatesAccessedDuringModifierPlacement ||
+                    coordinatesAccessedDuringPlacement) {
+                    lookaheadLayoutPending = true
+                }
                 notifyChildrenUsingCoordinatesWhilePlacing()
             }
-            alignmentLines.usedByModifierLayout = false
             val owner = layoutNode.requireOwner()
-            coordinatesAccessedDuringPlacement = false
-            owner.snapshotObserver.observeLayoutModifierSnapshotReads(layoutNode) {
-                with(PlacementScope) {
-                    outerCoordinator.lookaheadDelegate!!.place(position)
+
+            if (!lookaheadLayoutPending && isPlaced) {
+                onNodePlaced()
+            } else {
+                coordinatesAccessedDuringModifierPlacement = false
+                alignmentLines.usedByModifierLayout = false
+                owner.snapshotObserver.observeLayoutModifierSnapshotReads(layoutNode) {
+                    with(PlacementScope) {
+                        outerCoordinator.lookaheadDelegate!!.place(position)
+                    }
                 }
             }
             lastPosition = position
@@ -1294,8 +1366,8 @@
             }
             if (parent != null) {
                 if (!relayoutWithoutParentInProgress &&
-                    parent.layoutState == LayoutState.LayingOut ||
-                    parent.layoutState == LayoutState.LookaheadLayingOut
+                    (parent.layoutState == LayoutState.LayingOut ||
+                        parent.layoutState == LayoutState.LookaheadLayingOut)
                 ) {
                     // the parent is currently placing its children
                     check(placeOrder == NotPlacedPlaceOrder) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
index d297aef..e743af7 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
@@ -48,6 +48,11 @@
     val hasPendingMeasureOrLayout get() = relayoutNodes.isNotEmpty()
 
     /**
+     * Whether any on positioned callbacks need to be dispatched
+     */
+    val hasPendingOnPositionedCallbacks get() = onPositionedDispatcher.isNotEmpty()
+
+    /**
      * Flag to indicate that we're currently measuring.
      */
     private var duringMeasureLayout = false
@@ -365,7 +370,7 @@
     private fun recurseRemeasure(layoutNode: LayoutNode) {
         remeasureOnly(layoutNode)
 
-        layoutNode._children.forEach { child ->
+        layoutNode.forEachChild { child ->
             if (child.measureAffectsParent) {
                 recurseRemeasure(child)
             }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
index 6d16589..0e9b845 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
@@ -247,15 +247,21 @@
             return null
         }
 
+    internal fun onCoordinatesUsed() {
+        layoutNode.layoutDelegate.onCoordinatesUsed()
+    }
+
     final override val parentLayoutCoordinates: LayoutCoordinates?
         get() {
             check(isAttached) { ExpectAttachedLayoutCoordinates }
+            onCoordinatesUsed()
             return layoutNode.outerCoordinator.wrappedBy
         }
 
     final override val parentCoordinates: LayoutCoordinates?
         get() {
             check(isAttached) { ExpectAttachedLayoutCoordinates }
+            onCoordinatesUsed()
             return wrappedBy
         }
 
@@ -301,6 +307,14 @@
         zIndex: Float,
         layerBlock: (GraphicsLayerScope.() -> Unit)?
     ) {
+        placeSelf(position, zIndex, layerBlock)
+    }
+
+    private fun placeSelf(
+        position: IntOffset,
+        zIndex: Float,
+        layerBlock: (GraphicsLayerScope.() -> Unit)?
+    ) {
         updateLayerBlock(layerBlock)
         if (this.position != position) {
             this.position = position
@@ -318,6 +332,14 @@
         this.zIndex = zIndex
     }
 
+    fun placeSelfApparentToRealOffset(
+        position: IntOffset,
+        zIndex: Float,
+        layerBlock: (GraphicsLayerScope.() -> Unit)?
+    ) {
+        placeSelf(position + apparentToRealOffset, zIndex, layerBlock)
+    }
+
     /**
      * Draws the content of the LayoutNode
      */
@@ -374,9 +396,9 @@
         layerBlock: (GraphicsLayerScope.() -> Unit)?,
         forceUpdateLayerParameters: Boolean = false
     ) {
-        val updateParameters = this.layerBlock !== layerBlock || layerDensity != layoutNode
-            .density || layerLayoutDirection != layoutNode.layoutDirection ||
-            forceUpdateLayerParameters
+        val layoutNode = layoutNode
+        val updateParameters = forceUpdateLayerParameters || this.layerBlock !== layerBlock ||
+            layerDensity != layoutNode.density || layerLayoutDirection != layoutNode.layoutDirection
         this.layerBlock = layerBlock
         this.layerDensity = layoutNode.density
         this.layerLayoutDirection = layoutNode.layoutDirection
@@ -737,6 +759,7 @@
         }
 
         val nodeCoordinator = sourceCoordinates.toCoordinator()
+        nodeCoordinator.onCoordinatesUsed()
         val commonAncestor = findCommonAncestor(nodeCoordinator)
 
         var position = relativeToSource
@@ -751,6 +774,7 @@
 
     override fun transformFrom(sourceCoordinates: LayoutCoordinates, matrix: Matrix) {
         val coordinator = sourceCoordinates.toCoordinator()
+        coordinator.onCoordinatesUsed()
         val commonAncestor = findCommonAncestor(coordinator)
 
         matrix.reset()
@@ -795,6 +819,7 @@
             "LayoutCoordinates $sourceCoordinates is not attached!"
         }
         val srcCoordinator = sourceCoordinates.toCoordinator()
+        srcCoordinator.onCoordinatesUsed()
         val commonAncestor = findCommonAncestor(srcCoordinator)
 
         val bounds = rectCache
@@ -842,6 +867,7 @@
 
     override fun localToRoot(relativeToLocal: Offset): Offset {
         check(isAttached) { ExpectAttachedLayoutCoordinates }
+        onCoordinatesUsed()
         var coordinator: NodeCoordinator? = this
         var position = relativeToLocal
         while (coordinator != null) {
@@ -1160,7 +1186,8 @@
                         val layoutNode = coordinator.layoutNode
                         val layoutDelegate = layoutNode.layoutDelegate
                         if (layoutDelegate.childrenAccessingCoordinatesDuringPlacement > 0) {
-                            if (layoutDelegate.coordinatesAccessedDuringPlacement) {
+                            if (layoutDelegate.coordinatesAccessedDuringModifierPlacement ||
+                                layoutDelegate.coordinatesAccessedDuringPlacement) {
                                 layoutNode.requestRelayout()
                             }
                             layoutDelegate.measurePassDelegate
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OnPositionedDispatcher.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OnPositionedDispatcher.kt
index 9642118..4b34850 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OnPositionedDispatcher.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/OnPositionedDispatcher.kt
@@ -26,6 +26,8 @@
     private val layoutNodes = mutableVectorOf<LayoutNode>()
     private var cachedNodes: Array<LayoutNode?>? = null
 
+    fun isNotEmpty() = layoutNodes.isNotEmpty()
+
     fun onNodePositioned(node: LayoutNode) {
         layoutNodes += node
         node.needsOnPositionedDispatch = true
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
index f245fde..defab69 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/node/LayoutNodeTest.kt
@@ -520,10 +520,10 @@
 
     @Test
     fun testLocalPositionOfWithSiblings() {
-        val node0 = LayoutNode()
+        val node0 = ZeroSizedLayoutNode()
         node0.attach(MockOwner())
-        val node1 = LayoutNode()
-        val node2 = LayoutNode()
+        val node1 = ZeroSizedLayoutNode()
+        val node2 = ZeroSizedLayoutNode()
         node0.insertAt(0, node1)
         node0.insertAt(1, node2)
         node1.place(10, 20)
diff --git a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListBeyondBoundsTest.kt b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListBeyondBoundsTest.kt
index 149cf47..4544704 100644
--- a/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListBeyondBoundsTest.kt
+++ b/tv/tv-foundation/src/androidTest/java/androidx/tv/foundation/lazy/list/LazyListBeyondBoundsTest.kt
@@ -39,9 +39,12 @@
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Below
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Left
 import androidx.compose.ui.layout.BeyondBoundsLayout.LayoutDirection.Companion.Right
+import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.ModifierLocalBeyondBoundsLayout
-import androidx.compose.ui.layout.onPlaced
 import androidx.compose.ui.modifier.modifierLocalConsumer
+import androidx.compose.ui.node.LayoutAwareModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.junit4.createComposeRule
@@ -107,7 +110,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
         }
@@ -127,7 +130,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
         }
@@ -147,7 +150,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
         }
@@ -181,8 +184,7 @@
         }
 
         // Act.
-        rule.waitForIdle()
-        val hasMoreContent = rule.runOnUiThread {
+        val hasMoreContent = rule.runOnIdle {
             beyondBoundsLayoutRef.layout(beyondBoundsLayoutDirection) {
                 hasMoreContent
             }
@@ -202,28 +204,27 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
             item {
                 Box(
                     Modifier
-                        .size(10.toDp())
-                        .onPlaced { placedItems += 5 }
-                        .modifierLocalConsumer {
-                            beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
-                        }
+                    .size(10.toDp())
+                    .trackPlaced(5)
+                    .modifierLocalConsumer {
+                        beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
+                    }
                 )
             }
             items(5) { index ->
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index + 6 }
+                        .trackPlaced(index + 6)
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act.
         rule.runOnUiThread {
@@ -236,7 +237,6 @@
                     assertThat(placedItems).containsExactly(5, 6, 7, 8)
                     assertThat(visibleItems).containsExactly(5, 6, 7)
                 }
-                placedItems.clear()
                 // Just return true so that we stop as soon as we run this once.
                 // This should result in one extra item being added.
                 true
@@ -259,14 +259,14 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
             item {
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += 5 }
+                        .trackPlaced(5)
                         .modifierLocalConsumer {
                             beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                         }
@@ -276,17 +276,15 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index + 6 }
+                        .trackPlaced(index + 6)
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act.
         rule.runOnUiThread {
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 if (--extraItemCount > 0) {
-                    placedItems.clear()
                     // Return null to continue the search.
                     null
                 } else {
@@ -298,7 +296,6 @@
                         assertThat(placedItems).containsExactly(5, 6, 7, 8, 9)
                         assertThat(visibleItems).containsExactly(5, 6, 7)
                     }
-                    placedItems.clear()
                     // Return true to stop the search.
                     true
                 }
@@ -320,7 +317,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
             item {
@@ -330,26 +327,22 @@
                         .modifierLocalConsumer {
                             beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                         }
-                        .onPlaced { placedItems += 5 }
+                        .trackPlaced(5)
                 )
             }
             items(5) { index ->
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced {
-                            placedItems += index + 6
-                        }
+                        .trackPlaced(index + 6)
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act.
         rule.runOnUiThread {
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 if (hasMoreContent) {
-                    placedItems.clear()
                     // Just return null so that we keep adding more items till we reach the end.
                     null
                 } else {
@@ -361,7 +354,6 @@
                         assertThat(placedItems).containsExactly(5, 6, 7, 8, 9, 10)
                         assertThat(visibleItems).containsExactly(5, 6, 7)
                     }
-                    placedItems.clear()
                     // Return true to end the search.
                     true
                 }
@@ -383,14 +375,14 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index }
+                        .trackPlaced(index)
                 )
             }
             item {
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += 5 }
+                        .trackPlaced(5)
                         .modifierLocalConsumer {
                             beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                         }
@@ -400,14 +392,13 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced { placedItems += index + 6 }
+                        .trackPlaced(index + 6)
                 )
             }
         }
         rule.runOnIdle {
             assertThat(placedItems).containsExactly(5, 6, 7)
             assertThat(visibleItems).containsExactly(5, 6, 7)
-            placedItems.clear()
         }
 
         // Act.
@@ -416,7 +407,6 @@
                 beyondBoundsLayoutCount++
                 when (beyondBoundsLayoutDirection) {
                     Left, Right, Above, Below -> {
-                        assertThat(placedItems).containsExactlyElementsIn(visibleItems)
                         assertThat(placedItems).containsExactly(5, 6, 7)
                         assertThat(visibleItems).containsExactly(5, 6, 7)
                     }
@@ -430,7 +420,6 @@
                         }
                     }
                 }
-                placedItems.clear()
                 // Just return true so that we stop as soon as we run this once.
                 // This should result in one extra item being added.
                 true
@@ -462,9 +451,7 @@
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced {
-                            placedItems += index
-                        }
+                        .trackPlaced(index)
                 )
             }
             item {
@@ -474,20 +461,17 @@
                         .modifierLocalConsumer {
                             beyondBoundsLayout = ModifierLocalBeyondBoundsLayout.current
                         }
-                        .onPlaced { placedItems += 5 }
+                        .trackPlaced(5)
                 )
             }
             items(5) { index ->
                 Box(
                     Modifier
                         .size(10.toDp())
-                        .onPlaced {
-                            placedItems += index + 6
-                        }
+                        .trackPlaced(index + 6)
                 )
             }
         }
-        rule.runOnIdle { placedItems.clear() }
 
         // Act.
         var count = 0
@@ -495,7 +479,6 @@
             beyondBoundsLayout!!.layout(beyondBoundsLayoutDirection) {
                 // Assert that we don't keep iterating when there is no ending condition.
                 assertThat(count++).isLessThan(lazyListState.layoutInfo.totalItemsCount)
-                placedItems.clear()
                 // Always return null to continue the search.
                 null
             }
@@ -515,7 +498,9 @@
             Column {
                 BasicText(
                     text = "Outer button",
-                    Modifier.focusRequester(buttonFocusRequester).focusable())
+                    Modifier
+                        .focusRequester(buttonFocusRequester)
+                        .focusable())
 
                 TvLazyColumn {
                     items(3) {
@@ -617,4 +602,37 @@
     private fun unsupportedDirection(): Nothing = error(
         "Lazy list does not support beyond bounds layout for the specified direction"
     )
+
+    private fun Modifier.trackPlaced(index: Int): Modifier =
+        this then TrackPlacedElement(placedItems, index)
+}
+
+internal data class TrackPlacedElement(
+    var placedItems: MutableSet<Int>,
+    var index: Int
+) : ModifierNodeElement<TrackPlacedNode>() {
+    override fun create() = TrackPlacedNode(placedItems, index)
+
+    override fun update(node: TrackPlacedNode) {
+        node.placedItems = placedItems
+        node.index = index
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        name = "trackPlaced"
+        properties["index"] = index
+    }
+}
+
+internal class TrackPlacedNode(
+    var placedItems: MutableSet<Int>,
+    var index: Int
+) : LayoutAwareModifierNode, Modifier.Node() {
+    override fun onPlaced(coordinates: LayoutCoordinates) {
+        placedItems += index
+    }
+
+    override fun onDetach() {
+        placedItems -= index
+    }
 }