Android custom view layout principle

Posted by the_damo2004 on Fri, 27 Dec 2019 19:39:03 +0100

Android custom View usually needs to go through the process of measure, layout and draw. If you haven't understood the process of measure, you can take a look at it first This article.

1, Function of Layout: calculate the position of view, that is, the position of Left, Top, Right and Bottom

2, Layout process: similar to measure, layout can be divided into two situations according to the type of View.

View type layout process
Single View Only the position of View itself is calculated
ViewGroup Determine the position of View itself and child View in the parent container

Next, we analyze the two situations respectively.

(1) layout process of single View

Specific process: layout() → onLayout()

The relevant source code analysis is as follows:

/**
  * Source code analysis: layout ()
  * Function: determine the position of View itself, that is, set the four vertex positions of View itself
  */ 
  public void layout(int l, int t, int r, int b) {  

    // Four vertices of the current view
    int oldL = mLeft;  
    int oldT = mTop;  
    int oldB = mBottom;  
    int oldR = mRight;  
      
    // 1. Determine the location of View: setFrame() / setOpticalFrame()
    // That is, initialize the values of four vertices, judge whether the current View size and position have changed & return 
    // ->>Analysis 1, analysis 2
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    // 2. If the size & position of the view changes
    // The position of all child views of the View in the parent container will be determined again: onLayout()
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {  

        onLayout(changed, l, t, r, b);  
        // For the layout of a single View: since a single View has no child views, onLayout() is an empty implementation - > > analysis 3
        // For the layout of the ViewGroup: as the location is related to the specific layout, onLayout() is an abstract method in the ViewGroup, which needs to be overridden (to be explained in detail later)
  ...

}  

/**
  * Analyze 1: setFrame()
  * Function: set the four vertex positions of the View itself according to the four position values passed in
  * That is: determine the location of the View itself
  */ 
  protected boolean setFrame(int left, int top, int right, int bottom) {
        ...
    // The position information of the View is recorded by the following assignment statement, that is, the four vertices of the View are determined
    // This determines the position of the view
    mLeft = left;
    mTop = top;
    mRight = right;
    mBottom = bottom;

    mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

    }

/**
  * Analysis 2: setOpticalFrame()
  * Function: set the four vertex positions of the View itself according to the four position values passed in
  * That is: determine the location of the View itself
  */ 
  private boolean setOpticalFrame(int left, int top, int right, int bottom) {

        Insets parentInsets = mParent instanceof View ?
                ((View) mParent).getOpticalInsets() : Insets.NONE;

        Insets childInsets = getOpticalInsets();

        // The inside is actually a call to setFrame()
        return setFrame(
                left   + parentInsets.left - childInsets.left,
                top    + parentInsets.top  - childInsets.top,
                right  + parentInsets.left + childInsets.right,
                bottom + parentInsets.top  + childInsets.bottom);
    }
    // Back to where it was called

/**
  * Analysis 3: onLayout()
  * Note: for the laytou process of a single View
  *    a. Because a single View has no child views, onLayout() is an empty implementation
  *    b. Since the position calculation of its own View has been carried out in layout (), the layout process of a single View is completed after layout()
  */ 
 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

   // Parameter description
   // Changed the size and location of the current View have changed 
   // left position
   // top position
   // Right right position
   // bottom position

}

We summarize the layout process of a single View:

Single View layout process

(2) layout analysis of ViewGroup

Technological process:

The relevant source code analysis is as follows:

/**
  * Source code analysis: layout ()
  * Function: determine the position of View itself, that is, set the four vertex positions of View itself
  * Note: it is consistent with the layout () source code of a single View
  */ 
  public void layout(int l, int t, int r, int b) {  

    // Four vertices of the current view
    int oldL = mLeft;  
    int oldT = mTop;  
    int oldB = mBottom;  
    int oldR = mRight;  
      
    // 1. Determine the location of View: setFrame() / setOpticalFrame()
    // That is, initialize the values of four vertices, judge whether the current View size and position have changed & return 
    // ->>Analysis 1, analysis 2
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    // 2. If the size & position of the view changes
    // The position of all child views of the View in the parent container will be determined again: onLayout()
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {  

        onLayout(changed, l, t, r, b);  
        // For the layout of a single View: since a single View has no child views, onLayout() is an empty implementation (the above analysis has been completed)
        // For the layout of ViewGroup: as the location is related to the specific layout, onLayout() is an abstract method in ViewGroup, which needs to be rewritten to implement - > > analyze3
  ...

}  

/**
  * Analyze 1: setFrame()
  * Function: determine the position of View itself, that is, set the four vertex positions of View itself
  */ 
  protected boolean setFrame(int left, int top, int right, int bottom) {
        ...
    // The position information of the View is recorded by the following assignment statement, that is, the four vertices of the View are determined
    // This determines the position of the view
    mLeft = left;
    mTop = top;
    mRight = right;
    mBottom = bottom;

    mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

    }

/**
  * Analysis 2: setOpticalFrame()
  * Function: determine the position of View itself, that is, set the four vertex positions of View itself
  */ 
  private boolean setOpticalFrame(int left, int top, int right, int bottom) {

        Insets parentInsets = mParent instanceof View ?
                ((View) mParent).getOpticalInsets() : Insets.NONE;

        Insets childInsets = getOpticalInsets();

        // The inside is actually a call to setFrame()
        return setFrame(
                left   + parentInsets.left - childInsets.left,
                top    + parentInsets.top  - childInsets.top,
                right  + parentInsets.left + childInsets.right,
                bottom + parentInsets.top  + childInsets.bottom);
    }
    // Back to where it was called

/**
  * Analysis 3: onLayout()
  * Role: calculate the position of the View Group containing all child views in the parent container ()
  * Note: 
  *      a. It is defined as an abstract method and needs to be rewritten. Because the determined position of the child View is related to the specific layout, onLayout() is not implemented in the ViewGroup
  *      b. onLayout() must be copied when customizing ViewGroup!!!!!
  *      c. Replication principle: traverse subview, calculate four position values of current subview & determine the position of self body View (call subview layout())
  */ 
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

     // Parameter description
     // Changed the size and location of the current View have changed 
     // left position
     // top position
     // Right right position
     // bottom position

     // 1. Traverse sub View: loop all sub views
          for (int i=0; i<getChildCount(); i++) {
              View child = getChildAt(i);   

              // 2. Calculate the four position values of the current subview
                // 2.1 calculation logic of position
                ...// It needs to be implemented by itself, and it is also the key to customize View

                // 2.2 assignment of calculated position value
                int mLeft  = Left
                int mTop  = Top
                int mRight = Right
                int mBottom = Bottom

              // 3. According to the calculated values of the above four positions, set the four vertices of the subview: call the layout () of the subview & pass the calculated parameters
              // That is, the position of the child View in the parent container is determined
              child.layout(mLeft, mTop, mRight, mBottom);
              // This process is similar to the layout () and onLayout () in the layout process of a single View, which are not described here too much
          }
      }
  }

The layout process of ViewGroup is summarized as follows:

ViewGroup layout process

Finally, let's say an important question: what is the difference between the width and height obtained by getWidth(), getHeight(), getMeasureWidth(), getMeasureHeight()?

First of all, let's look at the definitions of the two,

getWidth()/getHeight(): get the final width and height of View

getMeasureWidth()/getMeasureHeight(): get the width and height measured by View

Then, take a look at the source code of the two:

// Get the width / height measured by View
  public final int getMeasuredWidth() {  
      return mMeasuredWidth & MEASURED_SIZE_MASK;  
      // Smeasuredwidth returned during measure
  }  

  public final int getMeasuredHeight() {  
      return mMeasuredHeight & MEASURED_SIZE_MASK;  
      // Smeasuredheight returned during measure
  }  


// Get the final width / height of View
  public final int getWidth() {  
      return mRight - mLeft;  
      // View final width = right boundary of child view - left boundary of child view.
  }  

  public final int getHeight() {  
      return mBottom - mTop;  
     // View final height = lower boundary of child view - upper boundary of child view.
  }

Finally, let's look at the differences:

It should be noted here that in the unusual case, that is to say, the layout() of View is rewritten to force the setting. In this case, the measured value is different from the final value.

 

Topics: Android