In JetPack Compose, you’ll often use Modifier to control the behavior and appearance of components such as size, background, etc. You can also add interactions such as clicking, swiping, etc. The Modifier function is very powerful. The Modifier core code is less than 100 lines, the Modifier Kotlin advanced function with very 6, so that the initial look at the code a little meng ๐ต. So I plan to write this article to learn how to perform the Modifier code. Modifier core source code as follows Modifier.kt
interface Modifier {
fun <R> foldIn(initial: R, operation: (R.Element) - >R): R
fun <R> foldOut(initial: R, operation: (Element.R) - >R): R
fun any(predicate: (Element) - >Boolean): Boolean
fun all(predicate: (Element) - >Boolean): Boolean
infix fun then(other: Modifier): Modifier =
if (other === Modifier) this else CombinedModifier(this, other)
interface Element : Modifier {
override fun <R> foldIn(initial: R, operation: (R.Element) - >R): R =
operation(initial, this)
override fun <R> foldOut(initial: R, operation: (Element.R) - >R): R =
operation(this, initial)
override fun any(predicate: (Element) - >Boolean): Boolean = predicate(this)
override fun all(predicate: (Element) - >Boolean): Boolean = predicate(this)}companion object : Modifier {
override fun <R> foldIn(initial: R, operation: (R.Element) - >R): R = initial
override fun <R> foldOut(initial: R, operation: (Element.R) - >R): R = initial
override fun any(predicate: (Element) - >Boolean): Boolean = false
override fun all(predicate: (Element) - >Boolean): Boolean = true
override infix fun then(other: Modifier): Modifier = other
override fun toString(a) = "Modifier"}}class CombinedModifier(
private val outer: Modifier,
private val inner: Modifier
) : Modifier {
override fun <R> foldIn(initial: R, operation: (R.Modifier.Element) - >R): R =
inner.foldIn(outer.foldIn(initial, operation), operation)
override fun <R> foldOut(initial: R, operation: (Modifier.Element.R) - >R): R =
outer.foldOut(inner.foldOut(initial, operation), operation)
override fun any(predicate: (Modifier.Element) - >Boolean): Boolean =
outer.any(predicate) || inner.any(predicate)
override fun all(predicate: (Modifier.Element) - >Boolean): Boolean =
outer.all(predicate) && inner.all(predicate)
override fun equals(other: Any?).: Boolean =
other is CombinedModifier && outer == other.outer && inner == other.inner
override fun hashCode(a): Int = outer.hashCode() + 31 * inner.hashCode()
override fun toString(a) = "[" + foldIn("") { acc, element ->
if (acc.isEmpty()) element.toString() else "$acc.$element"
} + "]"
}
Copy the code
The code structure in the Modifier. Kt file is as follows
Element Provides an abstract interface for the Modifier Element. Modifier ** provides a static singleton class that implements the Modifier interface. CombinedModifier will be two Modifier combination, is an important key to generate the Modifier chain.
I’m going to focus on the then foldIn foldOut three functions
fun foldIn(initial: R, operation: (R.Element) - >R): R
fun <R> foldOut(initial: R, operation: (Element.R) - >R): R
infix fun then(other: Modifier): Modifier =
if (other === Modifier) this else CombinedModifier(this, other)
Copy the code
To facilitate the explanation, provide a simplified version of the Modifier implementation classes and some functions.
class SizeModifier : Modifier.Element {
override fun toString(a) = "SizeModifier"
}
class PaddingModifier : Modifier.Element {
override fun toString(a) = "PaddingModifier"
}
class OffsetModifier : Modifier.Element {
override fun toString(a) = "OffsetModifier"
}
fun Modifier.size(a) = this.then(SizeModifier())
fun Modifier.padding(a) = this.then(PaddingModifier())
fun Modifier.offset(a) = this.then(OffsetModifier())
Copy the code
Then the function
The then method is an important function to generate the Modifier chain.
fun main(a) {
valModifier = modifier. The size (). The padding (). The offset () println (modifier)} output -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- [SizeModifier, PaddingModifier, OffsetModifier]Copy the code
Below the Modifier.size().padding().offset() single step to break the Modifier generation chain process
The first step
val modifier1 = Modifier
Copy the code
โ ๏ธ This Modifier is a static singleton Modifier, not an interface.
The second step
val modifier2 = modifier1.size()
Copy the code
According to the size function defined above, =>
val modifier2 = Modifier.then(SizeModifier())
Then according to the static singleton Modifier then function
companion object : Modifier {
override infix fun then(other: Modifier): Modifier = other
}
Copy the code
Modifier2 =SizeModifier() val Modifier2 =SizeModifier(
The third step
val modifier3 = modifier2.padding()
Copy the code
PaddingModifier() => Modifier3 =modifier2.then(PaddingModifier())
Modifier2 is the SizeModifier object, which implements Modifier.Element. That is to perform the Modifier interface default then function.
infix fun then(other: Modifier): Modifier =
if (other === Modifier) this else CombinedModifier(this, other)
Copy the code
SizeModifier = PaddingModifier = SizeModifier = PaddingModifier = SizeModifier = PaddingModifier
=> modifier3 = CombinedModifier(modifier2, PaddingModifier())
=> modifier3 = CombinedModifier(SizeModifier(), PaddingModifier())
The fourth step
val modifier4 = modifier3.offset()
Copy the code
This step is similar to modifier3, so the
//modifier4 =>
CombinedModifier(outer = modifier3, inner = OffsetModifier())
//modifier4 =>
CombinedModifier(outer = CombinedModifier(modifier2, PaddingModifier()), inner = OffsetModifier())
//modifier4 =>
CombinedModifier(outer = CombinedModifier(SizeModifier(), PaddingModifier()), inner = OffsetModifier())
Copy the code
Modifier4 finally gets something like a Russian doll CombinedModifier(outer = CombinedModifier(SizeModifier(), PaddingModifier()), inner = OffsetModifier())
Here are two graphs to summarize
FoldOut function
With the chain analysis above, let’s talk about how to traverse the elements of the chain. The foldOut function is given an initial value, then evaluates and returns the value on the Modifier chain according to the operation logic we passed in.
fun main(a) {
val modifier = Modifier.size().padding().offset()
val result = modifier.foldOut("start---") { mod, str ->
println(mod)
"$str$mod "
}
print("result=$result")} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- output -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- OffsetModifier PaddingModifier SizeModifier result = start - OffsetModifier PaddingModifier SizeModifierCopy the code
Magically, foldOut can pass through all the elements on the Modifier chain. How does it do this? Let’s change the above code for easy analysis
fun main(a) {
// ๐ code 0๏ธ one
val modifier = Modifier.size().padding().offset()
val initial="start---"
val operation= { mod:Modifier, str:String ->
println(mod)
"$str$mod "
}
val result= modifier.foldOut(initial,operation)
print("result=$result")
Copy the code
According to the then function of the Modifier above, we know how it forms the chain. From the above analysis, we know that code 0๏ธ retail modifier is the type of CombinedModifier, and its form is as follows Figure 2-1 ๐
Now that you know is CombinedModifier that look at its foldOut is how to achieve it
override fun <R> foldOut(initial: R, operation: (Modifier.Element.R) - >R): R =
outer.foldOut(inner.foldOut(initial, operation), operation)
Copy the code
This looks like a very simple two lines of code, but it’s a lot of work, so let’s transform this function.
override fun <R> foldOut(initial: R, operation: (Modifier.Element.R) - >R): R {
// execute inner foldOut first
val nextInitial:R=inner.foldOut(initial, operation)
Inner foldOut = initial; outer foldOut = initial
return outer.foldOut(nextInitial, operation)
}
Copy the code
The first step is to execute the Inner foldOut function. As shown in Figure 2-1, the inner modifier is the object of the OffsetModifier type. OffsetModifier finally inherits Modifier.Element, whose foldOut function is implemented as follows
override fun <R> foldOut(initial: R, operation: (Element.R) - >R): R =
operation(this, initial)
Copy the code
This function calls the operation function we passed in and takes the current object and the intial passed in as arguments. This is the first time the code we wrote in the operation body executesAccording to the above analysis, the first step is knownย val nextInitial:R=inner.foldOut(initial, operation)
The return value is the return value of operation, which is"``$``str``$``mod ``"
The value here is zerostart---OffsetModifier
Once the parameters are ready, we are ready to execute step 2, which executes outer’s foldOut function, as shown in Figure 2-1Outer is still of CombinedModifier type, its foldOut function is still the first step to execute its inner foldOut function, and its inner is the SizeModifer object. It is still of type Modifier.Element, like above, when the code in the operation body is executed a second timeNow it’s time to execute its Outer foldOut, whose outer is the SizeModifer object, which is the Modifier.Element, so the code in the operation body is executed a third time.
The foldOut execution flow is illustrated with a piece of pseudocode
//0๏ธ retail CombinedModifier foldOut function we decompose into two steps
override fun <R> foldOut(initial: R, operation: (Modifier.Element.R) - >R): R {
// execute inner foldOut first
val initial1:R=inner.foldOut(initial, operation)
Inner fodOut = foldOut; outer foldOut = foldOut
return outer.foldOut(initial1, operation)
}
//1๏ธ inner iF Modifier.Element type,
// Inner foldOut executes operation
override fun <R> foldOut(initial: R, operation: (Modifier.Element.R) - >R): R {
// Operation is executed
val initial1:R=operation(inner,initial)
return outer.foldOut(initial1, operation)
}
//2๏ธ discontinuation of outer's foldOut, if it is still CombinedModifier, repeat the above steps
override fun <R> foldOut(initial: R, operation: (Modifier.Element.R) - >R): R {
val initial1:R=operation(inner,initial)
// Assume that outer can access its inner and outer directly
val initial2=outer.inner.foldOut(initial1, operation)
return outer.outer.foldOut(initial2, operation)
}
//3๏ธ if outer. Inner or Modifier.Element type, the same
override fun <R> foldOut(initial: R, operation: (Modifier.Element.R) - >R): R {
val initial1:R=operation(inner,initial)
val initial2:R=operation(outer.inner,initial1)
return outer.outer.foldOut(initial2, operation)
}
//4๏ธ one until finally OUTER is not CombinedModifier type end
override fun <R> foldOut(initial: R, operation: (Modifier.Element.R) - >R): R {
val initial1:R=operation(inner,initial)
val initial2:R=operation(outer.inner,initial1/* The return value of the previous step */)...valinitialN:R=operation(outer.outer....... outer.inner,initialN_1/* The return value of the previous step */)
returnoperation(outer.outer......... outer,initialN) }Copy the code
Conclusion: The CombinedModifier foldOut function executes its inner foldOut function and then outer’s foldOut function, taking the foldOut return value of inner as an argument to outer’s foldOut function
FoldIn function
The foldIn function is similar to the foldOut function in that it requires an initial value and an operation function and returns the result according to the Modifier chain. But there are some differences. Here is a simple example of foldIn
val modifier = Modifier.size().padding().offset()
val result = modifier.foldIn("start----") { str, mod ->
println(mod)
"$str$mod "
}
print("result=$result") -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- output -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- SizeModifier PaddingModifier OffsetModifier result = start - SizeModifier PaddingModifier OffsetModifierCopy the code
Code 3-1๐ You can see from the output that foldIn is executed in the reverse order of foldOut. FoldIn function of CombinedModifier
override fun <R> foldIn(initial: R, operation: (R.Modifier.Element) - >R): R =
inner.foldIn(outer.foldIn(initial, operation), operation)
Copy the code
Again, this function can be decomposed into two steps
override fun <R> foldIn(initial: R, operation: (R.Modifier.Element) - >R): R{
//1. Execute outer's foldIn function
val result1=outer.foldIn(initial, operation)
Outer's foldIn function returns the foldIn of inner
val result0=inner.foldIn(result1, operation)
return result0;
}
Copy the code
For code 3-1 we can expand it based on the function above
//val modifier = Modifier.size().padding().offset()
//๐ is equivalent to => ๐
val modifier1 = Modifier.size()
val modifier2 = CombinedModifier(inner = PaddingModifier(), outer = modifier1)
val modifier3 = CombinedModifier(inner = OffsetModifier(), outer = modifier2)
//val result = modifier.foldIn("start----"){.... }
//๐ is equivalent to => ๐
val initial = "start---"
val operation = { str: String, mod: Modifier ->
println(mod)
"$str$mod "
}
/ / 0 ๏ธ โฃ
modifier3.foldIn(initial, operation)
Copy the code
According to the foldIn function of CombinedModifier, code 0๏ธ is equivalent to
/ / 1 ๏ธ โฃ
val result1=modifier2.foldIn(initial,operation)
val result0=OffsetModifier().foldIn(result1,operation)
return result0
Copy the code
Because modifier2 is still code CombinedModifier type, code 1๏ธ is equivalent to
val result2=modifier1.foldIn(initial,operation)
val result1=PaddingModifier().foldIn(result2,operation)
val result0=OffsetModifier().foldIn(result1,operation)
return result0
Copy the code
Code 3-2๐ modifier1 is the SizeModifier object, which is of type Modifier.Element.Element’s foldIn function is as follows
override fun <R> foldIn(initial: R, operation: (R.Element) - >R): R =
operation(initial, this)
Copy the code
This function simply executes the operation function we passed in. PaddingModifier and OffsetModifier are also of the Modifier type.Element type, so codes 3-2 ** are equivalent to
val result2 = operation(initial, SizeModifier())
val result1 = operation(result2, PaddingModifier())
val result0 = operation(result1, OffsetModifier())
return result0
Copy the code
As you can see, foldIn eventually executes the operation function we passed in sequence along the Modifier chain. The result of each operation is used as the initial parameter of the next operation function.
conclusion
Modifier Combines the two modifiers into a CombinedModifier using the THEN function, inner pointing to the Modifier corresponding to the current node, outer pointing to the Modifier corresponding to the next node, If outer is also a CombinedModifier, then the Modifier can be extended.
FoldOut & foldIn Similarity: Given an initial value, return a calculated value. Each element on the execution Modifier chain is traversed. Difference: The traversal sequence of the two functions is different. FoldOut is executed from back to front in the sequence of adding the Modifier chain, while foldIn is executed from front to back in the sequence of adding the Modifier chain.