preface

This is the first in a View Architecture awareness sub-series that will explore how Android designs make views appear correctly on the screen.

About Think Again series

The View series is highly recommended for readers to know why this series was written

Since blogs are not suitable for large code presentations, I will present the code in the form of a WorkShop. link

We know that in GUI programming, there must be an attempt to create a system of content, and there is one in Android, too. In this article, we will explore how the View system in the Framework makes visual presentation, leaving out the underlying content and the content in Compose.


Supplement: 2021-02-22

Thanks to readers Lu Ban thief six suggestions, supplementary content map

This article is quite long, and it spends a lot of time on the measure mechanism of View. This paper attempts to set aside the existing knowledge system of Android and simulate thinking from the real situation to establish a cognitive system.

So the content of the article layout and mapping have a certain difference.

Note: This article does not cover:

  • Canvas Drawing Basics
  • The underlying mechanism of screen rendering

We’re going to start by thinking about how to describe an arbitrary interface, which leads to View inheritance, and view-tree.

When the interface is described, there are three steps to correctly display it:

  • willThe right contentPainted onThe correct position
    • In this article, the content of the Widget is omitted
  • Determine the layout position according to the layout rules.note:According to the size of theCan also be counted asLayout rulesThe category of
  • Measurement display size

We will start from the reality, think and design a feasible measurement rule, and constantly improve it. The key points are:

  • understandWhat about this designThe evolution offrom
  • understandThe measurement itself is equal toLayout rulesAbout,Layout rulesIt will affect the measurement process

If the reader has already laid a solid foundation for some content, it is recommended to read extensively.


How to describe an arbitrary interface

How can we describe an arbitrary interface if we currently know nothing about Android’s content?

  • Whatever effect we’re trying to achieve, it has to exist, okayA virtual form, corresponding to the physical screen
  • System level abstraction of the rendering process, must be through thisVirtual form, and the interface content we describe will be placed in the form
  • In accordance with theObject-oriented thinkingSingle responsibility principleTo describeThis formClass, assumed to be calledWindow, must be the class that describes the viewNot the same. Suppose the view class is calledView
  • The Window can get information about the internal View

And on that basis,

Plan 1: Construct a God class, which is omniscient and can record and express any “text”, “picture”, “block” and other information.

Option 2: Build a simple class View that has a way to know how big it is, abstracts the View content drawing, and can place child views internally, and has a way to determine how to place them;

Obviously, option 1 is not desirable. Let’s refine plan 2.

At this point, we make an assumption that the View has three capabilities

  • Measure your size
  • Can be placedThe child View; And knowing where it is, owning itAbility to layout
  • Know exactly how to draw what you represent

From this, we can split any interface into a structure that can be expressed as a tree.

Now we agree:

  • eachViewThere can only beaparents
  • parentalViewIs used for description onlyLayout information
  • The actualvisualinteractiveView, describes the content information it represents

The problem of describing any interface can then be solved by describing a tree.

Note: at present, this convention is still very rough, but it does not affect our cognition of the problem

Trees can be stored in three ways:

  • Parent representation method
  • Child notation
  • Child brother notation

And an improved version based on the above method

To make it easier to search up and down, we use an improved version of the parent child notation.

Refine scenario 2, ViewGroup and Widget, each in its own right

As we agreed with the tree above

We break it down by responsibilities:

  • Part of the View focuses on the ability to lay out the child View, instead of expressing “text”, “image” and other content information, we abstract it into a subclass ViewGroup. Because there are no rules that specify how to place a child View, it is an abstract class.

  • View that does not contain child views and expresses specific information, such as “text” or “image”, as widgets.

Summary: In the above phase results, we have refined the scheme and described the structure and content of the interface in the form of a tree. There is a default ViewGroup that acts as the root node of the tree.

Let’s start with some pseudocode.

open class View {
    var parent: View? = null

    // Draw ability
    protected open fun onDraw(canvas: Canvas){}// Layout ability
    protected open fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int){}// Measure the ability
    protected open fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int){}}abstract class ViewGroup : View() {
    protected val children = arrayListOf<View>()

    abstract override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int)

    fun addView(view: View) {
        if(view.parent ! =null) {
            throw IllegalArgumentException("view has a parent")
        }
        children.add(view)
        view.parent = this}}Copy the code

Measuring the size

And then we design the ability to measure size,

Suppose you have a View that displays text, and it can measure its own size, but there are three possibilities:

  • Just fit the text
  • The specified size is not the same as the first size, which is divided into two cases:
    • An explicit value that is manually specified
    • A limited area, such as the size of the screen

What does it mean to measure the size of each node in a View-tree?

Accurately complete the layout and complete their own drawing

But there’s an important point: the size of the screen, the size of the screen is fixed, and it’s clear, which means that the maximum area of the interface that can be displayed at one time is fixed.

Similarly, for a Parent View, its display area is, in principle, restricted to the Parent’s area.

But if you think about it, it doesn’t make sense. There’s a revolutionary interaction: swiping, unlimited content in a limited window.

So, let’s keep one thing in mind:

Different types of viewgroups, corresponding to different layout characteristics, their attitudes towards child views are also different, which can be expressed as

  • A child View can require a larger display size than itself. Whether or not it is satisfied and how it is satisfied is a matter later.
  • A child View can ask for a larger display size than itself, but does not give one.

At this point we can conclude that there are two groups of sizes that need to be considered when displaying and drawing content:

  • The size of the content itself
  • Size of area used for display

Similarly, when A View or A View group, called A, is placed in ViewGroup B.

The size of A is the size of the content itself, and the size of B is the size of the display area, and after recursion, the entire View-tree is the same.

Obviously, the measurement starts at the root of the tree, and as a rule of thumb, the entire measurement can be done using depth-first.

What we want to get is the size of the display area for each View. Following the example above, we can specify the display size of a View in three ways:

  • An explicit value
  • Relative value: just enough to let go of its contentwrap_content
  • Relative value: space full of Parent —match_parent

And in the measurement, get accurate results.

Let’s consider these value scenarios again:

For Child,

  • Set the display size toA clear valueThere is no doubt that this definite value can be obtained during measurement
  • Set the display size tomatch_parent, because the measurement is from Parent to Child,For the ChildAs long as the Parent’s measurement has been completed, i.eParentHave already calculated their ownAccurate size, then Child is usedmatch_parentYou can get a definite value. But if Parent doesn’t finish the calculation, let’s forget about it
  • Set upwrap_contentObviously, you have to figure it outIts contentIn order to getDisplay areaA clear value

Note that the above paragraph is very important and deserves careful consideration. Also: In the above content, we first ignore the possible inside margin.

There are a few things we didn’t consider:

Parent not finished counting, Child set match_parent

At the very least, we can be sure that Parent can’t specify an explicit value for display size. In other cases, we need to use mathematical induction to discuss nesting.

For the ViewGroup of the root node, we can get an explicit value for the display size. As we discussed earlier, the child View, using match_parent or explicit value, can get an explicit value with Parent information.

Only if it is wrAP_content, you need to measure its content and determine the display size based on the content size.

It can be determined that when a node in the tree is WRAP_content, the node is taken as the root node and the subtree is taken out. When all branches of the subtree can find nodes that meet the condition R, the root node can determine the display size required by itself.

Condition R is: the View

  • Specifies that the display size ismatch_parentorA clear value
  • Or itslayoutCan make parent size up to oneA clear value

The above paragraph is a bit long, so digest it properly

At this point, we can make a few agreements:

The Parent should assume more responsibility. In combination with its own situation and the Child situation, first make sure whether the Child can get a clear display size.

  • If not, it passes its own information to the Child to continue processing
  • If so, the Parent can figure out the size of the Child,Pay attention to Different types of ParentThere should beDifferent calculation methods. This isIn front of thementioned

Determination of measurement rules

After the above thinking, we can draw up the measurement rules.

  1. The measurement must go from oneDefine your display sizeThe ViewGroup to start
  2. For aThe child ViewAWhen theParentPDetermine theA
    • You can getclearWhen the size is displayed, change the information to:Accurate results can be obtained + The resulting valuePass it to child View A;Note that the result value is evaluated by Parent according to its own rules and may not be the same as the child View
    • Otherwise, the PTheir own sizeYou need to keep measuring to get the resultsIs passed to child View A.
  3. For a Parent, if it iswrap_content, you need to calculate its own size when the display size of the child View is determined.
  4. As long as the View-tree existsNot been determinedA node that displays its own size. You need to start at the root node and continue to traverse the processing measurements.

To make your expression more accurate, use EXACTLY instead of EXACTLY. You have to keep measuring it to get the result and replace it with AT_MOST.

Self-explanatory, AT_MOST means that a maximum value will be given. Meaning: an immediate family member has helped it limit its freedom.

For easy and accurate expression, they are called the measurement mode, or mode for short:

  • EXACTLY: Parent determines the size of the display for the Child. As a rule, the Child should use the value given by Parent
  • AT_MOSTThe: Parent already determines the maximum display size for the Child, and according to the rules, the Child uses it at its discretionThe value cannot exceed the maximum valueDisplay size of

For easy expression, the display size is called size for short

The display is related to the number of pixels on the screen, which is obviously a natural number. In most cases, size can be expressed accurately with an Int value. In rare cases, size is too large to be reasonable.

If you use an object to encapsulate mode and size, you will get a lot of object creation, which is not elegant at all. You can divide ints into high-order areas and low-order areas to express mode and size respectively, which is also the design used in Android

It is also possible that the Parent does not constrain the Child in the measurement mode.

We use a 32-bit Int to identify mode at the top 2 bits and size at the bottom 30 bits

Further optimization to reduce traversal

The fourth point of the rule is to complete the measurement of all nodes in the whole tree by iteration. According to the actual analysis, we can simplify it by recursion.

We agree that for a tail node with wrAP_content set, if it has no actual content, we also assume that it has measured the required display size

So in one recursion, we can measure the entire tree.

In the process of recursion, only the Parent role set to WRAP_content cannot complete the accurate measurement, and the end node must complete its own measurement.

To start the homing process, we can be sure that every time we go to a Parent,

  • Continuation of measurements that have been completedBelong to the.
  • The Children who did not complete the measurement completed the measurement, then according towrap_contentIt must be able to complete the measurement and move onBelong to the

Finally, the whole tree completes the measurement.

Improve the rules and add a mode

Earlier we mentioned swiping as a form of interaction that allows unlimited content to be displayed with limited display space.

That is, we will encounter situations where the Child is not constrained by the Parent. To be more precise, content is not restricted by the presentation subject in the display space.

For this scenario, beyond the functions of the EXACTLY and AT_MOST two measurement modes, we also need a matching measurement mode:

UNSPECIFIED, the Parent does not constrain the Child, which is determined according to its own condition.

Note: For UNSPECIFIED, do not forcibly combine the scenario, especially with the concepts of Warp_content or match_parent. Although they are related, they are not the contents of a category and cannot be derived from each other.

So I singled it out.

Code to verify

Refer to the FrameLayout layout rule in Android, which requires the size of Child: A Child View can request a display size larger than its own, but does not display any size beyond its own display scope. Therefore, we do not adjust the size of the child View to its own situation

First add the necessary content to the View:

open class View {

    companion object {
        const val layout_width = "layout_width"
        const val layout_height = "layout_height"
        var debug = true
    }

    var tag: Any? = null

    var parent: View? = null

    val layoutParams: MutableMap<String, Int> = mutableMapOf()

    var measuredWidth: Int = WRAP_CONTENT

    var measuredHeight: Int = WRAP_CONTENT

    val heightMeasuredSize: Int
        get() = android.view.View.MeasureSpec.getSize(measuredHeight)

    val widthMeasuredSize: Int
        get() = android.view.View.MeasureSpec.getSize(measuredWidth)

    val heightMeasureMode: Int
        get() = android.view.View.MeasureSpec.getMode(measuredHeight)

    val widthMeasureMode: Int
        get() = android.view.View.MeasureSpec.getMode(measuredWidth)


    private var measured: Boolean = false

    fun isMeasured(a) = measured

    // Draw ability
    protected open fun onDraw(canvas: Canvas){}// Layout ability
    protected open fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int){}fun measure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        if(! measured) { onMeasure(widthMeasureSpec, heightMeasureSpec) } }// Measure the ability
    protected open fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        setMeasuredDimensionRaw(widthMeasureSpec, heightMeasureSpec)
        debugMeasureInfo()
    }

    protected fun debugMeasureInfo(a) {
        if (debug) {
            Log.d(
                "view-debug"."$tag has measured: $measured, w mode:${getMode(widthMeasureMode)}, w size: $widthMeasuredSize " +
                        "h mode:${getMode(heightMeasureMode)}, h size: $heightMeasuredSize ")}}protected fun setMeasuredDimension(measuredWidth: Int, measuredHeight: Int) {
        setMeasuredDimensionRaw(measuredWidth, measuredHeight)
    }

    private fun setMeasuredDimensionRaw(measuredWidth: Int, measuredHeight: Int) {
        this.measuredWidth = measuredWidth
        this.measuredHeight = measuredHeight
        measured = true
        if (debug) {
            Log.d(
                "view-debug"."$tag mark has measured: $measured")}}}Copy the code

Add a FrameLayout:

class FrameLayout : ViewGroup() {
    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int){}override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        //handle horizon
        val widthMode = View.MeasureSpec.getMode(widthMeasureSpec)
        var widthSize = View.MeasureSpec.getSize(widthMeasureSpec)

        var wMeasured = false
        var hMeasured = false
        when (widthMode) {
            View.MeasureSpec.EXACTLY -> {
                // widthSize is the exact value determined by the Parent
                wMeasured = true
            }
            View.MeasureSpec.AT_MOST -> {
                // It needs to be measured again, but you can save the information
                measuredWidth = widthMeasureSpec
            }
            else- > {throw IllegalStateException("Measurement mode is not supported:$widthMode")}}// Do the same for the vertical direction

        val heightMode = View.MeasureSpec.getMode(heightMeasureSpec)
        var heightSize = View.MeasureSpec.getSize(heightMeasureSpec)

        when (heightMode) {
            View.MeasureSpec.EXACTLY -> {
                hMeasured = true
            }
            View.MeasureSpec.AT_MOST -> {
                measuredHeight = heightMeasureSpec
            }
            else- > {throw IllegalStateException("Measurement mode is not supported:$widthMode")}}if (hMeasured && wMeasured) {
            setMeasuredDimension(widthMeasureSpec, heightMeasureSpec)
        }

        children.forEach {
            val childWidthMeasureSpec = makeMeasureSpec(widthMode, widthSize, it.layoutWidth)
            val childHeightMeasureSpec = makeMeasureSpec(heightMode, heightSize, it.layoutHeight)
            it.measure(childWidthMeasureSpec, childHeightMeasureSpec)
        }


        if(! hMeasured || ! wMeasured) {var w = 0
            var h = 0
            children.forEach {
                if(! wMeasured) w = maxOf(w, it.widthMeasuredSize)if(! hMeasured) h = maxOf(h, it.heightMeasuredSize) }if (wMeasured)
                w = widthSize

            if (hMeasured)
                h = heightSize

            setMeasuredDimension(
                View.MeasureSpec.makeMeasureSpec(w, widthMode),
                View.MeasureSpec.makeMeasureSpec(h, heightMode),
            )
        }
        if(! allChildHasMeasured())throw IllegalStateException("Child has not completed all measurements")

        debugMeasureInfo()
    }

    private fun makeMeasureSpec(mode: Int, size: Int, childSize: Int): Int {
        // Refer to the Android FrameLayout layout rules, which require the size of Child:
        // A subview can request a display size larger than its own, but does not display any size larger than its own display range.
        // Do not adjust the size of the child View according to its own situation
        val childMode = when (childSize) {
            WRAP_CONTENT -> View.MeasureSpec.AT_MOST
            else -> View.MeasureSpec.EXACTLY

        }

        val childSize2 = when (childSize) {
            WRAP_CONTENT -> size
            MATCH_PARENT -> size
            else -> childSize
        }
        return View.MeasureSpec.makeMeasureSpec(childSize2, childMode)
    }

    private fun allChildHasMeasured(a): Boolean {
        val i = children.iterator()
        while (i.hasNext()) {
            if(! i.next().isMeasured())return false
        }

        return true}}Copy the code

The above code can be understood in combination with the previous rules

We are not at the LayoutParam stage yet, so we declare the necessary layout information to be stored in the map.

Let’s add some helper classes to create the View-tree

enum class Mode(val v: Int) {

    /** * Measure specification mode: The parent has not imposed any constraint * on the child. It can be whatever size it wants. */
    UNSPECIFIED(0 shl 30),

    /** * Measure specification mode: The parent has determined an exact size * for the child. The child is going to be given those bounds regardless * of how  big it wants to be. */
    EXACTLY(1 shl 30),

    /** * Measure specification mode: The child can be as large as it wants up * to the specified size. */
    AT_MOST(2 shl 30)}class A {
    companion object {
        fun getMode(v: Int): Mode {
            Mode.values().forEach {
                if (it.v == v)
                    return it
            }
            throw IllegalStateException()
        }
    }
}
Copy the code

The above code is self-explanatory

typealias Decor<T> = (v: T) -> Unit

val MATCH_PARENT: Int = android.view.ViewGroup.LayoutParams.MATCH_PARENT
val WRAP_CONTENT = android.view.ViewGroup.LayoutParams.WRAP_CONTENT

var View.layoutWidth: Int
    get() {
        returnlayoutParams[View.layout_width] ? : WRAP_CONTENT }set(value) {
        layoutParams[View.layout_width] = value
    }

var View.layoutHeight: Int
    get() {
        returnlayoutParams[View.layout_height] ? : WRAP_CONTENT }set(value) {
        layoutParams[View.layout_height] = value
    }


fun root(a): ViewGroup = FrameLayout().apply {
    this.layoutWidth = 1080
    this.layoutHeight = 1920
}

inline funViewGroup? .frameLayout(decor: Decor<FrameLayout>): ViewGroup {
    val child = FrameLayout()
    child.let(decor)
    return this? .apply { addView(child) } ? : child }inline fun ViewGroup.view(decor: Decor<View>): ViewGroup {
    val child = View()
    child.let(decor)
    return this.apply { addView(child) }
}
Copy the code

The assistants used to implement tree structure descriptions are self-explanatory

Instead of designing unit tests, build a structure:

class ViewTest {

    @Test
    fun testMeasure(a) {

        val tree = root().frameLayout { v1 ->

            v1.tag = "v1"
            v1.layoutWidth = MATCH_PARENT
            v1.layoutHeight = WRAP_CONTENT

            v1.frameLayout { frameLayout ->
                frameLayout.tag = "v2"
                frameLayout.layoutWidth = MATCH_PARENT
                frameLayout.layoutHeight = WRAP_CONTENT

                frameLayout.view {
                    it.tag = "v3"
                    it.layoutWidth = 200
                    it.layoutHeight = 300
                }

                frameLayout.frameLayout {
                    it.tag = "v4"
                    it.layoutWidth = WRAP_CONTENT
                    it.layoutHeight = WRAP_CONTENT
                }
            }
        }

        tree.tag = "root"


        tree.measure(
            View.MeasureSpec.makeMeasureSpec(1080, View.MeasureSpec.EXACTLY),
            View.MeasureSpec.makeMeasureSpec(1920, View.MeasureSpec.EXACTLY)
        )
        assert(tree is FrameLayout)
        assertEquals(true, (tree as FrameLayout).allChildHasMeasured())
    }
}
Copy the code

Take a look at the log output directly:

I/TestRunner: started: testMeasure(osp.leobert.blog.code.ViewTest)
D/view-debug: root mark has measured: true
D/view-debug: v3 mark has measured: true
D/view-debug: v3 has measured: true, w mode:EXACTLY, w size: 200 h mode:EXACTLY, h size: 300 
D/view-debug: v4 mark has measured: true
D/view-debug: v4 has measured: true, w mode:AT_MOST, w size: 0 h mode:AT_MOST, h size: 0 
D/view-debug: v2 mark has measured: true
D/view-debug: v2 has measured: true, w mode:EXACTLY, w size: 1080 h mode:AT_MOST, h size: 300 
D/view-debug: v1 mark has measured: true
D/view-debug: v1 has measured: true, w mode:EXACTLY, w size: 1080 h mode:AT_MOST, h size: 300 
D/view-debug: root has measured: true, w mode:EXACTLY, w size: 1080 h mode:EXACTLY, h size: 1920 
I/TestRunner: finished: testMeasure(osp.leobert.blog.code.ViewTest)
Copy the code

Given that there are nine possible combinations of Parent and Child, let’s verify all of them. I won’t cover the code and results for space


Summary: In a long paragraph above, let’s set aside our knowledge of Android:

  1. How do you design a system to describe any interface

According to experience, the method of using view tree was determined to describe the interface, and realized that different classes should be applied to encapsulate different functions and cooperate with each other to complete the interface description.

  1. I thought about describing dimensionsOne of two waysThree value types are availableAnd extend outmeasurement The view treePer nodeAccording to the size of theThe problem.

From the perspective of reality, a measurement method is obtained and optimized, and the conclusion is drawn:

  • The measurement process goes from Parent to Child. Parent decides the measurement mode for the Child, i.e. mode, exact value in EXACTLY mode and maximum reference value in AT_MOST mode, based on its own situation and Child’s situation

    • From Parent to Child, the entry of measurement ismeasure(), which encapsulates the call itselfonMeasure()Logic, concrete ViewGroup class overridesonMeasure()And calls Child’smeasure()Method, transfer measurement process
    • Display-sizedmeasurementLayout rulesThe relevant
  • The display size of each node in the view tree can be measured through a recursion

At this point, we know enough about this measurement mechanism, but note that it has not been perfected.

Determine the layout

Above, we considered a set of feasible measurement schemes, in which we mentioned: a case

Furthermore, condition R is proposed, in which we mention a concept: layout rules

Based on our experience, there is a system of layout rules in different GUIs. In order to solve the possible layout requirements, different layout classes are abstracted to implement different rules.

As we mentioned earlier, a ViewGroup measures its subviews differently under different rules.

This is very reasonable, the purpose of measurement is for correct layout, different layout rules, have specific measurement rules.

Use LayoutParams to describe layout rules and information

Previously, we created the FrameLayout class based on Android to implement the FrameLayout rules. Of course, this rule is not enough to handle the various interface layout requirements, there are more ViewGroup subclasses waiting to be implemented.

In other words: when a View is added to a ViewGroup, it needs to explain its layout information according to the layout rules of the ViewGroup. Necessary information cannot be default

Obviously,

  • According to object-oriented thinking, layout rule clusters should be encapsulated into classes, calledLayoutParams.
  • According to the single responsibility principle, different layout rules, corresponding to different ViewGroup subclasses, also corresponding to differentLayoutParamsClass, obviously there’s a one-to-one correspondence
  • According to the dependency inversion principle, a View’s layoutParam relies on an abstraction, rather than a specific class of a rule;
  • According to the Richter substitution principle, the inheritance relationship of LayoutParams and ViewGroup should be the same;

As a rule of thumb, we should write code like this, a viewGroup.layoutParams base class that must specify width and height rules.

Views can have margins inside and outside, which can be considered a basic rule.

Continue to add gravity rules to FrameLayout.

We quickly wrote the following code:

abstract class ViewGroup : View() {
    open class LayoutParams(var width: Int.var height: Int) {}open class MarginLayoutParams(width: Int, height: Int) : LayoutParams(width, height) {
        var leftMargin = 0

        var topMargin = 0

        var rightMargin = 0

        var bottomMargin = 0}}class FrameLayout : ViewGroup() {
    class LayoutParams(width: Int, height: Int) : ViewGroup.MarginLayoutParams(width, height) {
        val UNSPECIFIED_GRAVITY = -1

        var gravity = UNSPECIFIED_GRAVITY
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int){}override fun checkLayoutParams(layoutParams: ViewGroup.LayoutParams): Boolean {
        return layoutParams is LayoutParams
    }

    override fun generateDefaultLayoutParams(a): ViewGroup.LayoutParams {
        return LayoutParams(MATCH_PARENT, MATCH_PARENT)
    }
}
Copy the code

And the original Demo project reconstruction, limited to space, omit the relevant code

Note: According to The Richter substitution principle, the LayoutParams system defined by us may encounter the problem that the input does not meet the expectation when it is used. At this point we need to know: Design by contract:

With design by contract, methods in a class need to declare pre – and post-conditions. The method can only be executed if the precondition is true. Before the method call completes, the method itself ensures that the post-condition holds.

Therefore, in ViewGroup system, design:

  • checkLayoutParams(layoutParams: ViewGroup.LayoutParams): Boolean
  • generateDefaultLayoutParams(): ViewGroup.LayoutParams

We can use two types of contract:

  • The input LayoutParams must satisfy the constraint, otherwise an exception is thrown
  • The entered LayoutParams must satisfy the constraints, otherwise the default rules are used

Obtain layout rule information and layout according to the layout rules of the ViewGroup

So far, we have understood:

  • Describe an arbitrary view using a view tree
  • Different ViewGroup subclasses describe different layouts with specific layout rules; Display different content with different widgets
  • A measurementView each node of the treeAccording to the size of theThe measurement method of
  • Different rules determine the details of the display size measurement
  • Use LayoutParams to describe layout rule information

On this basis, we need to accept the setting:

There is a mechanism for correctly parsing layout rule information declared in each node of the view tree, which is stored in the correct LayoutParams object and held by the corresponding node for use.

This mechanism, we’re going to ignore for the moment.

In terms of experience, the layout and measurement process is similar,

We define layout() and onLayout() methods

open class View {
    open fun layout(l: Int, t: Int, r: Int, b: Int) {
        //todo
    }

    // Layout ability
    protected open fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int){}}Copy the code

For parameters, the convention is:

  • l Left position, relative to parent
  • t Top position, relative to parent
  • r Right position, relative to parent
  • b Bottom position, relative to parent

After completing the size test and layout rule analysis, the calculation of these relative values is not complicated.

We’ve agreed that the actual layout logic is done in onLayout, and the Layout method is used to implement preconditions, onLayout calls, and state maintenance

For ViewGroup, Children need to be traversed to display size information & layout rule information for each Child and determine its layout position, namely l,t,r, B four parameter values. Call the Layout () method of Child.

In the case of widgets, you need to determine the display area for the Content, because the Content is no longer a View and you don’t need to call the Layout method down.

At this point, all the preparation work is complete, next, is the drawing work.

The last step is to draw in the right place

Before this, we have got the correct position of each node in the view tree. At this point, we just need to draw the content in the corresponding position, and then the user can see it on the screen.

Based on previous experience, we define:

  • The draw(canvas:Canvas) method encapsulates the entire drawing process
  • OnDraw (canvas: canvas) method to draw content
  • If you overwrite onDraw(canvas: canvas) in ViewGroup at the same timeThe drawing of its own content.Such as the background, andDistribute the drawing of ChildThis does not comply with the open closed principle

Therefore, we add dispatchDraw(canvas: Canvas) to distribute the drawing of the Child

In fact, so far, we have a relatively complete understanding of the correct display of the content, the content of the drawing, understanding is not complex, but the content is very complex, this article will not expand.

Looking forward to

It also plans to write the following:

  • Compare with the implementation ideas in the Framework
  • Code validation layout rules, etc

These were omitted for lack of space. But I will present some of the coding validation in the form of WorkShop.

The next article will explore the interactive functions of the View architecture. Focus not to get lost.

Note: As for the content of the WorkShop, the original plan is to independently establish a simple and visual interactive system in accordance with the knowledge system in the blog. To validate and help understand the knowledge of the Android-View architecture.

I hope we have plenty of business time this year.