Zakir Sheikh
12/03/2024, 5:18 PM/**
* Represents an high performance [PaddingValues]. In Compose, `padding` values are represented by a `data class`,
* but it is used frequently in animations and composables. Since it represents padding in dp instead of
* pixels, making this a value class that stores 4 properties as half-precision shorts (`float16`)
* within a Long is more efficient.
*
* The maximum value of a `float16` is `65505`, which is more than enough for everyday use cases.
* Since padding can never be negative in Compose, we might increase this value further.
*
* This class also allows users to perform addition operations on padding values internally,
* saving boilerplate code.
*/
@Immutable
@JvmInline
value class PaddingValues2(private val packed: Long) : PaddingValues {
// Constructor to pack four Dp values into a single Long
constructor(start: Dp = 0.dp, top: Dp = 0.dp, end: Dp = 0.dp, bottom: Dp = 0.dp) :
this(
(floatToHalf(start.value).toLong() shl 48) or
(floatToHalf(top.value).toLong() shl 32) or
(floatToHalf(end.value).toLong() shl 16) or
floatToHalf(bottom.value).toLong()
)
// Unpacking each 16-bit Dp value from the Long
internal val start: Dp get() = Dp(halfToFloat((packed shr 48).toShort()))
internal val top: Dp get() = Dp(halfToFloat((packed shr 32 and 0xFFFF).toShort()))
internal val end: Dp get() = Dp(halfToFloat((packed shr 16 and 0xFFFF).toShort()))
internal val bottom: Dp get() = Dp(halfToFloat((packed and 0xFFFF).toShort()))
override fun calculateLeftPadding(layoutDirection: LayoutDirection) =
if (layoutDirection == LayoutDirection.Ltr) start else end
override fun calculateTopPadding() = top
override fun calculateRightPadding(layoutDirection: LayoutDirection) =
if (layoutDirection == LayoutDirection.Ltr) end else start
override fun calculateBottomPadding() = bottom
// Override toString for better readability
override fun toString() = "Padding(start=$start, top=$top, end=$end, bottom=$bottom)"
/**
* Adds another `PaddingValues2` instance to this instance.
*
* @param other The other `PaddingValues2` instance.
* @return A new `PaddingValues2` instance representing the sum of the two instances.
*/
operator fun plus(other: PaddingValues2): PaddingValues2 {
return PaddingValues2(
start + other.start,
top + other.top,
end + other.end,
bottom + other.bottom
)
}
}
I've come up with this solution, but I need to confirm if it's correct. I am new to Slack, so please be patient with me.
Thanks.
Stylianos Gakis
12/03/2024, 5:25 PMZakir Sheikh
12/03/2024, 5:40 PMDaniel Pitts
12/03/2024, 9:41 PMshikasd
12/03/2024, 11:23 PMPaddingValues
, and in that case using 4 ints is generally cheaperZakir Sheikh
12/03/2024, 11:45 PMZakir Sheikh
12/03/2024, 11:55 PMDaniel Pitts
12/04/2024, 2:07 AMZakir Sheikh
12/04/2024, 2:51 AMPaddingValues
as a value class might be beneficial in certain cases.
While it’s true that we typically use far fewer padding values in Compose, the fact that Compose automatically invalidates layouts makes it critical to have performant code wherever possible. Even seemingly small optimizations can add up in complex UI scenarios.
For example, consider the collapsible top bar in Compose Material3. This component relies on animating padding values during its collapse/expand transitions. In such cases, having a more performant representation for PaddingValues
could provide tangible benefits, especially during animations where every frame matters. Animating PaddingValues
directly is another scenario where performance can be a concern.
Additionally, I believe the rationale for making PaddingValues
a value class is similar to why IntOffset
and other similar structures in Compose are value classes. These are lightweight objects designed to avoid unnecessary heap allocations, which can improve performance and memory efficiency.
That said, I think the primary reason PaddingValues
hasn’t been implemented as a value class is because it’s an interface. Since interfaces in Kotlin lead to autoboxing when used with value classes, the benefits of using a value class would be nullified in many cases.
The reason PaddingValues
is an interface in the first place is to accommodate multiple implementations, such as AbsolutePaddingValues
and RelativePaddingValues
, which depend on the layout direction. This design ensures flexibility, but it does come at the cost of potential boxing and the inability to use value class optimizations.Daniel Pitts
12/04/2024, 2:55 AMZakir Sheikh
12/04/2024, 2:56 AMshikasd
12/04/2024, 7:03 PMshikasd
12/04/2024, 7:04 PM