[go: nahoru, domu]

Avoid relayouts from unchanged semantics modifiers

Adding an equals() implementation to SemanticsModifierCore means layout
composables won't rerun unnecessarily every time a new one is
constructed with the exact same properties.

Fixes: 162006751
Test: unchangedSemanticsDoesNotCauseRelayout
Change-Id: I7d0d20ea5b3080e84e3b0faf606cf73c38b4a62f
diff --git a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt
index b7d8053..7939b4d 100644
--- a/ui/ui-core/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt
+++ b/ui/ui-core/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt
@@ -19,6 +19,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.Stable
 import androidx.test.filters.MediumTest
 import androidx.ui.core.Layout
 import androidx.ui.core.Modifier
@@ -43,6 +44,7 @@
 import androidx.ui.test.runOnIdle
 import androidx.ui.test.runOnUiThread
 import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -69,6 +71,22 @@
     }
 
     @Test
+    fun unchangedSemanticsDoesNotCauseRelayout() {
+        val layoutCounter = Counter(0)
+        val recomposeForcer = mutableStateOf(0)
+        composeTestRule.setContent {
+            recomposeForcer.value
+            CountingLayout(Modifier.semantics { accessibilityLabel = "label" }, layoutCounter)
+        }
+
+        runOnIdle { assertEquals(1, layoutCounter.count) }
+
+        runOnIdle { recomposeForcer.value++ }
+
+        runOnIdle { assertEquals(1, layoutCounter.count) }
+    }
+
+    @Test
     fun nestedMergedSubtree() {
         val tag1 = "tag1"
         val tag2 = "tag2"
@@ -322,6 +340,21 @@
     assert(SemanticsMatcher.keyNotDefined(property))
 }
 
+// Falsely mark the layout counter stable to avoid influencing recomposition behavior
+@Stable
+private class Counter(var count: Int)
+
+@Composable
+private fun CountingLayout(modifier: Modifier, counter: Counter) {
+    Layout(
+        modifier = modifier,
+        children = {}
+    ) { _, constraints ->
+        counter.count++
+        layout(constraints.minWidth, constraints.minHeight) {}
+    }
+}
+
 /**
  * A simple test layout that does the bare minimum required to lay out an arbitrary number of
  * children reasonably.  Useful for Semantics hierarchy testing
diff --git a/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/semantics/SemanticsModifier.kt b/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/semantics/SemanticsModifier.kt
index c14fe67..4417986 100644
--- a/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/semantics/SemanticsModifier.kt
+++ b/ui/ui-core/src/commonMain/kotlin/androidx/ui/core/semantics/SemanticsModifier.kt
@@ -56,6 +56,20 @@
         private var lastIdentifier = AtomicInt(0)
         fun generateSemanticsId() = lastIdentifier.addAndGet(1)
     }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is SemanticsModifierCore) return false
+
+        if (id != other.id) return false
+        if (semanticsConfiguration != other.semanticsConfiguration) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        return 31 * semanticsConfiguration.hashCode() + id.hashCode()
+    }
 }
 
 /**