ConstraintLayout is a component that Google uses to reduce the hierarchy of ConstraintLayout. Interpret is a component that Google uses to reduce the hierarchy of ConstraintLayout.

A, doubtful

First let’s think about what would happen if we added the following constraints to a View in ConstraintLayout

app:layout_constraintBottom_toTopOf="parent"
Copy the code

The intent of the constraint is to align the bottom of the child with the top edge of the parent control, so that the control will be moved out of the parent control. The purpose of the constraint is to do something like a drawer, where the child control will be animated.

The complete layout is as follows:


      
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#29B6F6"
    tools:context=".MainActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="256dp"
        android:layout_height="256dp"
        android:layout_centerInParent="true"
        android:background="#FFF">

        <View
            android:id="@+id/clcTop"
            android:layout_width="128dp"
            android:layout_height="128dp"
            android:background="#FFC107"
            android:tag="Top"
            app:layout_constraintBottom_toTopOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent" />


    </androidx.constraintlayout.widget.ConstraintLayout>

</RelativeLayout>
Copy the code

So what are the practical effects?

Well, it looks as expected. Wait, it looks like… What is that? Let’s zoom in ten times:

It seems that the child control is not completely removed from the parent control, which is a bit of a slipshod. Is this a problem with the AndroidStudio rendering? On the machine!

To confirm the problem, check out the Layout Inspector

This problem is confirmed, this constraint is not completely removed from the child control, will leave two pixels in the parent control, it seems to be reluctant to part, love deep ah! Is this my fault of improper use or ConstraintLayout?

Second, the traceability

After a variety of searches, and did not find the answer, it seems that there is no shortcut, can only rely on their own.

ConstraintLayout is only a couple of thousand lines, but it’s only the tip of the iceberg of a constrained layout system that can get stuck. Through the following problem tracing, we will also have a more intuitive impression of the constraint layout system.

ConstraintLayout is the parent control that determines the position of the child control. When do I tell the child control its position? For onLayout, we use breakpoint debugging to trace.

Look at the execution process, there is no complex position calculation when onLayout, at this time the data has been wrong. All the parameters from ConstraintLayout. LayoutParams take a ConstraintWidget object, the object is stem what of? Let’s look at the members of ConstraintWidget, which are of various sizes and sizes in terms of name and data type. Here are a few:

ConstraintAnchor, ConstraintLayout, ConstraintLayout, ConstraintLayout, ConstraintAnchor, ConstraintAnchor, ConstraintAnchor, ConstraintAnchor, ConstraintAnchor

ConstraintLayout, which is held by LayoutParams to hold and record the calculations involved in placing the child controls in the specified position, Child control location information can be taken directly from ConstraintWidget.

Okay, let’s go back to onLayout

int l = widget.getDrawX();
int t = widget.getDrawY();
int r = l + widget.getWidth();
int b = t + widget.getHeight();
child.layout(l, t, r, b);
Copy the code

We see that the top position t is obtained by ConstraintWidget’s getDrawY() method

public int getDrawY(a) {
    return this.mDrawY + this.mOffsetY;
}
Copy the code

Breakpoint debugging found that mOffsetY is 0, so we can trace the change of mDrawY by adding breakpoints to the variable, so that we can observe the change of the variable every time. After further debugging, we can find the change of the variable. UpdateDrawPosition method in ConstraintWidget:

ConstraintWidget (), ConstraintWidget (), ConstraintWidget (), ConstraintWidget ();

Again, the data is in error, the error came from the input parameter, let’s jump out and see where it is, ConstraintWidget, updateFromSolver method:

The data is taken out of the LinearSystem by the mTop, which is the constraint point at the top. What is the LinearSystem? The positions are actually calculated using the LinearSystem, but I don’t understand the specific process. Let’s focus on the problem instead of solving it thoroughly.

Moving on, let’s take a look at how a linear equation system calculates the position based on the anchor points, and go into the getObjectVariableValue method on the LinearSystem

public int getObjectVariableValue(Object anchor) {
    SolverVariable variable = ((ConstraintAnchor)anchor).getSolverVariable();
    returnvariable ! =null ? (int)(variable.computedValue + 0.5 F) : 0;
}
Copy the code

The data is already computed, so we simply extract the mSolverVariable member of ConstraintAnchor, and the computedValue member of SolverVariable, and so on.

The computedValue is a floating-point type, and the return value is an integer, so it must be cast by force. Instead of being cast by force, it must be cast by force. So what’s going on here? Let’s look at debug:

We see that the variable.computedValue is actually -255, but after this strong rotation, it returns -254, so it looks like we’ve found the source of the deviation.

Floating-point type integer is a certain loss of accuracy, but what is the specific rules, checked the Java official, also not explained only says this is narrow, lose precision, after the test, if it is positive, will efface small digital directly, if it is negative, the decimal part will be in a non zero, while Google this way, The intent may have been to round, but negative numbers were not taken into account, resulting in deviations when the coordinates were negative.

After a look at the source code, found that there is a lot of this conversion, each conversion, the location of a pixel offset…

ConstraintLayout, ConstraintLayout, ConstraintLayout, ConstraintLayout, ConstraintLayout Here’s what you learned about ConstraintLayout:

As stated earlier, ConstraintLayout is just the tip of the iceberg for ConstraintLayout systems. The bigger iceberg is ConstraintLayout’s dependency on another library, constraintLayout-Solver, which is really responsible for doing control position calculations based on constraints.

OnLayout control position has been calculated, so where is the entry of the calculation? Actually in onMeasure:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    / / to omit
    if (this.getChildCount() > 0) {
        this.solveLinearSystem("First pass");
    }
    / / to omit
    if (needSolverPass) {
    // 
        this.solveLinearSystem("2nd pass");
        / / to omit
        if (needSolverPass) {
            this.solveLinearSystem("3rd pass"); }}/ / to omit
}
Copy the code

OnMeasure calls solveLinearSystem multiple times to calculate the position of the control:

protected void solveLinearSystem(String reason) {
    this.mLayoutWidget.layout();
    if (this.mMetrics ! =null) {
        ++this.mMetrics.resolutions; }}Copy the code

And solveLinearSystem method, entrusted to mLayoutWidget to calculate, this is a ConstraintWidgetContainer object, it is a subclass of ConstraintWidget, It also holds the ConstraintWidget object in the LayoutParameter of all child controls. Let’s look at its Layout method:

public void layout(a) {
    / / to omit
    if (this.mOptimizationLevel ! =0) {
        if (!this.optimizeFor(8)) {
            this.optimizeReset();
        }
        if (!this.optimizeFor(32)) {
            this.optimize();
        }
        this.mSystem.graphOptimizer = true;
    } else {
        this.mSystem.graphOptimizer = false;
    }
    / / to omit
    for(groupIndex = 0; groupIndex < groupSize && !this.mSkipSolver; ++groupIndex) {
        if(! ((ConstraintWidgetGroup)this.mWidgetGroups.get(groupIndex)).mSkipSolver) {
            / / to omit
           for(int i = 0; i < count; ++i) {
                ConstraintWidget widget = (ConstraintWidget)this.mChildren.get(i);
                if (widget instanceofWidgetContainer) { ((WidgetContainer)widget).layout(); }}/ / to omit
            while(needsSolving) {
                / / to omit
                needsSolving = this.addChildrenToSolver(this.mSystem);
                if (needsSolving) {
                    this.updateChildrenFromSolver(this.mSystem, Optimizer.flags);
                } else {
                    this.updateFromSolver(this.mSystem);
                    / / to omit}}/ / to omit}}}Copy the code

This method is a bit more complicated, and the optimize related method actually does the basic calculation of the child control positions before entering the for loop. In the for loop, you hand the calculation to the child controls, each of them in a LinearSystem to calculate the position, and then you call the addToSolver(LinearSystem) method of interpret twidget.

ConstraintLayout, ConstraintLayout, ConstraintLayout, ConstraintLayout, ConstraintLayout, ConstraintLayout, ConstraintLayout But I’m curious about the LinearSystem. How exactly does the calculation work? Study again sometime.

Three, validation,

After our roots, the cause of the problem is the source code in turn floating-point type integer when trying to use rounding method, but it can cause error when negative deviation, so that there is more than we will start the layout problem, all the layout on the boundary and the left boundary beyond the parent layout, will offset errors, To test our conjecture, the layout is as follows


      
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#29B6F6"
    tools:context=".MainActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="256dp"
        android:layout_height="256dp"
        android:layout_centerInParent="true"
        android:background="#FFF">

        <View
            android:id="@+id/clcCenter"
            android:layout_width="128dp"
            android:layout_height="128dp"
            android:background="#664CAF50"
            android:tag="Center"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent" />

        <View
            android:id="@+id/clcTop"
            android:layout_width="128dp"
            android:layout_height="128dp"
            android:background="#80FF5722"
            android:tag="Top"
            app:layout_constraintBottom_toTopOf="@id/clcCenter"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent" />

        <View
            android:id="@+id/clcLeft"
            android:layout_width="128dp"
            android:layout_height="128dp"
            android:background="#80C6AFEF"
            android:tag="Left"
            app:layout_constraintRight_toLeftOf="@id/clcCenter"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>


    </androidx.constraintlayout.widget.ConstraintLayout>

</RelativeLayout>
Copy the code

The renderings are as follows

There’s something going on. I can’t see. Here, zoom in

As we expected, the controls overlap.

Four, solve

The problem is clear, though it’s not clear whether it’s a bug or Google’s intent, but it will have to be fixed.

I first tried to upgrade to ConstraintLayout’s latest version, 2.0.0-beta6, and still had problems with the fanciful constraints and imperceptible overlap. But once we know the problem, it’s easy to avoid. To achieve the effect of moving the seed control out of the parent control, you can do this by adding translationX or translationY.

ConstraintLayout is not open source on Github, so there is no way to report any bugs.

Five, the afterword.

Fortunately, I recently had time to track down the two pixels that caused the disaster, but it would not have been obvious if the example had not used a high-contrast color, so I would have let it go if I had been busy. Too many of these details are missed in the work, too busy to meet the needs of the various, no time to polish the basic skills, in fact, every such pursuit is an opportunity for progress.

Another day has passed when programmers are unpretentious and boring.