SoFunction
Updated on 2025-04-08

13 Questions and 13 Answers Full Learn Android View Drawing

This article learns Android View drawing through 13 questions and 13 answers for your reference. The specific content is as follows

How many steps is the drawing process, and where to start? Which process can you see the view after the end?

Answer: Start with ViewRoot's performTraversals, and go through three processes: measure, layout, and draw. After the draw process is over, you can see the view on the screen.

 Is there any difference between the measured width and height of the actual width and height?

Answer: Basically, 99% of the cases can be considered to be no difference. There are two situations, and there is a difference. The first type is that sometimes the view will measure multiple times for some reason. The width and height measured for the first time are definitely not necessarily equal to the actual width and height in the end, but in this case

The width and height of the last measurement are consistent with the actual width and height. In addition, the actual width and height are determined in the layout process. We can write the actual width and height into hard code in the layout process. In this way, the measured width and height are definitely different from the actual width and height, although it is meaningless and not good to do so.

 Who decides the measureSpec? Where is the top view?

Answer: Let view your own layoutparams and parent container together decide your own measureSpec. Once the spec is determined, the width and height of the view can be determined in onMeasure.

The top view is a little special, and the measurement of decorView is in the source code of ViewRootImpl.

//These two parameters of desire represent the width and height of the screen. childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, );
 childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, );
 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);



 //The measureSpec of decorView is determined here, but it is actually much simpler than the measurespec of ordinary view //The code will not be analyzed. Something can be seen at a glance private static int getRootMeasureSpec(int windowSize, int rootDimension) {
  int measureSpec;
  switch (rootDimension) {

  case .MATCH_PARENT:
   // Window can't resize. Force root view to be windowSize.
   measureSpec = (windowSize, );
   break;
  case .WRAP_CONTENT:
   // Window can resize. Set max size for root view.
   measureSpec = (windowSize, MeasureSpec.AT_MOST);
   break;
  default:
   // Window wants to be an exact size. Force root view to be that size.
   measureSpec = (rootDimension, );
   break;
  }
  return measureSpec;
} 

4. For ordinary view, is it related to the parent view during its measurement? If it is related, what role does this parent view, that is, viewgroup play?

Answer: See the source code:

//For the measurement of an ordinary view, it is triggered by the parent view of this view, that is, the viewgroup.//This is the following measureChildWithMargins method
protected void measureChildWithMargins(View child,
   int parentWidthMeasureSpec, int widthUsed,
   int parentHeightMeasureSpec, int heightUsed) {
   //Step 1: Get the layoutParams parameter value of the subview first  final MarginLayoutParams lp = (MarginLayoutParams) ();

  //Then start calculating the spec value of the subview. Note that when calculating, in addition to using the layoutparams parameter of the subview, the calculation is performed.  //The value of the parent view, that is, the viewgroup's own spec is also used.  final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
    mPaddingLeft + mPaddingRight +  + 
      + widthUsed, );
  final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
    mPaddingTop + mPaddingBottom +  + 
      + heightUsed, );

  (childWidthMeasureSpec, childHeightMeasureSpec);
}






//This method of calculating the spec of the view looks like a lot of things, but the logic is really very simple. It is based on the father's viewgroup//The meaurespec also has the view of its own params to determine the view of its own measureSpec.//Note that the parameter here is padding, the meaning of this value is the size of the control occupied by the parent container, so the Specsize of the viewThe value of // You can see that the value of this padding is to be subtracted.  Total size - already used = available.  Very easy to understand.
//Then you need to sort out the following switch logic clearly.  Actually, it is not difficult, mainly the following principles//If the view uses a fixed width and height, that is, the type of numerical value that is written.  Then regardless of the value of the father's spec, the spec of the view must be exactly and the size follows the size set in the layout parameter.
//If the width and height of the view are match_parent, then it depends on the spec value of the parent container viewgroup. If the spec of the parent view is exactly mode,//The view must be exactly, and the size is the remaining space in the parent container.  If the parent container is at_most mode, then the view is at_most and will not exceed the remaining space size
//If the width and height of the view are wrap_content, then regardless of the spec of the parent container, the spec of the view must be at_most and will not exceed the size of the remaining space of the parent view.

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
  int specMode = (spec);
  int specSize = (spec);

  int size = (0, specSize - padding);

  int resultSize = 0;
  int resultMode = 0;

  switch (specMode) {
  // Parent has imposed an exact size on us
  case :
   if (childDimension >= 0) {
    resultSize = childDimension;
    resultMode = ;
   } else if (childDimension == LayoutParams.MATCH_PARENT) {
    // Child wants to be our size. So be it.
    resultSize = size;
    resultMode = ;
   } else if (childDimension == LayoutParams.WRAP_CONTENT) {
    // Child wants to determine its own size. It can't be
    // bigger than us.
    resultSize = size;
    resultMode = MeasureSpec.AT_MOST;
   }
   break;

  // Parent has imposed a maximum size on us
  case MeasureSpec.AT_MOST:
   if (childDimension >= 0) {
    // Child wants a specific size... so be it
    resultSize = childDimension;
    resultMode = ;
   } else if (childDimension == LayoutParams.MATCH_PARENT) {
    // Child wants to be our size, but our size is not fixed.
    // Constrain child to not be bigger than us.
    resultSize = size;
    resultMode = MeasureSpec.AT_MOST;
   } else if (childDimension == LayoutParams.WRAP_CONTENT) {
    // Child wants to determine its own size. It can't be
    // bigger than us.
    resultSize = size;
    resultMode = MeasureSpec.AT_MOST;
   }
   break;

  // Parent asked to see how big we want to be
  case :
   if (childDimension >= 0) {
    // Child wants a specific size... let him have it
    resultSize = childDimension;
    resultMode = ;
   } else if (childDimension == LayoutParams.MATCH_PARENT) {
    // Child wants to be our size... find out how big it should
    // be
    resultSize =  ? 0 : size;
    resultMode = ;
   } else if (childDimension == LayoutParams.WRAP_CONTENT) {
    // Child wants to determine its own size.... find out how
    // big it should be
    resultSize =  ? 0 : size;
    resultMode = ;
   }
   break;
  }
  return (resultSize, resultMode);
 } 

What is the relationship between meaure and onMeasure?

Answer: See the source code:

//The measure of view is final method Our subclass cannot be modified. public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  boolean optical = isLayoutModeOptical(this);
  if (optical != isLayoutModeOptical(mParent)) {
   Insets insets = getOpticalInsets();
   int oWidth =  + ;
   int oHeight =  + ;
   widthMeasureSpec = (widthMeasureSpec, optical ? -oWidth : oWidth);
   heightMeasureSpec = (heightMeasureSpec, optical ? -oHeight : oHeight);
  }

  // Suppress sign extension for the low bytes
  long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
  if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

  if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
    widthMeasureSpec != mOldWidthMeasureSpec ||
    heightMeasureSpec != mOldHeightMeasureSpec) {

   // first clears the measured dimension flag
   mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

   resolveRtlPropertiesIfNeeded();

   int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
     (key);
   if (cacheIndex < 0 || sIgnoreMeasureCache) {
    // measure ourselves, this should set the measured dimension flag back
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
   } else {
    long value = (cacheIndex);
    // Casting a long to int drops the high 32 bits, no mask needed
    setMeasuredDimensionRaw((int) (value >> 32), (int) value);
    mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
   }

   // flag not set, setMeasuredDimension() was not invoked, we raise
   // an exception to warn the developer
   if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
    throw new IllegalStateException("View with id " + getId() + ": "
      + getClass().getName() + "#onMeasure() did not set the"
      + " measured dimension by calling"
      + " setMeasuredDimension()");
   }

   mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
  }

  mOldWidthMeasureSpec = widthMeasureSpec;
  mOldHeightMeasureSpec = heightMeasureSpec;

  (key, ((long) mMeasuredWidth) << 32 |
    (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
 }

//However, you can see that the onMeasure method is called in the measure method//So you can know that when we customize the view, we must rewrite this method! protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
 }

6. Briefly analyze the view's measure process?

Answer: First review question 4. After viewgroup calculates the spec of the child view, it will call the measure method of the child view, and the measure method of the child view. We have also seen the onMeasure method that is actually called.

So we just need to analyze the onMeasure method well. Note that the parameters of the onMeasure method are exactly the values ​​of the two specs calculated by its parent view (the measure method of the view will slightly modify the specSize value in this spec. This part will not be analyzed because the measure method is very simple to modify the specSize part).

//You can see that this is the call of the setMeasuredDimension method. This method can be determined by the name of the view.//So the focus of our analysis is to see how the getDefaultSize method determines the measured width and height of the view. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
 }


//This method is very simple. It can basically be considered to be an approximate return specSize in the spec, unless your specMode is UNSPECIFIED//UNSPECIFIED This is usually used for internal measurement of the system. In this case, the size is returned, which is the return value of getSuggestedMinimumWidth. public static int getDefaultSize(int size, int measureSpec) {
  int result = size;
  int specMode = (measureSpec);
  int specSize = (measureSpec);

  switch (specMode) {
  case :
   result = size;
   break;
  case MeasureSpec.AT_MOST:
  case :
   result = specSize;
   break;
  }
  return result;
}

//Related to the background of the view. I won't do much analysis hereprotected int getSuggestedMinimumWidth() {
  return (mBackground == null) ? mMinWidth : max(mMinWidth, ());
 } 

7. In a custom view, what happens if the onMeasure method does not process wrap_content? Why? How to solve it?

Answer: If wrap_content is not processed, even if you set wrap_content in xml, the effect is the same as match_parent. Look at the analysis of question 4. We can know that the view's layout is wrap, and the mode is at_most (regardless of the specmode of the father's view).

In this mode, the width and height are equal to specSize (it can be seen by the analysis of the getDefaultSize function), and the specSize here is obviously the size of parentSize. That is the size remaining in the parent container. Isn’t that the same effect as when we set it directly to match_parent?

The solution is to do special processing for wrap in onMeasure. For example, specify a default width and height. When it is found that it is wrap_content, set this default width and height.

 Is there an onMeasure method? Why?

Answer: No, this method is left to the subclass to implement itself. Different viewgroup subclasses must have different layouts, so they can simply leave them all to implement.

9. Why can’t measured width and height be obtained during the life cycle of an activity? Is there any way to solve this problem?

Answer: Because the process of measure has nothing to do with the life cycle of activity. You cannot determine which life cycle the view's measurement process will be completed after the execution is completed. You can try the following methods to obtain the measured width and height of the view.

//Rewrite the activity methodpublic void onWindowFocusChanged(boolean hasFocus) {
  (hasFocus);
  if (hasFocus) {
   int width = ();
   int height = ();
   ("burning", "width==" + width);
   ("burning", "height==" + height);

  }
 }

Or rewrite this method

@Override
 protected void onStart() {
  ();
  (new Runnable() {
   @Override
   public void run() {
    int width = ();
    int height = ();
   }
  });
 }

Or:

@Override
 protected void onStart() {
  ();
  ViewTreeObserver observer = ();
  (new () {
   @Override
   public void onGlobalLayout() {
    int width = ();
    int height = ();
    ().removeOnGlobalLayoutListener(this);
   }
  });
 }

What is the difference between onLayout method?

Answer: layout is to determine the position of its own view, and onLayout is to determine the position of all child elements. In layout, the position of the four vertices of the view is set through the serFrame method. These 4 positions are fixed to determine the position of your view

Then call onLayout to determine the location of the child element. Neither the view nor the viewgroup's onlayout methods are written. All are left to us to layout the child elements ourselves

Method: How many steps are there?

Answer: There are 4 steps in total, draw the background------------------------------------------------------------------------------------------------------------------

What's the use of the method?

Answer: This method is in the view.

/**
  * If this view doesn't do any drawing on its own, set this flag to
  * allow further optimizations. By default, this flag is not set on
  * View, but could be set on some View subclasses such as ViewGroup.
  *
  * Typically, if you override {@link #onDraw()}
  * you should clear this flag.
  *
  * @param willNotDraw whether or not this View draw on its own
  */
 public void setWillNotDraw(boolean willNotDraw) {
  setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
 }

For setting flag bits, that is, if your custom view does not require draw, you can set this method to true. In this way, the system knows that your view does not require draw to optimize the execution speed. Viewgroup generally sets this to true by default, because most viewgroups are only responsible for layout.

Not responsible for drawing. The view flag is usually turned off by default.

13. What are the points to pay attention to when customizing the view?

Answer: The main thing is to deal with wrap_content and padding. Otherwise, setting these two properties on the xml will be useless at all. Also, don't use handler in view because they have provided the post method. If it is inherited from viewGroup, then you should also consider it in onMeasure and onLayout

The influence of padding and layout. In other words, specSize needs to be calculated. Finally, if the view animation or thread needs to be stopped, you can consider doing it in onDetachedFromWindow.

Based on the above points, we will give several simple custom views for everyone to understand.

Give a circular view example:

package ;

import ;
import ;
import ;
import ;
import ;
import ;

/**
 * Created by Administrator on 2016/2/4.
 */
public class CircleView extends View {

 private int mColor = ;
 private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

 private void init() {
  (mColor);
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  (widthMeasureSpec, heightMeasureSpec);
  int widthSpecMode = (widthMeasureSpec);
  int widthSpecSize = (widthMeasureSpec);
  int heightSpecMode = (heightMeasureSpec);
  int heightSpecSize = (heightMeasureSpec);

  //The situation when it is handled as wrap_content  if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
   setMeasuredDimension(200, 200);
  } else if (widthSpecMode == MeasureSpec.AT_MOST) {
   setMeasuredDimension(200, heightSpecSize);
  } else if (heightSpecMode == MeasureSpec.AT_MOST) {
   setMeasuredDimension(widthSpecSize, 200);
  }

 }

 @Override
 protected void onDraw(Canvas canvas) {
  (canvas);
  // Handle padding situation  final int paddingLeft = getPaddingLeft();
  final int paddingRight = getPaddingRight();
  final int paddingTop = getPaddingTop();
  final int paddingBottom = getPaddingBottom();


  int width = getWidth() - paddingLeft - paddingRight;
  int height = getHeight() - paddingTop - paddingBottom;
  int radius = (width, height) / 2;
  (paddingLeft + width / 2, paddingTop + height / 2, radius, mPaint);
 }

 public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  init();
 }

 public CircleView(Context context) {
  super(context);
  init();

 }

 public CircleView(Context context, AttributeSet attrs) {
  super(context, attrs);
  init();
 }
}

Then here is another example. A little more complicated is to customize the viewgroup (mainly to strengthen the understanding of onMeasure and onLayout). The requirements are as follows:

A horizontal viewgroup, internal child elements, for simplicity, let's assume that their width and height are the same. Let's write a simple viewgroup like this.

package ;

import ;
import ;
import ;
import ;
import ;

/**
 * Created by Administrator on 2016/2/4.
 */
//Here we only deal with the padding state. The state of margin is not processed. The impact of margin of subview on measure and layout//It's left to the readers to complete it themselvespublic class CustomHorizontalLayout extends ViewGroup {

 //Set the minimum default control. Custom attributes are not provided here. Write them in the code. You can expand them yourself. final int minHeight = 0;
 final int minWidth = 0;


 public CustomHorizontalLayout(Context context) {
  super(context);
 }

 public CustomHorizontalLayout(Context context, AttributeSet attrs) {
  super(context, attrs);
 }

 public CustomHorizontalLayout(Context context, AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  (widthMeasureSpec, heightMeasureSpec);
  int measureWidth = 0;
  int measureHeight = 0;
  final int childCount = getChildCount();
  measureChildren(widthMeasureSpec, heightMeasureSpec);
  int widthSpecMode = (widthMeasureSpec);
  int widthSpecSize = (widthMeasureSpec);
  int heightSpecMode = (heightMeasureSpec);
  int heightSpecSize = (heightMeasureSpec);
  final View childView = getChildAt(0);
  final int paddingLeft = getPaddingLeft();
  final int paddingRight = getPaddingRight();
  final int paddingTop = getPaddingTop();
  final int paddingBottom = getPaddingBottom();
  //When there are no child controls, our width and height need to be handled specially  if (childCount == 0) {
   //When there is no child control, if there is a wrap in length and width, then let the control be displayed in the smallest form   // Here we set it to 0 at least   if (widthSpecMode == MeasureSpec.AT_MOST || heightSpecMode == MeasureSpec.AT_MOST) {
    setMeasuredDimension(minWidth, minHeight);
   } else {
    //Otherwise, according to our layout attribute    setMeasuredDimension(getLayoutParams().width, getLayoutParams().height);
   }

  } else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
   measureWidth = () * childCount;
   measureHeight = ();
   setMeasuredDimension(paddingLeft + measureWidth + paddingRight, paddingTop + measureHeight + paddingBottom);
  } else if (heightSpecMode == MeasureSpec.AT_MOST) {
   measureHeight = ();
   setMeasuredDimension(paddingLeft + paddingRight + widthSpecSize, paddingTop + paddingBottom + measureHeight);
  } else if (widthSpecMode == MeasureSpec.AT_MOST) {
   measureWidth = () * childCount;
   setMeasuredDimension(paddingLeft + paddingRight + measureWidth, paddingTop + paddingBottom + heightSpecSize);
  }
 }

 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
  final int paddingLeft = getPaddingLeft();
  final int paddingRight = getPaddingRight();
  final int paddingTop = getPaddingTop();
  final int paddingBottom = getPaddingBottom();
  //The initial position on the left is 0  int childLeft = 0 + paddingLeft;
  final int childCount = getChildCount();
  for (int i = 0; i < childCount; i++) {
   final View childView = getChildAt(i);
   if (() != ) {
    final int childWidth = ();
    (childLeft, 0 + paddingTop, childLeft + childWidth, paddingTop + ());
    childLeft += childWidth;
   }
  }
 }
}

The above is all about this article, I hope it will be helpful to everyone's learning.