Merge "Revert "Fixed bug having to do with Row/Column not having the right intrinsic settings"" into androidx-main
diff --git a/compose/foundation/foundation/api/current.ignore b/compose/foundation/foundation/api/current.ignore
index dab733e..ea44979 100644
--- a/compose/foundation/foundation/api/current.ignore
+++ b/compose/foundation/foundation/api/current.ignore
@@ -1,5 +1,19 @@
// Baseline format: 1.0
+AddedAbstractMethod: androidx.compose.foundation.gestures.DraggableAnchors#anchorAt(int):
+ Added method androidx.compose.foundation.gestures.DraggableAnchors.anchorAt(int)
+AddedAbstractMethod: androidx.compose.foundation.gestures.DraggableAnchors#hasPositionFor(T):
+ Added method androidx.compose.foundation.gestures.DraggableAnchors.hasPositionFor(T)
+AddedAbstractMethod: androidx.compose.foundation.gestures.DraggableAnchors#maxPosition():
+ Added method androidx.compose.foundation.gestures.DraggableAnchors.maxPosition()
+AddedAbstractMethod: androidx.compose.foundation.gestures.DraggableAnchors#minPosition():
+ Added method androidx.compose.foundation.gestures.DraggableAnchors.minPosition()
+AddedAbstractMethod: androidx.compose.foundation.gestures.DraggableAnchors#positionAt(int):
+ Added method androidx.compose.foundation.gestures.DraggableAnchors.positionAt(int)
AddedAbstractMethod: androidx.compose.foundation.lazy.grid.LazyGridItemInfo#getSpan():
Added method androidx.compose.foundation.lazy.grid.LazyGridItemInfo.getSpan()
AddedAbstractMethod: androidx.compose.foundation.lazy.grid.LazyGridLayoutInfo#getMaxSpan():
Added method androidx.compose.foundation.lazy.grid.LazyGridLayoutInfo.getMaxSpan()
+
+
+ParameterNameChange: androidx.compose.foundation.gestures.DraggableAnchors#positionOf(T) parameter #0:
+ Attempted to change parameter name from value to anchor in method androidx.compose.foundation.gestures.DraggableAnchors.positionOf
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index d2cfb40..e3708d9 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -413,6 +413,7 @@
method public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, boolean reverseDirection, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional boolean startDragImmediately, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
method public static suspend <T> Object? animateTo(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public static suspend <T> Object? animateToWithDecay(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, float velocity, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, kotlin.coroutines.Continuation<? super java.lang.Float>);
+ method public static inline <T> void forEach(androidx.compose.foundation.gestures.DraggableAnchors<T>, kotlin.jvm.functions.Function2<? super T,? super java.lang.Float,kotlin.Unit> block);
method public static suspend <T> Object? snapTo(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
}
@@ -509,14 +510,15 @@
}
public interface DraggableAnchors<T> {
+ method public T? anchorAt(int index);
method public T? closestAnchor(float position);
method public T? closestAnchor(float position, boolean searchUpwards);
- method public void forEach(kotlin.jvm.functions.Function2<? super T,? super java.lang.Float,kotlin.Unit> block);
method public int getSize();
- method public boolean hasAnchorFor(T value);
- method public float maxAnchor();
- method public float minAnchor();
- method public float positionOf(T value);
+ method public boolean hasPositionFor(T anchor);
+ method public float maxPosition();
+ method public float minPosition();
+ method public float positionAt(int index);
+ method public float positionOf(T anchor);
property public abstract int size;
}
diff --git a/compose/foundation/foundation/api/restricted_current.ignore b/compose/foundation/foundation/api/restricted_current.ignore
index dab733e..ea44979 100644
--- a/compose/foundation/foundation/api/restricted_current.ignore
+++ b/compose/foundation/foundation/api/restricted_current.ignore
@@ -1,5 +1,19 @@
// Baseline format: 1.0
+AddedAbstractMethod: androidx.compose.foundation.gestures.DraggableAnchors#anchorAt(int):
+ Added method androidx.compose.foundation.gestures.DraggableAnchors.anchorAt(int)
+AddedAbstractMethod: androidx.compose.foundation.gestures.DraggableAnchors#hasPositionFor(T):
+ Added method androidx.compose.foundation.gestures.DraggableAnchors.hasPositionFor(T)
+AddedAbstractMethod: androidx.compose.foundation.gestures.DraggableAnchors#maxPosition():
+ Added method androidx.compose.foundation.gestures.DraggableAnchors.maxPosition()
+AddedAbstractMethod: androidx.compose.foundation.gestures.DraggableAnchors#minPosition():
+ Added method androidx.compose.foundation.gestures.DraggableAnchors.minPosition()
+AddedAbstractMethod: androidx.compose.foundation.gestures.DraggableAnchors#positionAt(int):
+ Added method androidx.compose.foundation.gestures.DraggableAnchors.positionAt(int)
AddedAbstractMethod: androidx.compose.foundation.lazy.grid.LazyGridItemInfo#getSpan():
Added method androidx.compose.foundation.lazy.grid.LazyGridItemInfo.getSpan()
AddedAbstractMethod: androidx.compose.foundation.lazy.grid.LazyGridLayoutInfo#getMaxSpan():
Added method androidx.compose.foundation.lazy.grid.LazyGridLayoutInfo.getMaxSpan()
+
+
+ParameterNameChange: androidx.compose.foundation.gestures.DraggableAnchors#positionOf(T) parameter #0:
+ Attempted to change parameter name from value to anchor in method androidx.compose.foundation.gestures.DraggableAnchors.positionOf
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index 19498c7..37aaab2 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -415,6 +415,7 @@
method public static <T> androidx.compose.ui.Modifier anchoredDraggable(androidx.compose.ui.Modifier, androidx.compose.foundation.gestures.AnchoredDraggableState<T> state, boolean reverseDirection, androidx.compose.foundation.gestures.Orientation orientation, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional boolean startDragImmediately, optional androidx.compose.foundation.gestures.FlingBehavior? flingBehavior);
method public static suspend <T> Object? animateTo(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> animationSpec, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public static suspend <T> Object? animateToWithDecay(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, float velocity, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> snapAnimationSpec, optional androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> decayAnimationSpec, kotlin.coroutines.Continuation<? super java.lang.Float>);
+ method public static inline <T> void forEach(androidx.compose.foundation.gestures.DraggableAnchors<T>, kotlin.jvm.functions.Function2<? super T,? super java.lang.Float,kotlin.Unit> block);
method public static suspend <T> Object? snapTo(androidx.compose.foundation.gestures.AnchoredDraggableState<T>, T targetValue, kotlin.coroutines.Continuation<? super kotlin.Unit>);
}
@@ -511,14 +512,15 @@
}
public interface DraggableAnchors<T> {
+ method public T? anchorAt(int index);
method public T? closestAnchor(float position);
method public T? closestAnchor(float position, boolean searchUpwards);
- method public void forEach(kotlin.jvm.functions.Function2<? super T,? super java.lang.Float,kotlin.Unit> block);
method public int getSize();
- method public boolean hasAnchorFor(T value);
- method public float maxAnchor();
- method public float minAnchor();
- method public float positionOf(T value);
+ method public boolean hasPositionFor(T anchor);
+ method public float maxPosition();
+ method public float minPosition();
+ method public float positionAt(int index);
+ method public float positionOf(T anchor);
property public abstract int size;
}
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/DraggableAnchorsBenchmark.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/DraggableAnchorsBenchmark.kt
index 8b3472f..c500e30 100644
--- a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/DraggableAnchorsBenchmark.kt
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/DraggableAnchorsBenchmark.kt
@@ -102,7 +102,7 @@
}
@Test
- fun hasAnchorFor() {
+ fun hasPositionFor() {
val anchors = DraggableAnchors {
DraggableAnchorsSampleValue.Start at 0f
DraggableAnchorsSampleValue.HalfStart at 100f
@@ -110,11 +110,11 @@
DraggableAnchorsSampleValue.HalfEnd at 300f
DraggableAnchorsSampleValue.End at 400f
}
- benchmarkRule.measureRepeated { anchors.hasAnchorFor(DraggableAnchorsSampleValue.Center) }
+ benchmarkRule.measureRepeated { anchors.hasPositionFor(DraggableAnchorsSampleValue.Center) }
}
@Test
- fun minAnchor() {
+ fun minPosition() {
val anchors = DraggableAnchors {
DraggableAnchorsSampleValue.Start at 0f
DraggableAnchorsSampleValue.HalfStart at 100f
@@ -122,11 +122,11 @@
DraggableAnchorsSampleValue.HalfEnd at 300f
DraggableAnchorsSampleValue.End at 400f
}
- benchmarkRule.measureRepeated { anchors.minAnchor() }
+ benchmarkRule.measureRepeated { anchors.minPosition() }
}
@Test
- fun maxAnchor() {
+ fun maxPosition() {
val anchors = DraggableAnchors {
DraggableAnchorsSampleValue.Start at 0f
DraggableAnchorsSampleValue.HalfStart at 100f
@@ -134,6 +134,6 @@
DraggableAnchorsSampleValue.HalfEnd at 300f
DraggableAnchorsSampleValue.End at 400f
}
- benchmarkRule.measureRepeated { anchors.maxAnchor() }
+ benchmarkRule.measureRepeated { anchors.maxPosition() }
}
}
diff --git a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt
index baf477f..499c99d 100644
--- a/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt
+++ b/compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/AnchoredDraggableSample.kt
@@ -28,6 +28,7 @@
import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.foundation.gestures.anchoredDraggable
import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.gestures.forEach
import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -331,7 +332,10 @@
state =
rememberDraggableState { delta ->
offset =
- (offset + delta).coerceIn(anchors.minAnchor(), anchors.maxAnchor())
+ (offset + delta).coerceIn(
+ anchors.minPosition(),
+ anchors.maxPosition()
+ )
},
orientation = Orientation.Horizontal,
onDragStopped = { velocity ->
diff --git a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/gestures/DraggableAnchorsTest.kt b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/gestures/DraggableAnchorsTest.kt
index 73ac5ed..dbe355f 100644
--- a/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/gestures/DraggableAnchorsTest.kt
+++ b/compose/foundation/foundation/src/androidUnitTest/kotlin/androidx/compose/foundation/gestures/DraggableAnchorsTest.kt
@@ -68,28 +68,28 @@
}
@Test
- fun draggableAnchors_minAnchor() {
+ fun draggableAnchors_minPosition() {
val anchors = DraggableAnchors {
A at -100f
B at 100f
}
- assertThat(anchors.minAnchor()).isEqualTo(-100f)
+ assertThat(anchors.minPosition()).isEqualTo(-100f)
}
@Test
- fun draggableAnchors_maxAnchor() {
+ fun draggableAnchors_maxPosition() {
val anchors = DraggableAnchors {
A at -100f
B at 100f
}
- assertThat(anchors.maxAnchor()).isEqualTo(100f)
+ assertThat(anchors.maxPosition()).isEqualTo(100f)
}
@Test
- fun draggableAnchors_hasAnchorFor() {
+ fun draggableAnchors_hasPositionFor() {
val anchors = DraggableAnchors { A at 100f }
assertThat(anchors.positionOf(A)).isEqualTo(100f)
- assertThat(anchors.hasAnchorFor(A)).isTrue()
+ assertThat(anchors.hasPositionFor(A)).isTrue()
}
}
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
index c036c6d..9d6d74a 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/AnchoredDraggable.kt
@@ -17,8 +17,6 @@
package androidx.compose.foundation.gestures
import androidx.annotation.FloatRange
-import androidx.collection.MutableObjectFloatMap
-import androidx.collection.ObjectFloatMap
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.AnimationState
import androidx.compose.animation.core.DecayAnimationSpec
@@ -435,8 +433,8 @@
availableVelocity ->
val consumed = fling(velocity.reverseIfNeeded().toFloat())
val currentOffset = state.requireOffset()
- val minAnchor = state.anchors.minAnchor()
- val maxAnchor = state.anchors.maxAnchor()
+ val minAnchor = state.anchors.minPosition()
+ val maxAnchor = state.anchors.maxPosition()
// return consumed velocity only if we are reaching the min/max anchors
if (currentOffset >= maxAnchor || currentOffset <= minAnchor) {
consumed.toVelocity()
@@ -538,27 +536,31 @@
* Structure that represents the anchors of a [AnchoredDraggableState].
*
* See the DraggableAnchors factory method to construct drag anchors using a default implementation.
+ * This structure does not make any guarantees about ordering of the anchors.
*/
interface DraggableAnchors<T> {
+ /** The amount of anchors */
+ val size: Int
+
/**
- * Get the anchor position for an associated [value]
+ * Get the anchor position for an associated [anchor]
*
- * @param value The value to look up
+ * @param anchor The value to look up
* @return The position of the anchor, or [Float.NaN] if the anchor does not exist
*/
- fun positionOf(value: T): Float
+ fun positionOf(anchor: T): Float
/**
- * Whether there is an anchor position associated with the [value]
+ * Whether there is an anchor position associated with the [anchor]
*
- * @param value The value to look up
+ * @param anchor The value to look up
* @return true if there is an anchor for this value, false if there is no anchor for this value
*/
- fun hasAnchorFor(value: T): Boolean
+ fun hasPositionFor(anchor: T): Boolean
/**
- * Find the closest anchor to the [position].
+ * Find the closest anchor value to the [position].
*
* @param position The position to start searching from
* @return The closest anchor or null if the anchors are empty
@@ -566,7 +568,7 @@
fun closestAnchor(position: Float): T?
/**
- * Find the closest anchor to the [position], in the specified direction.
+ * Find the closest anchor value to the [position], in the specified direction.
*
* @param position The position to start searching from
* @param searchUpwards Whether to search upwards from the current position or downwards
@@ -575,20 +577,31 @@
fun closestAnchor(position: Float, searchUpwards: Boolean): T?
/** The smallest anchor, or [Float.NEGATIVE_INFINITY] if the anchors are empty. */
- fun minAnchor(): Float
+ fun minPosition(): Float
/** The biggest anchor, or [Float.POSITIVE_INFINITY] if the anchors are empty. */
- fun maxAnchor(): Float
+ fun maxPosition(): Float
+
+ /** Get the anchor key at the specified index, or null if the index is out of bounds. */
+ fun anchorAt(index: Int): T?
/**
- * Iterate over all the anchors and corresponding positions.
- *
- * @param block The action to invoke with the anchor and position
+ * Get the anchor position at the specified index, or [Float.NaN] if the index is out of bounds.
*/
- fun forEach(block: (anchor: T, position: Float) -> Unit)
+ fun positionAt(index: Int): Float
+}
- /** The amount of anchors */
- val size: Int
+/**
+ * Iterate over all the anchors.
+ *
+ * @param block The action to invoke with the key and position
+ */
+inline fun <T> DraggableAnchors<T>.forEach(block: (key: T, position: Float) -> Unit) {
+ for (i in 0 until size) {
+ val key =
+ requireNotNull(anchorAt(i)) { "There was no key at index $i. Please report a bug." }
+ block(key, positionAt(i))
+ }
}
/**
@@ -598,7 +611,8 @@
*/
class DraggableAnchorsConfig<T> {
- internal val anchors = MutableObjectFloatMap<T>()
+ internal val keys = mutableListOf<T>()
+ internal var positions = FloatArray(size = 5) { Float.NaN }
/**
* Set the anchor position for [this] anchor.
@@ -607,7 +621,26 @@
*/
@Suppress("BuilderSetStyle")
infix fun T.at(position: Float) {
- anchors[this] = position
+ keys.add(this)
+ if (positions.size < keys.size) {
+ expandPositions()
+ }
+ positions[keys.size - 1] = position
+ }
+
+ internal fun buildPositions(): FloatArray {
+ // We might have expanded more than we actually need, so trim the array
+ return positions.copyOfRange(
+ fromIndex = 0,
+ // toIndex is exclusive, so we need to take the entire keys.size, not just - 1
+ toIndex = keys.size
+ )
+ }
+
+ internal fun buildKeys(): List<T> = keys
+
+ private fun expandPositions() {
+ positions = positions.copyOf(keys.size + 2)
}
}
@@ -618,8 +651,10 @@
* @return A new [DraggableAnchors] instance with the anchor positions set by the `builder`
* function.
*/
-fun <T : Any> DraggableAnchors(builder: DraggableAnchorsConfig<T>.() -> Unit): DraggableAnchors<T> =
- MapDraggableAnchors(DraggableAnchorsConfig<T>().apply(builder).anchors)
+fun <T : Any> DraggableAnchors(builder: DraggableAnchorsConfig<T>.() -> Unit): DraggableAnchors<T> {
+ val config = DraggableAnchorsConfig<T>().apply(builder)
+ return DefaultDraggableAnchors(keys = config.buildKeys(), anchors = config.buildPositions())
+}
/**
* Scope used for suspending anchored drag blocks. Allows to set [AnchoredDraggableState.offset] to
@@ -1094,7 +1129,7 @@
dragPriority: MutatePriority = MutatePriority.Default,
block: suspend AnchoredDragScope.(anchor: DraggableAnchors<T>, targetValue: T) -> Unit
) {
- if (anchors.hasAnchorFor(targetValue)) {
+ if (anchors.hasPositionFor(targetValue)) {
try {
dragMutex.mutate(dragPriority) {
dragTarget = targetValue
@@ -1128,8 +1163,8 @@
*/
internal fun newOffsetForDelta(delta: Float) =
((if (offset.isNaN()) 0f else offset) + delta).coerceIn(
- anchors.minAnchor(),
- anchors.maxAnchor()
+ anchors.minPosition(),
+ anchors.maxPosition()
)
/**
@@ -1482,84 +1517,95 @@
}
}
-private fun <T> emptyDraggableAnchors() = MapDraggableAnchors<T>(MutableObjectFloatMap())
+private fun <T> emptyDraggableAnchors() = DefaultDraggableAnchors<T>(emptyList(), FloatArray(0))
-private class MapDraggableAnchors<T>(private val anchors: ObjectFloatMap<T>) : DraggableAnchors<T> {
+private val GetOrNan: (Int) -> Float = { Float.NaN }
- override fun positionOf(value: T): Float = anchors.getOrDefault(value, Float.NaN)
+private class DefaultDraggableAnchors<T>(
+ private val keys: List<T>,
+ private val anchors: FloatArray
+) : DraggableAnchors<T> {
- override fun hasAnchorFor(value: T) = anchors.containsKey(value)
+ init {
+ assert(keys.size == anchors.size) {
+ "DraggableAnchors were constructed with " +
+ "inconsistent key-value sizes. Keys: $keys | Anchors: ${anchors.toList()}"
+ }
+ }
+
+ override fun positionOf(anchor: T): Float {
+ val index = keys.indexOf(anchor)
+ return anchors.getOrElse(index, GetOrNan)
+ }
+
+ override fun hasPositionFor(anchor: T) = keys.indexOf(anchor) != -1
override fun closestAnchor(position: Float): T? {
- var minAnchor: T? = null
+ var minAnchorIndex = -1
var minDistance = Float.POSITIVE_INFINITY
- anchors.forEach { anchor, anchorPosition ->
+ anchors.forEachIndexed { index, anchorPosition ->
val distance = abs(position - anchorPosition)
if (distance <= minDistance) {
- minAnchor = anchor
+ minAnchorIndex = index
minDistance = distance
}
}
- return minAnchor
+ return keys[minAnchorIndex]
}
override fun closestAnchor(position: Float, searchUpwards: Boolean): T? {
- var minAnchor: T? = null
+ var minAnchorIndex = -1
var minDistance = Float.POSITIVE_INFINITY
- anchors.forEach { anchor, anchorPosition ->
+ anchors.forEachIndexed { index, anchorPosition ->
val delta = if (searchUpwards) anchorPosition - position else position - anchorPosition
val distance = if (delta < 0) Float.POSITIVE_INFINITY else delta
if (distance <= minDistance) {
- minAnchor = anchor
+ minAnchorIndex = index
minDistance = distance
}
}
- return minAnchor
+ return keys[minAnchorIndex]
}
- override fun minAnchor() = anchors.minValueOrNaN()
+ override fun minPosition() = anchors.minOrNull() ?: Float.NaN
- override fun maxAnchor() = anchors.maxValueOrNaN()
+ override fun maxPosition() = anchors.maxOrNull() ?: Float.NaN
- override val size: Int
- get() = anchors.size
+ override val size = anchors.size
+
+ override fun anchorAt(index: Int) = keys.getOrNull(index)
+
+ override fun positionAt(index: Int) = anchors.getOrElse(index, GetOrNan)
override fun equals(other: Any?): Boolean {
if (this === other) return true
- if (other !is MapDraggableAnchors<*>) return false
- return anchors == other.anchors
+ other as DefaultDraggableAnchors<*>
+
+ if (keys != other.keys) return false
+ if (!anchors.contentEquals(other.anchors)) return false
+ if (size != other.size) return false
+
+ return true
}
- override fun hashCode() = 31 * anchors.hashCode()
-
- override fun toString() = "MapDraggableAnchors($anchors)"
-
- override fun forEach(block: (anchor: T, position: Float) -> Unit) {
- anchors.forEach(block)
+ override fun hashCode(): Int {
+ var result = keys.hashCode()
+ result = 31 * result + anchors.contentHashCode()
+ result = 31 * result + size
+ return result
}
-}
-private fun <K> ObjectFloatMap<K>.minValueOrNaN(): Float {
- if (size == 1) return Float.NaN
- var minValue = Float.POSITIVE_INFINITY
- forEachValue { value ->
- if (value <= minValue) {
- minValue = value
+ override fun toString() = buildString {
+ append("DraggableAnchors(anchors={")
+ for (i in 0 until size) {
+ append("${anchorAt(0)}=${positionAt(i)}")
+ if (i < size - 1) {
+ append(", ")
+ }
}
+ append("})")
}
- return minValue
-}
-
-private fun <K> ObjectFloatMap<K>.maxValueOrNaN(): Float {
- if (size == 1) return Float.NaN
- var maxValue = Float.NEGATIVE_INFINITY
- forEachValue { value ->
- if (value >= maxValue) {
- maxValue = value
- }
- }
- return maxValue
}
internal val AnchoredDraggableMinFlingVelocity = 125.dp
diff --git a/compose/material/material/src/androidUnitTest/kotlin/androidx/compose/material/DraggableAnchorsTest.kt b/compose/material/material/src/androidUnitTest/kotlin/androidx/compose/material/DraggableAnchorsTest.kt
index 1925376..420079f 100644
--- a/compose/material/material/src/androidUnitTest/kotlin/androidx/compose/material/DraggableAnchorsTest.kt
+++ b/compose/material/material/src/androidUnitTest/kotlin/androidx/compose/material/DraggableAnchorsTest.kt
@@ -70,7 +70,7 @@
}
@Test
- fun draggableAnchors_minAnchor() {
+ fun draggableAnchors_minPosition() {
val anchors = DraggableAnchors {
A at -100f
B at 100f
@@ -79,7 +79,7 @@
}
@Test
- fun draggableAnchors_maxAnchor() {
+ fun draggableAnchors_maxPosition() {
val anchors = DraggableAnchors {
A at -100f
B at 100f
@@ -88,7 +88,7 @@
}
@Test
- fun draggableAnchors_hasAnchorFor() {
+ fun draggableAnchors_hasPositionFor() {
val anchors = DraggableAnchors { A at 100f }
assertThat(anchors.positionOf(A)).isEqualTo(100f)
assertThat(anchors.hasAnchorFor(A)).isTrue()
diff --git a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt
index 5c539bbe..350d763 100644
--- a/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt
+++ b/compose/ui/ui-graphics/src/androidInstrumentedTest/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayerTest.kt
@@ -16,7 +16,6 @@
package androidx.compose.ui.graphics.layer
-import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.PixelFormat
@@ -44,7 +43,6 @@
import androidx.compose.ui.graphics.PixelMap
import androidx.compose.ui.graphics.TestActivity
import androidx.compose.ui.graphics.TileMode
-import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
@@ -150,8 +148,7 @@
.toImageBitmap()
.toPixelMap()
.verifyQuadrants(Color.Red, Color.Red, Color.Red, Color.Red)
- },
- verifySoftwareRender = false // Only supported in hardware accelerated use cases
+ }
)
}
@@ -196,6 +193,9 @@
)
}
+ // this test is failing on API 21 as there toImageBitmap() is using software rendering
+ // and we reverted the software rendering b/333866398
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP_MR1)
@Test
fun testPersistenceDrawAfterHwuiDiscardsDisplaylists() {
// Layer persistence calls should not fail even if the DisplayList is discarded beforehand
@@ -212,8 +212,7 @@
}
drawIntoCanvas { layer.drawForPersistence(it) }
},
- verify = { it.verifyQuadrants(Color.Red, Color.Red, Color.Red, Color.Red) },
- verifySoftwareRender = false
+ verify = { it.verifyQuadrants(Color.Red, Color.Red, Color.Red, Color.Red) }
)
}
@@ -741,8 +740,7 @@
}
assertTrue(shadowPixelCount > 0)
},
- usePixelCopy = true,
- verifySoftwareRender = false // Elevation only supported with hardware acceleration
+ usePixelCopy = true
)
}
@@ -819,7 +817,6 @@
}
},
usePixelCopy = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O,
- verifySoftwareRender = false // Elevation only supported with hardware acceleration
)
}
@@ -877,8 +874,7 @@
}
Assert.assertTrue(shadowPixelCount > 0)
},
- usePixelCopy = true,
- verifySoftwareRender = false // Elevation only supported with hardware acceleration
+ usePixelCopy = true
)
}
@@ -971,8 +967,7 @@
)
}
},
- usePixelCopy = true,
- verifySoftwareRender = false // Elevation only supported with hardware acceleration
+ usePixelCopy = true
)
}
@@ -1008,8 +1003,7 @@
}
assertTrue(nonPureRedCount > 0)
},
- entireScene = false,
- verifySoftwareRender = false // RenderEffect only supported with hardware acceleration
+ entireScene = false
)
}
@@ -1122,8 +1116,7 @@
assertPixelColor(Color.Black, 0, height - 1)
assertPixelColor(expectedCenter, width / 2, height / 2)
}
- },
- verifySoftwareRender = false // ModulateAlpha only supported with hardware acceleration
+ }
)
}
@@ -1488,8 +1481,7 @@
block: DrawScope.(GraphicsContext) -> Unit,
verify: (suspend (PixelMap) -> Unit)? = null,
entireScene: Boolean = false,
- usePixelCopy: Boolean = false,
- verifySoftwareRender: Boolean = true
+ usePixelCopy: Boolean = false
) {
var scenario: ActivityScenario<TestActivity>? = null
var androidGraphicsContext: GraphicsContext? = null
@@ -1583,16 +1575,6 @@
bitmap.toPixelMap()
}
runBlocking { verify(pixelMap) }
- if (verifySoftwareRender) {
- val softwareRenderLatch = CountDownLatch(1)
- var softwareBitmap: Bitmap? = null
- testActivity!!.runOnUiThread {
- softwareBitmap = doSoftwareRender(target)
- softwareRenderLatch.countDown()
- }
- assertTrue(softwareRenderLatch.await(300, TimeUnit.MILLISECONDS))
- runBlocking { verify(softwareBitmap!!.asImageBitmap().toPixelMap()) }
- }
}
} finally {
val detachLatch = CountDownLatch(1)
@@ -1613,13 +1595,6 @@
}
}
- private fun doSoftwareRender(target: View): Bitmap {
- val bitmap = Bitmap.createBitmap(target.width, target.height, Bitmap.Config.ARGB_8888)
- val softwareCanvas = Canvas(bitmap)
- target.draw(softwareCanvas)
- return bitmap
- }
-
private class GraphicsContextHostDrawable(
val graphicsContext: GraphicsContext,
val block: DrawScope.(GraphicsContext) -> Unit
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidGraphicsContext.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidGraphicsContext.android.kt
index dbe1c2f..5081429 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidGraphicsContext.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/AndroidGraphicsContext.android.kt
@@ -131,35 +131,29 @@
override fun createGraphicsLayer(): GraphicsLayer {
synchronized(lock) {
val ownerId = getUniqueDrawingId(ownerView)
- val reusedLayer = layerManager.takeFromCache(ownerId)
- val layer =
- if (reusedLayer != null) {
- reusedLayer
+ val layerImpl =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ GraphicsLayerV29(ownerId)
+ } else if (
+ isRenderNodeCompatible && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+ ) {
+ try {
+ GraphicsLayerV23(ownerView, ownerId)
+ } catch (_: Throwable) {
+ // If we ever failed to create an instance of the RenderNode stub
+ // based
+ // GraphicsLayer, always fallback to creation of View based layers
+ // as it is
+ // unlikely that subsequent attempts to create a GraphicsLayer with
+ // RenderNode
+ // stubs would be successful.
+ isRenderNodeCompatible = false
+ GraphicsViewLayer(obtainViewLayerContainer(ownerView), ownerId)
+ }
} else {
- val layerImpl =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- GraphicsLayerV29()
- } else if (
- isRenderNodeCompatible && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
- ) {
- try {
- GraphicsLayerV23(ownerView)
- } catch (_: Throwable) {
- // If we ever failed to create an instance of the RenderNode stub
- // based
- // GraphicsLayer, always fallback to creation of View based layers
- // as it is
- // unlikely that subsequent attempts to create a GraphicsLayer with
- // RenderNode
- // stubs would be successful.
- isRenderNodeCompatible = false
- GraphicsViewLayer(obtainViewLayerContainer(ownerView))
- }
- } else {
- GraphicsViewLayer(obtainViewLayerContainer(ownerView))
- }
- GraphicsLayer(layerImpl, layerManager, ownerId)
+ GraphicsViewLayer(obtainViewLayerContainer(ownerView), ownerId)
}
+ val layer = GraphicsLayer(layerImpl, layerManager)
layerManager.persist(layer)
return layer
}
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayer.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayer.android.kt
index 829d419..bbd27e7 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayer.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/AndroidGraphicsLayer.android.kt
@@ -36,7 +36,6 @@
import androidx.compose.ui.graphics.RenderEffect
import androidx.compose.ui.graphics.asAndroidPath
import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
import androidx.compose.ui.graphics.drawscope.DefaultDensity
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.layer.LayerManager.Companion.isRobolectric
@@ -50,11 +49,7 @@
@Suppress("NotCloseable")
actual class GraphicsLayer
-internal constructor(
- internal val impl: GraphicsLayerImpl,
- private val layerManager: LayerManager,
- ownerViewId: Long
-) {
+internal constructor(internal val impl: GraphicsLayerImpl, private val layerManager: LayerManager) {
private var density = DefaultDensity
private var layoutDirection = LayoutDirection.Ltr
private var drawBlock: DrawScope.() -> Unit = {}
@@ -69,7 +64,6 @@
private var outlinePath: Path? = null
private var roundRectClipPath: Path? = null
private var usePathForClip = false
- private var softwareDrawScope: CanvasDrawScope? = null
// Paint used only in Software rendering scenarios for API 21 when rendering to a Bitmap
private var softwareLayerPaint: Paint? = null
@@ -466,7 +460,7 @@
}
internal fun drawForPersistence(canvas: Canvas) {
- if (canvas.nativeCanvas.isHardwareAccelerated || impl.supportsSoftwareRendering) {
+ if (canvas.nativeCanvas.isHardwareAccelerated) {
recreateDisplayListIfNeeded()
impl.draw(canvas)
}
@@ -535,13 +529,7 @@
parentLayer?.addSubLayer(this)
- if (canvas.nativeCanvas.isHardwareAccelerated || impl.supportsSoftwareRendering) {
- impl.draw(canvas)
- } else {
- val drawScope = softwareDrawScope ?: CanvasDrawScope().also { softwareDrawScope = it }
- drawScope.draw(density, layoutDirection, canvas, size.toSize(), drawBlock)
- }
-
+ impl.draw(canvas)
if (willClipPath) {
canvas.restore()
}
@@ -562,10 +550,8 @@
discardContentIfReleasedAndHaveNoParentLayerUsages()
}
- private var skipOutlineConfiguration = false
-
private fun configureOutline() {
- if (outlineDirty && !skipOutlineConfiguration) {
+ if (outlineDirty) {
val outlineIsNeeded = clip || shadowElevation > 0f
if (!outlineIsNeeded) {
impl.setOutline(null)
@@ -593,8 +579,8 @@
impl.setOutline(roundRectOutline)
}
}
- outlineDirty = false
}
+ outlineDirty = false
}
private inline fun <T> resolveOutlinePosition(block: (Offset, Size) -> T): T {
@@ -676,8 +662,8 @@
* The uniqueDrawingId of the owner view of this graphics layer. This is used by tooling to
* match a layer to the associated owner View.
*/
- var ownerViewId: Long = ownerViewId
- private set
+ val ownerViewId: Long
+ get() = impl.ownerId
actual val outline: Outline
get() {
@@ -820,51 +806,6 @@
*/
actual suspend fun toImageBitmap(): ImageBitmap = SnapshotImpl.toBitmap(this).asImageBitmap()
- internal fun reuse(ownerViewId: Long) {
- // apply new owner id
- this.ownerViewId = ownerViewId
-
- // mark the layer as not released
- isReleased = false
-
- // prepare the implementation to be reused
- impl.onReused()
-
- // forget the previous draw lambda
- drawBlock = {}
-
- // multiple of the setters can cause configureOutline() calls, however we don't want
- // to execute it multiple times, so we set this flag to true
- skipOutlineConfiguration = true
-
- // reset properties to the default values
- alpha = 1f
- blendMode = BlendMode.SrcOver
- colorFilter = null
- pivotOffset = Offset.Unspecified
- scaleX = 1f
- scaleY = 1f
- translationX = 0f
- translationY = 0f
- shadowElevation = 0f
- rotationX = 0f
- rotationY = 0f
- rotationZ = 0f
- ambientShadowColor = Color.Black
- spotShadowColor = Color.Black
- cameraDistance = DefaultCameraDistance
- renderEffect = null
- compositingStrategy = CompositingStrategy.Auto
- clip = false
- size = IntSize.Zero
- topLeft = IntOffset.Zero
- setRectOutline()
-
- // unset this flag. if outlineDirty is true we will call configureOutline() again when
- // the layer will be drawn for the first time.
- skipOutlineConfiguration = false
- }
-
companion object {
// See b/340578758, fallback to software rendering for Robolectric tests
@@ -891,6 +832,12 @@
*/
val layerId: Long
+ /**
+ * The uniqueDrawingId of the owner view of this graphics layer. This is used by tooling to
+ * match a layer to the associated owner AndroidComposeView.
+ */
+ val ownerId: Long
+
/** @see GraphicsLayer.compositingStrategy */
var compositingStrategy: CompositingStrategy
@@ -957,14 +904,6 @@
*/
fun setOutline(outline: AndroidOutline?)
- /**
- * Flag to determine if the layer implementation has a software backed implementation On Android
- * L we conditionally also record drawing commands into a Picture as it does not natively
- * support rendering into a Bitmap with hardware acceleration
- */
- val supportsSoftwareRendering: Boolean
- get() = false
-
/** Draw the GraphicsLayer into the provided canvas */
fun draw(canvas: Canvas)
@@ -985,8 +924,6 @@
/** Calculate the current transformation matrix for the layer implementation */
fun calculateMatrix(): android.graphics.Matrix
- fun onReused() {}
-
companion object {
val DefaultDrawBlock: DrawScope.() -> Unit = { drawRect(Color.Transparent) }
}
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV23.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV23.android.kt
index 02d56e4..59cd922 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV23.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV23.android.kt
@@ -47,6 +47,7 @@
@RequiresApi(Build.VERSION_CODES.M)
internal class GraphicsLayerV23(
ownerView: View,
+ override val ownerId: Long,
private val canvasHolder: CanvasHolder = CanvasHolder(),
private val canvasDrawScope: CanvasDrawScope = CanvasDrawScope()
) : GraphicsLayerImpl {
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV29.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV29.android.kt
index 97691ec..7330a3d 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV29.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsLayerV29.android.kt
@@ -44,6 +44,7 @@
/** GraphicsLayer implementation for Android Q+ that uses the public RenderNode API */
@RequiresApi(Build.VERSION_CODES.Q)
internal class GraphicsLayerV29(
+ override val ownerId: Long,
private val canvasHolder: CanvasHolder = CanvasHolder(),
private val canvasDrawScope: CanvasDrawScope = CanvasDrawScope()
) : GraphicsLayerImpl {
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsViewLayer.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsViewLayer.android.kt
index edde423..26389c5 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsViewLayer.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/GraphicsViewLayer.android.kt
@@ -102,10 +102,6 @@
this.parentLayer = parentLayer
}
- fun resetDrawBlock() {
- drawBlock = DefaultDrawBlock
- }
-
init {
setWillNotDraw(false) // we WILL draw
this.clipBounds = null
@@ -157,6 +153,7 @@
internal class GraphicsViewLayer(
private val layerContainer: DrawChildContainer,
+ override val ownerId: Long,
val canvasHolder: CanvasHolder = CanvasHolder(),
canvasDrawScope: CanvasDrawScope = CanvasDrawScope()
) : GraphicsLayerImpl {
@@ -445,8 +442,6 @@
}
}
- override val supportsSoftwareRendering: Boolean = mayRenderInSoftware
-
private fun recordDrawingOperations() {
try {
canvasHolder.drawInto(PlaceholderCanvas) {
@@ -491,12 +486,6 @@
layerContainer.removeViewInLayout(viewLayer)
}
- override fun onReused() {
- viewLayer.resetDrawBlock()
- // it was removed in discardDisplayList()
- layerContainer.addView(viewLayer)
- }
-
companion object {
val mayRenderInSoftware = !isLockHardwareCanvasAvailable()
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/LayerManager.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/LayerManager.android.kt
index e11287f..ce86b5f 100644
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/LayerManager.android.kt
+++ b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/LayerManager.android.kt
@@ -19,8 +19,6 @@
import android.graphics.PixelFormat
import android.media.ImageReader
import android.os.Build
-import android.os.Build.VERSION.SDK_INT
-import android.os.Build.VERSION_CODES.M
import android.os.Looper
import android.os.Message
import android.view.Surface
@@ -37,8 +35,7 @@
*/
internal class LayerManager(val canvasHolder: CanvasHolder) {
- private val activeLayerSet = mutableScatterSetOf<GraphicsLayer>()
- private val nonActiveLayerCache = WeakCache<GraphicsLayer>()
+ private val layerSet = mutableScatterSetOf<GraphicsLayer>()
/**
* Create a placeholder ImageReader instance that we will use to issue a single draw call for
@@ -50,15 +47,12 @@
private val handler =
HandlerCompat.createAsync(Looper.getMainLooper()) {
- persistLayers(activeLayerSet)
+ persistLayers(layerSet)
true
}
- fun takeFromCache(ownerId: Long): GraphicsLayer? =
- nonActiveLayerCache.pop()?.also { it.reuse(ownerId) }
-
fun persist(layer: GraphicsLayer) {
- activeLayerSet.add(layer)
+ layerSet.add(layer)
if (!handler.hasMessages(0)) {
// we don't run persistLayers() synchronously in order to do less work as there
// might be a lot of new layers created during one frame. however we also want
@@ -71,11 +65,8 @@
}
fun release(layer: GraphicsLayer) {
- if (activeLayerSet.remove(layer)) {
+ if (layerSet.remove(layer)) {
layer.discardDisplayList()
- if (SDK_INT >= M) { // L throws during RenderThread when reusing the Views.
- nonActiveLayerCache.push(layer)
- }
}
}
@@ -137,7 +128,7 @@
*/
fun updateLayerPersistence() {
destroy()
- persistLayers(activeLayerSet)
+ persistLayers(layerSet)
}
companion object {
diff --git a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/WeakCache.android.kt b/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/WeakCache.android.kt
deleted file mode 100644
index 807ae0d..0000000
--- a/compose/ui/ui-graphics/src/androidMain/kotlin/androidx/compose/ui/graphics/layer/WeakCache.android.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.ui.graphics.layer
-
-import androidx.compose.runtime.collection.mutableVectorOf
-import java.lang.ref.Reference
-import java.lang.ref.ReferenceQueue
-import java.lang.ref.WeakReference
-
-// It is a copy from androidx.compose.ui.platform
-/**
- * A simple collection that keeps values as [WeakReference]s. Elements are added with [push] and
- * removed with [pop].
- */
-internal class WeakCache<T> {
- private val values = mutableVectorOf<Reference<T>>()
- private val referenceQueue = ReferenceQueue<T>()
-
- /**
- * Add [element] to the collection as a [WeakReference]. It will be removed when garbage
- * collected or from [pop].
- */
- fun push(element: T) {
- clearWeakReferences()
- values += WeakReference(element, referenceQueue)
- }
-
- /**
- * Remove an element from the collection and return it. If no element is available, `null` is
- * returned.
- */
- fun pop(): T? {
- clearWeakReferences()
-
- while (values.isNotEmpty()) {
- val item = values.removeAt(values.lastIndex).get()
- if (item != null) {
- return item
- }
- }
- return null
- }
-
- /**
- * The number of elements currently in the collection. This may change between calls if the
- * references have been garbage collected.
- */
- val size: Int
- get() {
- clearWeakReferences()
- return values.size
- }
-
- private fun clearWeakReferences() {
- do {
- val item: Reference<out T>? = referenceQueue.poll()
- if (item != null) {
- @Suppress("UNCHECKED_CAST") values.remove(item as Reference<T>)
- }
- } while (item != null)
- }
-}
diff --git a/compose/ui/ui-graphics/src/androidUnitTest/kotlin/androidx/compose/ui/graphics/layer/RobolectricGraphicsLayerTest.kt b/compose/ui/ui-graphics/src/androidUnitTest/kotlin/androidx/compose/ui/graphics/layer/RobolectricGraphicsLayerTest.kt
index 8755ec4..851a970 100644
--- a/compose/ui/ui-graphics/src/androidUnitTest/kotlin/androidx/compose/ui/graphics/layer/RobolectricGraphicsLayerTest.kt
+++ b/compose/ui/ui-graphics/src/androidUnitTest/kotlin/androidx/compose/ui/graphics/layer/RobolectricGraphicsLayerTest.kt
@@ -65,6 +65,7 @@
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
@@ -82,6 +83,7 @@
val TEST_SIZE = IntSize(TEST_WIDTH, TEST_HEIGHT)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testGraphicsLayerBitmap() {
lateinit var layer: GraphicsLayer
@@ -119,6 +121,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testDrawLayer() {
var layer: GraphicsLayer? = null
@@ -139,6 +142,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testDrawAfterDiscard() {
var layer: GraphicsLayer? = null
@@ -160,6 +164,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testPersistenceDrawAfterHwuiDiscardsDisplaylists() {
// Layer persistence calls should not fail even if the DisplayList is discarded beforehand
@@ -177,6 +182,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testRecordLayerWithSize() {
graphicsLayerTest(
@@ -191,6 +197,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testRecordLayerWithOffset() {
var layer: GraphicsLayer? = null
@@ -213,6 +220,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testSetOffset() {
var layer: GraphicsLayer? = null
@@ -238,6 +246,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testSetAlpha() {
var layer: GraphicsLayer? = null
@@ -266,6 +275,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testSetScaleX() {
var layer: GraphicsLayer? = null
@@ -294,6 +304,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testSetScaleY() {
var layer: GraphicsLayer? = null
@@ -322,6 +333,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testDefaultPivot() {
var layer: GraphicsLayer? = null
@@ -347,6 +359,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testBottomRightPivot() {
var layer: GraphicsLayer? = null
@@ -371,6 +384,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testTranslationX() {
var layer: GraphicsLayer? = null
@@ -393,6 +407,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testRectOutlineWithNonZeroTopLeft() {
graphicsLayerTest(
@@ -414,6 +429,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testRoundRectOutlineWithNonZeroTopLeft() {
graphicsLayerTest(
@@ -435,6 +451,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testRecordOverwritesPreviousRecord() {
graphicsLayerTest(
@@ -448,6 +465,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testTranslationY() {
var layer: GraphicsLayer? = null
@@ -470,6 +488,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testRotationX() {
var layer: GraphicsLayer? = null
@@ -498,6 +517,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testRotationY() {
var layer: GraphicsLayer? = null
@@ -525,6 +545,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testRotationZ() {
var layer: GraphicsLayer? = null
@@ -579,6 +600,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testUnboundedClip() {
var layer: GraphicsLayer?
@@ -602,6 +624,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testBoundedClip() {
var layer: GraphicsLayer?
@@ -632,6 +655,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testCompositingStrategyAuto() {
var layer: GraphicsLayer?
@@ -671,6 +695,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testCompositingStrategyOffscreen() {
var layer: GraphicsLayer?
@@ -704,6 +729,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testCameraDistanceWithRotationY() {
var layer: GraphicsLayer?
@@ -732,6 +758,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testTintColorFilter() {
var layer: GraphicsLayer?
@@ -756,6 +783,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testBlendMode() {
var layer: GraphicsLayer?
@@ -807,6 +835,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testRectOutlineClip() {
var layer: GraphicsLayer?
@@ -851,6 +880,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testPathOutlineClip() {
var layer: GraphicsLayer?
@@ -906,6 +936,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testRoundRectOutlineClip() {
var layer: GraphicsLayer?
@@ -960,6 +991,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun setOutlineExtensionAppliesValuesCorrectly() {
graphicsLayerTest(
@@ -984,6 +1016,7 @@
)
}
+ @Ignore("Software rendering support was reverted. b/333866398")
@Test
fun testSwitchingFromClipToBoundsToClipToOutline() {
val targetColor = Color.Red
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
index 7b42b55..2f492d15 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/GraphicsLayerOwnerLayer.android.kt
@@ -28,6 +28,7 @@
import androidx.compose.ui.graphics.Matrix
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Paint
+import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.ReusableGraphicsLayerScope
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
@@ -37,6 +38,7 @@
import androidx.compose.ui.graphics.layer.GraphicsLayer
import androidx.compose.ui.graphics.layer.drawLayer
import androidx.compose.ui.graphics.layer.setOutline
+import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.layout.GraphicLayerInfo
import androidx.compose.ui.node.OwnedLayer
import androidx.compose.ui.unit.Density
@@ -75,6 +77,7 @@
private var mutatedFields: Int = 0
private var transformOrigin: TransformOrigin = TransformOrigin.Center
private var outline: Outline? = null
+ private var tmpPath: Path? = null
/**
* Optional paint used when the RenderNode is rendered on a software backed canvas and is
* somewhat transparent (i.e. alpha less than 1.0f)
@@ -223,13 +226,53 @@
private var drawnWithEnabledZ = false
override fun drawLayer(canvas: Canvas, parentLayer: GraphicsLayer?) {
- updateDisplayList()
- drawnWithEnabledZ = graphicsLayer.shadowElevation > 0
- scope.drawContext.also {
- it.canvas = canvas
- it.graphicsLayer = parentLayer
+ val androidCanvas = canvas.nativeCanvas
+ if (androidCanvas.isHardwareAccelerated) {
+ updateDisplayList()
+ drawnWithEnabledZ = graphicsLayer.shadowElevation > 0
+ scope.drawContext.also {
+ it.canvas = canvas
+ it.graphicsLayer = parentLayer
+ }
+ scope.drawLayer(graphicsLayer)
+ } else {
+ // TODO ideally there should be some solution for drawing a layer on a software
+ // accelerated canvas built in right into GraphicsLayer, as this workaround is not
+ // solving all the use cases. For example, some one can use layers directly via
+ // drawWithContent {
+ // layer.record {
+ // [email protected]()
+ // }
+ // drawLayer(layer)
+ // }
+ // and if someone would try to draw the whole ComposeView on software accelerated
+ // canvas it will just crash saying RenderNodes can't be drawn into this canvas.
+ // This issue is tracked in b/333866398
+ val left = graphicsLayer.topLeft.x.toFloat()
+ val top = graphicsLayer.topLeft.y.toFloat()
+ val right = left + size.width
+ val bottom = top + size.height
+ // If there is alpha applied, we must render into an offscreen buffer to
+ // properly blend the contents of this layer against the background content
+ if (graphicsLayer.alpha < 1.0f) {
+ val paint =
+ (softwareLayerPaint ?: Paint().also { softwareLayerPaint = it }).apply {
+ alpha = graphicsLayer.alpha
+ }
+ androidCanvas.saveLayer(left, top, right, bottom, paint.asFrameworkPaint())
+ } else {
+ canvas.save()
+ }
+ // If we are software rendered we must translate the canvas based on the offset provided
+ // in the move call which operates directly on the RenderNode
+ canvas.translate(left, top)
+ canvas.concat(getMatrix())
+ if (graphicsLayer.clip) {
+ clipManually(canvas)
+ }
+ drawBlock?.invoke(canvas, null)
+ canvas.restore()
}
- scope.drawLayer(graphicsLayer)
}
override fun updateDisplayList() {
@@ -299,6 +342,7 @@
requireNotNull(context) {
"currently reuse is only supported when we manage the layer lifecycle"
}
+ require(graphicsLayer.isReleased) { "layer should have been released before reuse" }
// recreate a layer
graphicsLayer = context.createGraphicsLayer()
@@ -371,4 +415,27 @@
1.0f
)
}
+
+ /**
+ * Manually clips the content of the RenderNodeLayer in the provided canvas. This is used only
+ * in software rendered use cases
+ */
+ private fun clipManually(canvas: Canvas) {
+ if (graphicsLayer.clip) {
+ when (val outline = graphicsLayer.outline) {
+ is Outline.Rectangle -> {
+ canvas.clipRect(outline.rect)
+ }
+ is Outline.Rounded -> {
+ val path = tmpPath ?: Path().also { tmpPath = it }
+ path.reset()
+ path.addRoundRect(outline.roundRect)
+ canvas.clipPath(path)
+ }
+ is Outline.Generic -> {
+ canvas.clipPath(outline.path)
+ }
+ }
+ }
+ }
}
diff --git a/privacysandbox/ads/ads-adservices/api/1.1.0-beta08.txt b/privacysandbox/ads/ads-adservices/api/1.1.0-beta08.txt
index b25a1b0..dca5235 100644
--- a/privacysandbox/ads/ads-adservices/api/1.1.0-beta08.txt
+++ b/privacysandbox/ads/ads-adservices/api/1.1.0-beta08.txt
@@ -92,11 +92,11 @@
}
@SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class GetAdSelectionDataRequest {
- ctor public GetAdSelectionDataRequest(optional androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller, optional android.net.Uri? coordinatorOriginUri);
+ ctor public GetAdSelectionDataRequest(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller, optional android.net.Uri? coordinatorOriginUri);
method public android.net.Uri? getCoordinatorOriginUri();
- method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? getSeller();
+ method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getSeller();
property public final android.net.Uri? coordinatorOriginUri;
- property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller;
+ property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller;
}
@SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class PersistAdSelectionResultRequest {
diff --git a/privacysandbox/ads/ads-adservices/api/current.txt b/privacysandbox/ads/ads-adservices/api/current.txt
index b25a1b0..dca5235 100644
--- a/privacysandbox/ads/ads-adservices/api/current.txt
+++ b/privacysandbox/ads/ads-adservices/api/current.txt
@@ -92,11 +92,11 @@
}
@SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class GetAdSelectionDataRequest {
- ctor public GetAdSelectionDataRequest(optional androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller, optional android.net.Uri? coordinatorOriginUri);
+ ctor public GetAdSelectionDataRequest(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller, optional android.net.Uri? coordinatorOriginUri);
method public android.net.Uri? getCoordinatorOriginUri();
- method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? getSeller();
+ method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getSeller();
property public final android.net.Uri? coordinatorOriginUri;
- property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller;
+ property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller;
}
@SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class PersistAdSelectionResultRequest {
diff --git a/privacysandbox/ads/ads-adservices/api/restricted_1.1.0-beta08.txt b/privacysandbox/ads/ads-adservices/api/restricted_1.1.0-beta08.txt
index b25a1b0..dca5235 100644
--- a/privacysandbox/ads/ads-adservices/api/restricted_1.1.0-beta08.txt
+++ b/privacysandbox/ads/ads-adservices/api/restricted_1.1.0-beta08.txt
@@ -92,11 +92,11 @@
}
@SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class GetAdSelectionDataRequest {
- ctor public GetAdSelectionDataRequest(optional androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller, optional android.net.Uri? coordinatorOriginUri);
+ ctor public GetAdSelectionDataRequest(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller, optional android.net.Uri? coordinatorOriginUri);
method public android.net.Uri? getCoordinatorOriginUri();
- method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? getSeller();
+ method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getSeller();
property public final android.net.Uri? coordinatorOriginUri;
- property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller;
+ property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller;
}
@SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class PersistAdSelectionResultRequest {
diff --git a/privacysandbox/ads/ads-adservices/api/restricted_current.txt b/privacysandbox/ads/ads-adservices/api/restricted_current.txt
index b25a1b0..dca5235 100644
--- a/privacysandbox/ads/ads-adservices/api/restricted_current.txt
+++ b/privacysandbox/ads/ads-adservices/api/restricted_current.txt
@@ -92,11 +92,11 @@
}
@SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class GetAdSelectionDataRequest {
- ctor public GetAdSelectionDataRequest(optional androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller, optional android.net.Uri? coordinatorOriginUri);
+ ctor public GetAdSelectionDataRequest(androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller, optional android.net.Uri? coordinatorOriginUri);
method public android.net.Uri? getCoordinatorOriginUri();
- method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? getSeller();
+ method public androidx.privacysandbox.ads.adservices.common.AdTechIdentifier getSeller();
property public final android.net.Uri? coordinatorOriginUri;
- property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier? seller;
+ property public final androidx.privacysandbox.ads.adservices.common.AdTechIdentifier seller;
}
@SuppressCompatibility @androidx.privacysandbox.ads.adservices.common.ExperimentalFeatures.Ext10OptIn public final class PersistAdSelectionResultRequest {
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/GetAdSelectionDataRequest.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/GetAdSelectionDataRequest.kt
index 650e8c0..e84646b 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/GetAdSelectionDataRequest.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/adselection/GetAdSelectionDataRequest.kt
@@ -43,7 +43,7 @@
@ExperimentalFeatures.Ext10OptIn
class GetAdSelectionDataRequest
public constructor(
- val seller: AdTechIdentifier? = null,
+ val seller: AdTechIdentifier,
@property:ExperimentalFeatures.Ext12OptIn val coordinatorOriginUri: Uri? = null
) {
/** Checks whether two [GetAdSelectionDataRequest] objects contain the same information. */
@@ -88,7 +88,7 @@
request: GetAdSelectionDataRequest
): android.adservices.adselection.GetAdSelectionDataRequest {
return android.adservices.adselection.GetAdSelectionDataRequest.Builder()
- .setSeller(request.seller?.convertToAdServices())
+ .setSeller(request.seller.convertToAdServices())
.setCoordinatorOriginUri(request.coordinatorOriginUri)
.build()
}
@@ -103,7 +103,7 @@
request: GetAdSelectionDataRequest
): android.adservices.adselection.GetAdSelectionDataRequest {
return android.adservices.adselection.GetAdSelectionDataRequest.Builder()
- .setSeller(request.seller?.convertToAdServices())
+ .setSeller(request.seller.convertToAdServices())
.build()
}
}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/CheckboxButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/CheckboxButton.kt
index 4b87bb5..38adf08 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/CheckboxButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/CheckboxButton.kt
@@ -20,18 +20,32 @@
import androidx.compose.animation.core.TweenSpec
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.selection.toggleable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.State
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
@@ -41,11 +55,14 @@
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.takeOrElse
+import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material3.tokens.CheckboxButtonTokens
import androidx.wear.compose.material3.tokens.MotionTokens
+import androidx.wear.compose.material3.tokens.ShapeTokens
import androidx.wear.compose.material3.tokens.SplitCheckboxButtonTokens
import androidx.wear.compose.materialcore.animateSelectionColor
@@ -228,18 +245,77 @@
contentPadding: PaddingValues = CheckboxButtonDefaults.ContentPadding,
secondaryLabel: @Composable (RowScope.() -> Unit)? = null,
label: @Composable RowScope.() -> Unit
-) =
- androidx.wear.compose.materialcore.SplitToggleButton(
- checked = checked,
- onCheckedChange = onCheckedChange,
- label =
- provideScopeContent(
- contentColor = colors.contentColor(enabled = enabled, checked = checked),
- textStyle = SplitCheckboxButtonTokens.LabelFont.value,
- content = label
- ),
- onClick = onContainerClick,
- toggleControl = {
+) {
+ val containerColor = colors.containerColor(enabled, checked).value
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier =
+ modifier
+ .defaultMinSize(minHeight = MIN_HEIGHT)
+ .height(IntrinsicSize.Min)
+ .width(IntrinsicSize.Max)
+ .clip(shape = shape)
+ ) {
+ Row(
+ modifier =
+ Modifier.clickable(
+ enabled = enabled,
+ onClick = onContainerClick,
+ indication = ripple(),
+ interactionSource = containerInteractionSource,
+ onClickLabel = containerClickLabel,
+ )
+ .semantics { role = Role.Button }
+ .fillMaxHeight()
+ .clip(SPLIT_SECTIONS_SHAPE)
+ .background(containerColor)
+ .padding(contentPadding)
+ .weight(1.0f),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Labels(
+ label =
+ provideScopeContent(
+ contentColor = colors.contentColor(enabled = enabled, checked = checked),
+ textStyle = SplitCheckboxButtonTokens.LabelFont.value,
+ content = label
+ ),
+ secondaryLabel =
+ provideNullableScopeContent(
+ contentColor =
+ colors.secondaryContentColor(enabled = enabled, checked = checked),
+ textStyle = SplitCheckboxButtonTokens.SecondaryLabelFont.value,
+ content = secondaryLabel
+ ),
+ )
+ }
+
+ Spacer(modifier = Modifier.size(2.dp))
+
+ val splitBackground = colors.splitContainerColor(enabled, checked).value
+ Box(
+ modifier =
+ Modifier.toggleable(
+ enabled = enabled,
+ value = checked,
+ onValueChange = onCheckedChange,
+ indication = ripple(),
+ interactionSource = toggleInteractionSource
+ )
+ .fillMaxHeight()
+ .clip(SPLIT_SECTIONS_SHAPE)
+ .background(containerColor)
+ .drawWithCache {
+ onDrawWithContent {
+ drawRect(color = splitBackground)
+ drawContent()
+ }
+ }
+ .align(Alignment.CenterVertically)
+ .width(SPLIT_WIDTH)
+ .wrapContentHeight(align = Alignment.CenterVertically)
+ .padding(contentPadding)
+ ) {
Checkbox(
checked = checked,
enabled = enabled,
@@ -256,30 +332,9 @@
colors.checkmarkColor(enabled = enabled, checked = checked)
}
)
- },
- selectionControl = null,
- modifier = modifier.defaultMinSize(minHeight = MIN_HEIGHT).height(IntrinsicSize.Min),
- secondaryLabel =
- provideNullableScopeContent(
- contentColor = colors.secondaryContentColor(enabled = enabled, checked = checked),
- textStyle = SplitCheckboxButtonTokens.SecondaryLabelFont.value,
- content = secondaryLabel
- ),
- backgroundColor = { isEnabled, isChecked ->
- colors.containerColor(enabled = isEnabled, checked = isChecked)
- },
- splitBackgroundColor = { isEnabled, isChecked ->
- colors.splitContainerColor(enabled = isEnabled, checked = isChecked)
- },
- enabled = enabled,
- checkedInteractionSource = toggleInteractionSource,
- clickInteractionSource = containerInteractionSource,
- onClickLabel = containerClickLabel,
- contentPadding = contentPadding,
- shape = shape,
- labelSpacerSize = CheckboxButtonDefaults.LabelSpacerSize,
- ripple = ripple()
- )
+ }
+ }
+}
/** Contains the default values used by [CheckboxButton]s and [SplitCheckboxButton]s */
object CheckboxButtonDefaults {
@@ -1375,16 +1430,33 @@
)
}
+@Composable
+private fun RowScope.Labels(
+ label: @Composable RowScope.() -> Unit,
+ secondaryLabel: @Composable (RowScope.() -> Unit)?
+) {
+ Column(modifier = Modifier.weight(1.0f)) {
+ Row(content = label)
+ if (secondaryLabel != null) {
+ Spacer(modifier = Modifier.size(CheckboxButtonDefaults.LabelSpacerSize))
+ Row(content = secondaryLabel)
+ }
+ }
+}
+
private val TOGGLE_CONTROL_SPACING = 6.dp
private val ICON_SPACING = 6.dp
private val MIN_HEIGHT = 52.dp
-private val CHECKBOX_WIDTH = 32.dp
+private val CHECKBOX_WIDTH = 24.dp
private val CHECKBOX_HEIGHT = 24.dp
private val BOX_STROKE = 2.dp
private val BOX_RADIUS = 2.dp
private val BOX_SIZE = 18.dp
+private val SPLIT_WIDTH = 52.dp
+private val SPLIT_SECTIONS_SHAPE = ShapeTokens.CornerExtraSmall
+
private val COLOR_ANIMATION_SPEC: AnimationSpec<Color> =
tween(MotionTokens.DurationMedium1, 0, MotionTokens.EasingStandardDecelerate)
private val PROGRESS_ANIMATION_SPEC: TweenSpec<Float> =
diff --git a/wear/protolayout/protolayout/api/current.txt b/wear/protolayout/protolayout/api/current.txt
index afcfb3e..6298259 100644
--- a/wear/protolayout/protolayout/api/current.txt
+++ b/wear/protolayout/protolayout/api/current.txt
@@ -591,6 +591,9 @@
method public androidx.wear.protolayout.TypeBuilders.BoolProp? getUnderline();
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp? getVariant();
method public androidx.wear.protolayout.LayoutElementBuilders.FontWeightProp? getWeight();
+ field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static final String DEFAULT_SYSTEM_FONT = "default";
+ field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static final String ROBOTO_FLEX_FONT = "roboto-flex";
+ field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static final String ROBOTO_FONT = "roboto";
}
public static final class LayoutElementBuilders.FontStyle.Builder {
@@ -610,9 +613,6 @@
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setVariant(int);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setWeight(androidx.wear.protolayout.LayoutElementBuilders.FontWeightProp);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setWeight(int);
- field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static final String DEFAULT_SYSTEM_FONT = "default";
- field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static final String ROBOTO_FLEX_FONT = "roboto-flex";
- field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static final String ROBOTO_FONT = "roboto";
}
@Deprecated public static final class LayoutElementBuilders.FontStyles {
diff --git a/wear/protolayout/protolayout/api/restricted_current.txt b/wear/protolayout/protolayout/api/restricted_current.txt
index afcfb3e..6298259 100644
--- a/wear/protolayout/protolayout/api/restricted_current.txt
+++ b/wear/protolayout/protolayout/api/restricted_current.txt
@@ -591,6 +591,9 @@
method public androidx.wear.protolayout.TypeBuilders.BoolProp? getUnderline();
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental public androidx.wear.protolayout.LayoutElementBuilders.FontVariantProp? getVariant();
method public androidx.wear.protolayout.LayoutElementBuilders.FontWeightProp? getWeight();
+ field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static final String DEFAULT_SYSTEM_FONT = "default";
+ field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static final String ROBOTO_FLEX_FONT = "roboto-flex";
+ field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static final String ROBOTO_FONT = "roboto";
}
public static final class LayoutElementBuilders.FontStyle.Builder {
@@ -610,9 +613,6 @@
method @SuppressCompatibility @androidx.wear.protolayout.expression.ProtoLayoutExperimental @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setVariant(int);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setWeight(androidx.wear.protolayout.LayoutElementBuilders.FontWeightProp);
method @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=0) public androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder setWeight(int);
- field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static final String DEFAULT_SYSTEM_FONT = "default";
- field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static final String ROBOTO_FLEX_FONT = "roboto-flex";
- field @androidx.wear.protolayout.expression.RequiresSchemaVersion(major=1, minor=400) public static final String ROBOTO_FONT = "roboto";
}
@Deprecated public static final class LayoutElementBuilders.FontStyles {
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
index 044e01d..c63519e 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/LayoutElementBuilders.java
@@ -715,6 +715,32 @@
return Collections.unmodifiableList(list);
}
+ /** The recommended font family names to be used within {@link FontStyle}. */
+ @RequiresSchemaVersion(major = 1, minor = 400)
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(
+ value = {DEFAULT_SYSTEM_FONT, ROBOTO_FONT, ROBOTO_FLEX_FONT},
+ open = true)
+ public @interface FontFamilyName {}
+
+ /**
+ * Font family name that uses default system font. Supported in any renderer version.
+ */
+ @RequiresSchemaVersion(major = 1, minor = 400)
+ public static final String DEFAULT_SYSTEM_FONT = "default";
+
+ /** Font family name that uses Roboto font. Supported in renderers supporting 1.4. */
+ @RequiresSchemaVersion(major = 1, minor = 400)
+ public static final String ROBOTO_FONT = "roboto";
+
+ /**
+ * Font family name that uses Roboto Flex variable font. Supported in renderers
+ * supporting 1.4.
+ */
+ @RequiresSchemaVersion(major = 1, minor = 400)
+ public static final String ROBOTO_FLEX_FONT = "roboto-flex";
+
/** Get the fingerprint for this object, or null if unknown. */
@RestrictTo(Scope.LIBRARY_GROUP)
@Nullable
@@ -1024,32 +1050,6 @@
return this;
}
- /** The recommended font family names to be used within {@link FontStyle}. */
- @RequiresSchemaVersion(major = 1, minor = 400)
- @RestrictTo(RestrictTo.Scope.LIBRARY)
- @Retention(RetentionPolicy.SOURCE)
- @StringDef(
- value = {DEFAULT_SYSTEM_FONT, ROBOTO_FONT, ROBOTO_FLEX_FONT},
- open = true)
- public @interface FontFamilyName {}
-
- /**
- * Font family name that uses default system font. Supported in any renderer version.
- */
- @RequiresSchemaVersion(major = 1, minor = 400)
- public static final String DEFAULT_SYSTEM_FONT = "default";
-
- /** Font family name that uses Roboto font. Supported in renderers supporting 1.4. */
- @RequiresSchemaVersion(major = 1, minor = 400)
- public static final String ROBOTO_FONT = "roboto";
-
- /**
- * Font family name that uses Roboto Flex variable font. Supported in renderers
- * supporting 1.4.
- */
- @RequiresSchemaVersion(major = 1, minor = 400)
- public static final String ROBOTO_FLEX_FONT = "roboto-flex";
-
/**
* Sets the preferred font families for this {@link FontStyle}.
*
diff --git a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java
index 8620e76..1653411 100644
--- a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java
+++ b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/LayoutElementBuildersTest.java
@@ -21,7 +21,7 @@
import static androidx.wear.protolayout.DimensionBuilders.expand;
import static androidx.wear.protolayout.DimensionBuilders.sp;
import static androidx.wear.protolayout.DimensionBuilders.weight;
-import static androidx.wear.protolayout.LayoutElementBuilders.FontStyle.Builder.ROBOTO_FLEX_FONT;
+import static androidx.wear.protolayout.LayoutElementBuilders.FontStyle.ROBOTO_FLEX_FONT;
import static androidx.wear.protolayout.LayoutElementBuilders.TABULAR_OPTION_TAG;
import static androidx.wear.protolayout.LayoutElementBuilders.WEIGHT_AXIS_TAG;
import static androidx.wear.protolayout.LayoutElementBuilders.WIDTH_AXIS_TAG;