In JetPack Compose, Modifier is often used, which can control the behavior and appearance of components such as size, background, etc., and can also add some interactions, such as clicking, sliding, etc. The Modifier function is very powerful. The core code of Modifier is less than 100 lines. The high-level functions of Kotlin are used in Modifier so that it is a bit confusing at first glance. So I plan to write this article to learn how the code in Modifier is executed. The core source code of Modifier is 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 () = "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 () : Int = outer.hashCode() + 31 * inner .hashCode()
override fun toString () = "[" + foldIn( "" ) {acc, element ->
if (acc.isEmpty()) element.toString() else " $acc , $element "
} + "]"
}
Copy code
The code structure in the Modifier.kt file is as follows
Element is an abstract interface for Modifier elements. **Modifier ** provides a static singleton class that implements the Modifier interface. CombinedModifier combines two Modifiers, which is an important key to generate Modifier chain.
Next, let s focus on
fun foldIn (initial: R , operation: ( R , Element ) -> R ) : R
fun <R> foldOut (initial: R , operation: ( Element , R ) -> R ) : R
Infix Fun the then (OTHER: Modifier ) : Modifier =
IF (=== OTHER Modifier) the this the else CombinedModifier ( the this , OTHER)
copy the code
In order to facilitate the explanation, a few simplified versions of Modifier implementation classes and some functions are provided.
class SizeModifier : Modifier.Element {
override fun toString () = "SizeModifier"
}
class PaddingModifier : Modifier.Element {
override fun toString () = "PaddingModifier"
}
class OffsetModifier : Modifier.Element {
override fun toString () = "OffsetModifier"
}
Fun Modifier. size () = the this .then (SizeModifier ())
Fun Modifier. padding () = the this .then (PaddingModifier ())
Fun Modifier. offset () = the this .then (OffsetModifier ())
copying the code
then function
The then method is an important function to generate the Modifier chain.
fun main () {
val modifier = Modifier.size().padding().offset()
println(modifier)
}
---------Output---------------
[SizeModifier, PaddingModifier, OffsetModifier]
Copy code
Put below
first step
Val modifier1 = Modifier
duplicated code
This Modifier is the Modifier of that static singleton, not an interface.
2.step
val modifier2 = modifier1.size()
Copy code
According to the definition of the size function above =>
Then according to the then function of the static singleton Modifier
companion object : Modifier {
override infix fun then (other: Modifier ) : Modifier = other
}
Copy code
third step
val modifier3 = modifier2.padding()
Copy code
According to the definition of padding function =>
Because modifier2 is a SizeModifier object, SizeModifier implements Modifier.Element, so look at the function definition of Modifier.Element then and find that the then function is not rewritten, that is, the default then function of the Modifier interface is executed.
Infix Fun the then (OTHER: Modifier ) : Modifier =
IF (=== OTHER Modifier) the this the else CombinedModifier ( the this , OTHER)
copy the code
Obviously if the condition is not met, I left the else and combined SizeModifier and PaddingModifier.
=>
=>
the fourth step
Val modifier4 = modifier3.offset ()
to copy the code
This step is similar to modifier3, so
//modifier4 =>
CombinedModifier(outer = modifier3, inner = OffsetModifier())
//modifier4 =>
CombinedModifier(outer = CombinedModifier(modifier2, PaddingModifier()), inner = OffsetModifier())
//modifier4 =>
CombinedModifier(outer = CombinedModifier (SizeModifier (), PaddingModifier ()), Inner = OffsetModifier ())
copying the code
In the end modifier4 got something like a Russian doll CombinedModifier(outer = CombinedModifier(SizeModifier(), PaddingModifier()), inner = OffsetModifier())
Summarize with two pictures below
foldOut function
With the analysis of the generated chain above, let's talk about how to traverse the elements of this chain. The foldOut function is to give an initial value, and then calculate the value according to the elements on the Modifier chain according to the operation function logic we passed in and return it.
fun main () {
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 SizeModifier
Copy code
It's amazing, foldOut can pass all the elements in the Modifier chain. How does it do it? In order to facilitate the analysis, let's modify the above code
fun main () {
// Code 0
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 analysis of the then function of Modifier above, we know the process of forming a chain. From the above analysis shows that the code modifier is CombinedModifier 0 type, the following form in FIG 2-1
Now that I know it is
override fun <R> foldOut (initial: R , operation: ( Modifier . Element , R ) -> R ) : R =
outer.foldOut ( Inner .foldOut (Initial, Operation), Operation)
copy the code
It looks very simple with just two lines of code, but it does a lot of things. Let's transform the above function equivalently.
override fun <R> foldOut (initial: R , operation: ( Modifier . Element , R ) -> R ) : R {
//1 First execute the inner foldOut function
val nextInitial:R= inner .foldOut(initial, operation)
//2 Use the return value of inner foldOut as the initial parameter of outer foldOut function
return outer.foldOut(nextInitial, operation)
}
Copy code
The first step is to execute the inner foldOut function. As shown in Figure 2-1, the inner is an object of type OffsetModifier, and OffsetModifier eventually inherits Modifier.Element. Its foldOut function is implemented as follows
override fun <R> foldOut (initial: R , operation: ( Element , R ) -> R ) : R =
Operation ( the this , Initial)
copy the code
This function calls the operation function we passed in, and passes in the current object and the passed intial as parameters. At this time, the code in the operation function body we wrote is executed for the first time. According to the above analysis, we can see that the first step is
Let's use a piece of pseudo code to demonstrate the execution flow of foldOut
//0 CombinedModifier's foldOut function is divided into two steps.
Override fun <R> foldOut (initial: R , operation: ( Modifier . Element , R ) -> R ) : R {
//1 First execute inner foldOut function
val initial1:R= inner .foldOut(initial, operation)
//2 Use the return value of the inner fodOut as the initial parameter of the outer foldOut function
return outer.foldOut(initial1, operation)
}
//1 If inner is of Modifier.Element type,
//According to the above analysis, the inner foldOut function is actually the operation function
override fun <R> foldOut (initial: R , operation: ( Modifier . Element , R ) -> R ) : R {
//At this time, operation is executed
val initial1:R=operation( inner ,initial)
return outer.foldOut(initial1, operation)
}
//2 Then execute the outer foldOut, if it is still CombinedModifier, then repeat the above steps
override fun <R> foldOut (initial: R , operation: ( Modifier . Element , R ) -> R ) : R {
val initial1:R =operation( inner ,initial)
//Assuming that outer can directly access its inner and outer
val initial2=outer. inner .foldOut(initial1, operation)
return outer.outer.foldOut(initial2, operation)
}
//3 If outer.inner is of Modifier.Element type, the same is true to
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 Until the end outer is not a 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 /*return value of previous step*/ )
...
val initialN:R=operation(outer.outer.......outer. inner ,initialN_1 /*return value of previous step*/ )
return operation(outer.outer.........outer,initialN )
}
Copy code
summary: The foldOut function of CombinedModifier executes its inner foldOut first, then executes the outer foldOut function, and takes the inner foldOut return value as the parameter of the outer foldOut function
foldIn function
The foldIn function is similar to the foldOut function. It also requires an initial value and an operation function, and returns the result according to the Modifier chain calculation. But there are some differences between the two. 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 OffsetModifier
Copy code
Code 3-1 It can be seen from the output result that the execution order of foldIn and foldOut are opposite. CombinedModifier's foldIn function
override fun <R & lt> foldin (Initial: R & lt , Operation: ( R & lt , Modifier . the Element ) -> R & lt ) : = R & lt
Inner .foldIn (outer.foldIn (Initial, Operation), Operation)
copy the code
This function can still be broken down into two steps
override fun <R> foldIn (initial: R , operation: ( R , Modifier . Element ) -> R ) : R{
//1. Execute the outer foldIn function
val result1=outer.foldIn(initial, operation)
//2 Take the return value of the outer foldIn function as the parameter of the
inner foldIn val result0 = inner .foldIn(result1, operation)
return result0;
}
Copy code
For code 3-1, we can expand according to the above function
//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 code
According to the foldIn function of CombinedModifier, the code 0 is equivalent to
//1
val result1=modifier2.foldIn(initial,operation)
val result0=OffsetModifier().foldIn(result1,operation)
return result0
duplicated 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
duplicated code
Code 3-2 modifier1 is a SizeModifier object, which is of type Modifier.Element. The foldIn function of Modifier.Element is as follows
override fun <R> foldIn (initial: R , operation: ( R , Element ) -> R ) : R =
operation(initial, this )
copy the code
This function is very simple to execute the operation function we passed in. PaddingModifier and OffsetModifier are also Modifier.Element types, so **Code 3-2** is equivalent to
val result2 = operation(initial, SizeModifier())
val result1 = operation(result2, PaddingModifier())
val result0 = operation(result1, OffsetModifier())
return result0
duplicated code
It can be seen that foldIn finally executes the operation function we passed in according to the Modifier chain. The result of each operation is used as the initial parameter of the next operation function.
summary
Modifier combines the two Modifiers into a CombinedModifier through the then function. The inner points to the Modifier corresponding to the current node, and the outer points to the Modifier of the next node. If the outer is also a CombinedModifier, then this Modifier can continue to extend.
foldOut & foldIn are the same: given an initial value, return a calculated value. It will traverse every element in the execution Modifier chain. Difference: the order of traversal of the two functions is different. foldOut is executed from back to front according to the order of Modifier chain addition, and foldIn is executed from front to back according to the order of Modifier chain addition.