This is the first day of my participation in the Gwen Challenge in November. Check out the details: the last Gwen Challenge in 2021

Hi , : )

What questions can this help you solve?

  • whyComposeDon’t need to care aboutviewHierarchy, how can you nest it? (as simple as 10s);
  • ComposeHow to install to traditionalViewOn the view;

Layman – A peek at the layout

Here is a simple piece of Compose code that illustrates an example of Compose under multiple layers of nesting:

If we follow the traditional View thinking, we can easily see that the current content(R.I.C.Tent (FrameLayout)-> layout has 5 levels of nesting, which is highly undesirable.

But now that it’s Compose, will the final draw really have 5 layers?

Let’s open Filpper and see:

Obviously, there is only one ComposeView under R.I.D.C Tent, and then there is an AndroidComposeView inside, and all of our boxes above are eventually parsed and installed into this custom view.

Therefore, we can summarize it simply as follows:

Jetpack-compose customises a base container, ComposeView, as well as other extension views, such as AndroidComposeView, and encapsulates it, providing a variety of components or containers that we use at the top.

So when we setContent in Compose, it initializes a ComposeView and adds an AndroidComposeView that holds all the components we wrote in our code, parses them, and draws them in the traditional UI.

So why doesn’t Compose care about the layout hierarchy?

Because they only have two layers, that is, in the business code, there is only one AndroidComposeView under the ComposeView, while other components like Image and Box are drawn by them. Do you say there will be hierarchy problems compared to traditional View 😂

Some guesses:

Why is it called AndroidComposeView?

Compose now supports not only Android, but also Desktop in the preview, so it’s likely that ComposeView will be available for other platforms as well. So the top layer is ComposeView, and Android support is AndroidComposeView.

Of course, the above is only my guess, if you have other ideas, welcome to share.

Parse -setContent internal implementation

Compose Compose Compose Compose Compose Compose Compose Compose Compose Compose Compose Compose Compose Compose Compose Compose Compose Compose Compose Compose

ComponentActivity

setContent

public fun ComponentActivity.setContent(
    parent: CompositionContext? = null,
    content: @Composable() - >Unit
) {
  	
  // Get the first view in decorView-> r.icontent ->, which in compose defaults to composeView
    val existingComposeView = window.decorView
        .findViewById<ViewGroup>(android.R.id.content)
        .getChildAt(0) as? ComposeView 👉 -> If the view is notnullIf yes, reset the contentif(existingComposeView ! =null) with(existingComposeView) {
      	// Set the parent Compose content
        setParentCompositionContext(parent)
      	// Set the current page contentSetContent (content) 👉 -> If the view obtained above is empty, that is, it is not installed yet, then generate a new one to install on R.I.D.C. tent}else ComposeView(this).apply {
      	// Set the parent Compose content
        setParentCompositionContext(parent)
      	// Set the current page content
        setContent(content)
      	// Setting TreeLifecycleOwner essentially fixes bugs on Appcompat1.3 and below, so ignore them
        setOwners()
      	// Set the content view,activity method
        setContentView(this, DefaultActivityContentLayoutParams)
    }
}
Copy the code

From the source code above, our next major focus is:

  • setParentCompositionContext()
  • setContent()

ComposeView

setParentCompositionContext

Set the parent context of the view composition, which is just an assignment, skip it for now

fun setParentCompositionContext(parent: CompositionContext?). {
        parentContext = parent
}
Copy the code

setContent

Set the compose UI content to be called when the view is added to the window.

fun setContent(content: @Composable() - >Unit) {
    shouldCreateCompositionOnAttachedToWindow = true
    this.content.value = content
  IsAttachedToWindow =true if it is already attached to the window, i.e. when the view is fully displayed
  // The first time it is called, this is false, so the following method is called when onAttachToWindows is called
    if (isAttachedToWindow) {
        createComposition()
    }
}
Copy the code

Let’s look at the onAttachToWindows() method of AbstractComposeView next.


AbstractComposeView

onAttachToWindows

Called when installed on Windows.

 override fun onAttachedToWindow(a) {
        super.onAttachedToWindow()
        previousAttachedWindowToken = windowToken
   	// Here it is true when onAttached because the previous step was already set to true
        if (shouldCreateCompositionOnAttachedToWindow) {
            ensureCompositionCreated()
        }
    }
Copy the code
ensureCompositionCreated
private fun ensureCompositionCreated(a) {
  	// We did not see composition initialized during the process, so null is set here
    if (composition == null) {
        try{👉1.  /// this is a variable used to determine whether it has been added
            creatingComposition = true
       👉 2. / / read resolveParentCompositionContext ()
             // -> get a top-level CompositionContext
          	
         👉 3. // Take parentContext and pass in the setContent method
            composition = setContent(resolveParentCompositionContext()) {
              Content()
            }
        } finally {
            creatingComposition = false}}}Copy the code

resolveParentxxxContext

Its role is to resolve the parent composition context.

private fun resolveParentCompositionContext(a) = parentContext
		/ / 👉 1.? : findViewTreeCompositionContext()? .also { cachedViewTreeCompositionContext = it } ? : cachedViewTreeCompositionContext/ / 👉 2.? : windowRecomposer.also { cachedViewTreeCompositionContext = it }Copy the code
1. findViewTreexxxContext

Find the parent context of the current view tree

fun View.findViewTreeCompositionContext(a): CompositionContext? { 
// Get your own compositionContenxt first, and return it if you get it, otherwise keep looking up for the parent context.
var found: CompositionContext? = compositionContext
if(found ! =null) return found
var parent: ViewParent? = parent
while (found == null && parent is View) {
  found = parent.compositionContext
  parent = parent.getParent()
}
return found
}
Copy the code
View.compositionContext

CompositionContext is an extension function that internally holds the current context using a tag

var View.compositionContext: CompositionContext?
get() = getTag(R.id.androidx_compose_ui_view_composition_context) as? CompositionContext
set(value) {
  setTag(R.id.androidx_compose_ui_view_composition_context, value)
}
Copy the code

2. windowRecomposer

Let’s continue the analysis with the above process:

@OptIn(InternalComposeUiApi::class)
internal val View.windowRecomposer: Recomposer
    get() {...// This rootView is the first child of the FrameLayout corresponding to R.I.D.C tent, i.e. ComposeView
        val rootView = contentChild
      	// Since this is the initialization state, rootParentRef is also null for the first time, so let's continue
        return when (val rootParentRef = rootView.compositionContext) {
         // Call the window Recomposer to create a new one and pass in the root content
            null-> WindowRecomposerPolicy.createAndInstallWindowRecomposer(rootView) ... }}Copy the code
View.contentChild
private val View.contentChild: View
get() {
  var self: View = this
  var parent: ViewParent? = self.parent
  while (parent is View) {
      if (parent.id == android.R.id.content) return self
      self = parent
      parent = self.parent
  }
  return self
}
Copy the code
createAndxxxRecomposer(rootView)

Create a Recomposer and assign it to the rootView extension variable compositionContext

internal fun createAndInstallWindowRecomposer(rootView: View): Recomposer {
  	// Create a Recomposer using the default factory
        val newRecomposer = factory.get().createRecomposer(rootView)
  	// Assign it to the compositionContext of rootView,
        // compositionContext is an extension function that uses a tag
        rootView.compositionContext = newRecomposer
  		...
}

-- View.compositionContext
var View.compositionContext: CompositionContext?
get() = getTag(R.id.androidx_compose_ui_view_composition_context) as? CompositionContext
set(value) {
  setTag(R.id.androidx_compose_ui_view_composition_context, value)
}
Copy the code

Here, we know the resolveParentCompositionContext initialization () is actually the extension variable compositionContext ComposeView. And we get the return value parentContext.


ViewGroup.setContent

So let’s go back to setContent and see:

👉 3. SetContent (resolveParentCompositionContext ())

internal fun ViewGroup.setContent(
parent: CompositionContext,
content: @Composable() - >Unit
): Composition {
GlobalSnapshotManager.ensureStarted()
val composeView =
  if (childCount > 0) {
    	// The first call must be null, notice the following method calls
      getChildAt(0) as? AndroidComposeView
  } else {
      removeAllViews(); null
    // Initialize an AndroidComposeView and pass in our outermost content function,
      // Add the initialized AndroidComposeView to the ComposeView
  } ?: AndroidComposeView(context).also { addView(it.view, DefaultLayoutParams) }
	// This goes deeper, and will be covered in the next chapter
     return doSetContent(composeView, parent, content)
}
Copy the code

As mentioned above, here we take the current viewGroup, ComposeView, and then determine if childCount has any child views, since this is the first time, we must be executing flase.

AndroidComposeView(Context).also {addView(it. View, DefaultLayoutParams)} This generates a ViewGroup container for AndroidComposeView, and the context in the inner constructor is our content() of step 3, which is our own business code. You then call the addView() method of ComposeView to add yourself to the ComposeView.

At this point, if you remember our initial layout hierarchy, you should be able to understand the basic process.

conclusion

  1. When we callComposeActivity çš„ setContent()After that, its internal first judge the current basis(R.id.content) çš„ ViewIsn’t itComposeViewIf not, initialize one and call itssetContent()Methods,shouldCreateCompositionOnAttachedToWindowSet totrueAnd pass in the first onecontentThe function assigns a value toComposeInternal variablecontent 。
  2. Then use theActivity çš„ setContentView(), will be initializedComposeViewAdd to the underlying layoutR.id.contentOn;
  3. inviewWhen completely visible, i.eonAttachViewWhen called, starts to deinitialize the currentcomposePage, which internally initializes a page namedAndroidComposeViewA child of the View. And then call what we passed incontent()Function to generate acontentPass it in as a constructorAndroidComposeView, which generates a child view. thenaddtheComposeViewOn. This completes the initialization of the layout.

twitter

Compose and View Compose Compose Compose Compose and View Compose Compose Compose and View Compose Compose Compose and View Compose Compose I will continue to delve into the source code design of Compose and the scenario solution in practice.

If this article was helpful to you, please like it and support it