In the last article, we touched on intrinsic property measurements. In this article, we’ll explore ParentData
ParentData
Past examples
Let’s recall the example mentioned in the first article in order to achieve the following effect
We used a string of modifiers like:
Box(modifier = Modifier
.fillMaxSize()
.wrapContentSize(align = Alignment.Center)
.size(50.dp)
.background(Color.Blue))
Copy the code
That is, a child widget is centered as a result of its own wrapContentSize(align = align.center) adjustment. So, if we now know that the child widget (the small blue square) is wrapped in another Box, can we ask the parent layout to help determine the center?
The answer is yes! Box provides the align method in its content scope, which lets child widgets tell their parent layout: I need to center
@Composable
inline fun Box(
modifier: Modifier = Modifier,
// Content provides BoxScope
content: @Composable BoxScope. () - >Unit
) {
val measurePolicy = rememberBoxMeasurePolicy(contentAlignment, propagateMinConstraints)
Layout(
content = { BoxScopeInstance.content() },
measurePolicy = measurePolicy,
modifier = modifier
)
}
Copy the code
The source code for BoxScope is as follows:
@Immutable
interface BoxScope {
@Stable
fun Modifier.align(alignment: Alignment): Modifier
@Stable
fun Modifier.matchParentSize(a): Modifier
}
Copy the code
As an interface, in this scope, the child widget can call align to tell the parent widget its own align mode
So the above effect can be achieved like this:
@Composable
fun ModifierSample2(a) {
/ / the parent element
Box(modifier = Modifier
.width(200.dp)
.height(300.dp)
.background(Color.Yellow)){
/ / child elements
Box(modifier = Modifier
.align(Alignment.Center)
.size(50.dp)
.background(Color.Blue))
}
}
Copy the code
The effect is the same
Unlike the layout modifier we saw earlier, align is the parent data modifier. In essence, this communication from child widgets to parent layouts is implemented by parentData. Align above will eventually involve the following code:
override val parentData: Any?
get() = with(modifier) {
/** * ParentData provided through the parentData node will override the data provided * through a modifier */
layoutNode.measureScope.modifyParentData(wrapped.parentData)
}
Copy the code
The source code
ParentDataModifier source code:
/** * a Modifier, Provide data for the parent Layout (Layout). * in (Layout) in the process of measurement and positioning through [IntrinsicMeasurable. ParentData] read. * the parent data Usually used to tell the parent layout: How should child widgets be measured and positioned */
interface ParentDataModifier : Modifier.Element {
/** * Provides a parentData, given the [parentData] already provided through the modifier's chain. */
fun Density.modifyParentData(parentData: Any?).: Any?
}
Copy the code
Try it: “Ground stall” of salted fish
So let’s try it out. Let’s imagine a layout like this: a stall of small salted fish
-
Inside the stall are tiny pieces arranged vertically, one by one
-
Each sub-widget is “paid”, for example one Box “sells” for 100, another Box “sells” for 200… And so on
-
Each sub-widget displays its own price, while the “floor stall” displays the total price
The above description is translated into code: each sub-widget through a custom Modifier to define its own price, and pass it to the parent layout, the parent layout calculates all the prices accumulated together, and display.
Let’s start coding. Let’s start by defining a class that inherits from ParentDataModifier
/ / the author FunnySaltyFish (http://funnysaltyfish.fun)
class CountNumParentData(var countNum: Int) : ParentDataModifier {
override fun Density.modifyParentData(parentData: Any?). = this@CountNumParentData
}
Copy the code
For simplicity, the modifyParentData method returns itself directly. In the original Column implementation, the method actually looks something like this:
override fun Density.modifyParentData(parentData: Any?). =
((parentData as? RowColumnParentData) ? : RowColumnParentData()).also { it.weight = weight it.fill = fill }Copy the code
)
Then we write a simple Modifier that returns an instance
fun Modifier.count(num: Int) = this.then(
// This part is the parent data modifier
CountNumParentData(num)
)
Copy the code
Next, we will reuse the previous VerticalLayout, just read ParentData inside, part of the code is as follows
var num = 0
Layout(
modifier = modifier,
content = content
) { measurables: List<Measurable>, constraints: Constraints ->
val placeables = measurables.map {
if (it.parentData is CountNumParentData) {
num += (it.parentData as CountNumParentData).countNum
}
it.measure(constraints)
}
// Omit the rest of the layout code
Log.d(TAG, "CountChildrenNumber: total price:$num")}Copy the code
Finally, run the example
@Composable
fun CountNumTest(a) {
CountChildrenNumber {
repeat(5) {
Box(
modifier = Modifier
.size(40.dp)
.background(randomColor())
.count(Random.nextInt(30.100)))}}}Copy the code
The corresponding total price output is as follows:
You may have noticed that the Box above also specifies its “price” in Text, but the code that calls it doesn’t use Text. How is the text drawn here?
The answer is the countModifier, which modifies itself in addition to acting as a parent data modifier. Its code is complete as follows:
fun Modifier.count(num: Int) = this.drawWithContent {
drawIntoCanvas { canvas ->
val paint = android.graphics
.Paint()
.apply {
textSize = 40F
}
canvas.nativeCanvas.drawText(num.toString(), 0F.40F, paint)
}
// Draw the contents of Box itself
drawContent()
}
.then(
// This part is the parent data modifier
CountNumParentData(num)
)
Copy the code
This is part of drawing time, but IF you’re interested, I might also talk about custom drawing later. Well, dig a hole and fill it later
The actual scenario of ParentData focuses on the parent layout’s control of the specific position and size of child widgets, such as the align of Box, the align of Column and Row, alignBy, and weight. Let’s implement a simplified version of weight
Try using: implement simple version weight
For simplicity, we implement weight with the following restrictions:
- All child widgets have weights that are proportionally assigned to heights
- The width and height of the parent layout is determined
So the logic of the code is: just read all the weights and assign heights proportionally.
First, like Box, we’ll also write a VerticalScope so that our custom weight can only be used in our custom layout
interface VerticalScope {
@Stable
fun Modifier.weight(weight: Float) : Modifier
}
Copy the code
Then customize our ParentDataModifier
class WeightParentData(val weight: Float=0f) : ParentDataModifier {
override fun Density.modifyParentData(parentData: Any?). = this@WeightParentData
}
Copy the code
Write an Object that implements our VerticalScope
object VerticalScopeInstance : VerticalScope {
@Stable
override fun Modifier.weight(weight: Float): Modifier = this.then(
WeightParentData(weight)
)
}
Copy the code
Next, the Composable concrete is implemented. Notice that we need to add VerticalScope to our content here.
@Composable
fun WeightedVerticalLayout(
modifier: Modifier = Modifier,
content: @Composable VerticalScope. () - >Unit
)
Copy the code
The specific implementation is similar to the previous VerticalLayout, the difference is that we need to get the value of each WeightParentData and save it to calculate the total weight. So you can scale the heights.
The key codes are as follows:
val measurePolicy = MeasurePolicy { measurables, constraints ->
val placeables = measurables.map {it.measure(constraints)}
// Get each weight value
val weights = measurables.map {
(it.parentData as WeightParentData).weight
}
val totalHeight = constraints.maxHeight
val totalWeight = weights.sum()
// Width: the widest item
val width = placeables.maxOf { it.width }
layout(width, totalHeight) {
var y = 0
placeables.forEachIndexed() { i, placeable ->
placeable.placeRelative(0, y)
// Scale the size
y += (totalHeight * weights[i] / totalWeight).toInt()
}
}
}
Layout(modifier = modifier, content = { VerticalScopeInstance.content() }, measurePolicy=measurePolicy)
Copy the code
Test it? We plan to have the three boxes displayed at a height of 1:2:7
WeightedVerticalLayout(Modifier.padding(16.dp).height(200.dp)) {
Box(modifier = Modifier.width(40.dp).weight(1f).background(randomColor()))
Box(modifier = Modifier.width(40.dp).weight(2f).background(randomColor()))
Box(modifier = Modifier.width(40.dp).weight(7f).background(randomColor()))
}
Copy the code
The final result is as follows. You can see that the three boxes correctly display the height in a ratio of 1:2:7
Success!
subsequent
That’s all we have to do with ParentData. Next, we… Let me figure out what I want to write
Reference for this article:
- JetPack Compose handwritten a Row layout | custom layout – the nuggets (juejin. Cn)
- Deep Dive into Jetpack Compose layouts
All the code for this article is: here, welcome star~