13 Questions 13 Answers Learn Android View Drawing Completely

  • 2021-06-29 12:00:45
  • OfStack

This paper uses 13 questions and 13 answers to learn Android View drawing for your reference, the specific content is as follows

1.View drawing process is divided into several steps, where to start?Which process will end and see view?

A: From ViewRoot's performTraversals, through measure, layout, draw three processes.When the draw process is finished, you can see view on the screen.

2. Is there a difference between the measured width and the actual width of view?

A: Basically 99 percent of cases can be considered indistinguishable.There are two different situations.The first is that sometimes view measures several times for some reason, and the width of the first measurement is certainly not equal to the final actual width, but in this case

The last measured width and actual width are consistent.In addition, the actual width and height are determined in the layout process. We can write the actual width and height into hard codes in the layout process, so the measured width and actual width will not be the same, although it is meaningless and not good to do so.

3. Who decides measureSpec of view ?What about the top view?

A: view's own layoutparams and parent container 1 together determine its own measureSpec.1Once spec has been determined, the width and height of view can be determined in onMeasure.

The top-level view is a little special, and the decorView measurements are in the ViewRootImpl source code.


//desire This 2 Each parameter represents the width and height of the screen. 
 childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
 childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);



 //decorView Of measureSpec That's where it's determined, it's actually more general view Of measurespec Much easier 
 // Code is not analyzed  1 What you see 
 private static int getRootMeasureSpec(int windowSize, int rootDimension) {
  int measureSpec;
  switch (rootDimension) {

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

4. For ordinary view, is his measure process related to his parent view?If so, what role does this parent view, viewgroup, play?

A: Look at the source code:


//对于普通view的measure来说 是由这个view的 父view ,也就是viewgroup来触发的。
//也就是下面这个measureChildWithMargins方法

protected void measureChildWithMargins(View child,
   int parentWidthMeasureSpec, int widthUsed,
   int parentHeightMeasureSpec, int heightUsed) {
   //第1步 先取得子view的 layoutParams 参数值 
  final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

  //然后开始计算子view的spec的值,注意这里看到 计算的时候除了要用子view的 layoutparams参数以外
  //还用到了父view 也就是viewgroup自己的spec的值
  final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
    mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
      + widthUsed, lp.width);
  final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
    mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
      + heightUsed, lp.height);

  child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}






//这个算view的spec的方法 看上去1大串 但是真的逻辑非常简单 就是根据父亲viewgroup
//的meaurespec 同时还有view自己的params来确定 view自己的measureSpec。
//注意这里的参数是padding,这个值的含义是 父容器已占用的控件的大小 所以view的Specsize
//的值 你们可以看到 是要减去这个padding的值的。总大小-已经用的 =可用的。 很好理解。

//然后就是下面的switch逻辑 要自己梳理清楚。其实也不难,主要是下面几条原则
//如果view采用固定宽高,也就是写死的数值那种。那就不管父亲的spec的值了,view的spec 就肯定是exactly 并且大小遵循layout参数里设置的大小。

//如果view的宽高是match_parent ,那么就要看父容器viewgroup的 spec的值了,如果父view的spec是exactly模式,
//那view也肯定是exactly,并且大小就是父容器剩下的空间。如果父容器是at_most模式,那view也是at_most 并且不会超过剩余空间大小

//如果view的宽高是wrap_content, 那就不管父容器的spec了,view的spec1定是at_most 并且不会超过父view 剩余空间的大小。


public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
  int specMode = MeasureSpec.getMode(spec);
  int specSize = MeasureSpec.getSize(spec);

  int size = Math.max(0, specSize - padding);

  int resultSize = 0;
  int resultMode = 0;

  switch (specMode) {
  // Parent has imposed an exact size on us
  case MeasureSpec.EXACTLY:
   if (childDimension >= 0) {
    resultSize = childDimension;
    resultMode = MeasureSpec.EXACTLY;
   } else if (childDimension == LayoutParams.MATCH_PARENT) {
    // Child wants to be our size. So be it.
    resultSize = size;
    resultMode = MeasureSpec.EXACTLY;
   } 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 = MeasureSpec.EXACTLY;
   } 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 MeasureSpec.UNSPECIFIED:
   if (childDimension >= 0) {
    // Child wants a specific size... let him have it
    resultSize = childDimension;
    resultMode = MeasureSpec.EXACTLY;
   } else if (childDimension == LayoutParams.MATCH_PARENT) {
    // Child wants to be our size... find out how big it should
    // be
    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
    resultMode = MeasureSpec.UNSPECIFIED;
   } else if (childDimension == LayoutParams.WRAP_CONTENT) {
    // Child wants to determine its own size.... find out how
    // big it should be
    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
    resultMode = MeasureSpec.UNSPECIFIED;
   }
   break;
  }
  return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
 } 

5. What is the relationship between meaure of view and onMeasure?

A: Look at the source code:


//view Of measure yes final  Method   Our subclasses cannot be modified. 
 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  boolean optical = isLayoutModeOptical(this);
  if (optical != isLayoutModeOptical(mParent)) {
   Insets insets = getOpticalInsets();
   int oWidth = insets.left + insets.right;
   int oHeight = insets.top + insets.bottom;
   widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
   heightMeasureSpec = MeasureSpec.adjust(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 :
     mMeasureCache.indexOfKey(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 = mMeasureCache.valueAt(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;

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

// But what you can see is measure Called in method onMeasure Method 
// So you know   We are customizing view When 1 Do override this method! 
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
 }

6. Briefly analyze view's measure process?

Answer: Looking back at question 4 first, viewgroup calculated that spec of sub-view would call measure method of sub-view later, while measure method of sub-view We also looked at question 5 which onMeasure method was actually called

So we just need to analyze the onMeasure method, and note that the parameters of the onMeasure method are exactly the two spec values calculated by his father view (here the measure method of view will slightly modify the specSize value in 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 setMeasuredDimension Call to method   This way, by name, you know it's OK view Of measurement width and height 
// So the focus of our analysis is on this getDefaultSize  Method   How to determine view Of measurement width and height 
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
 }


// This method is particularly simple   You can basically think of it as an approximate return spec In specSize Unless yours specMode yes UNSPECIFIED
//UNSPECIFIED  this 1 Usually used for internal system measurements, return at this time size  that is getSuggestedMinimumWidth Return value of 
 public static int getDefaultSize(int size, int measureSpec) {
  int result = size;
  int specMode = MeasureSpec.getMode(measureSpec);
  int specSize = MeasureSpec.getSize(measureSpec);

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

// with view Background Relevance   Not much analysis here 
protected int getSuggestedMinimumWidth() {
  return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
 } 

7. Customize view if the onMeasure method does not apply to wrap_What happens when content does the processing?Why?How to solve it?

A: If there is no response to wrap_content handles this even if you set it to wrap_in xmlcontent. Its effect is also similar to match_parent is the same.Look at the analysis of Question 4.We can know that view's own layout is wrap, and mode is at_most (regardless of father view what specmode is).

In this mode, the width and height are equal to specSize, which is evidently the size of parentSize.This is the remaining size of the parent container.That's not set directly to match_with usIs parent the same?

The solution is to do special processing for wrap in onMeasure, such as specifying a default width and height, when wrap_is foundcontent sets this default width and height.

8.Does ViewGroup have an onMeasure method?Why ?

A: No, this method is left to the subclass to implement itself.The different viewgroup subclasses certainly have different layouts, so the onMeasure will all be implemented by themselves.

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

A: Because the process of measure has nothing to do with the life cycle of activity.You are not sure which life cycle measure process 1 of view will be completed after execution.There are several ways to obtain the measured width and height of view.


// Rewrite activity This method 
public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  if (hasFocus) {
   int width = tv.getMeasuredWidth();
   int height = tv.getMeasuredHeight();
   Log.v("burning", "width==" + width);
   Log.v("burning", "height==" + height);

  }
 }

Or override this method


@Override
 protected void onStart() {
  super.onStart();
  tv.post(new Runnable() {
   @Override
   public void run() {
    int width = tv.getMeasuredWidth();
    int height = tv.getMeasuredHeight();
   }
  });
 }

Or:


@Override
 protected void onStart() {
  super.onStart();
  ViewTreeObserver observer = tv.getViewTreeObserver();
  observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
   @Override
   public void onGlobalLayout() {
    int width = tv.getMeasuredWidth();
    int height = tv.getMeasuredHeight();
    tv.getViewTreeObserver().removeOnGlobalLayoutListener(this);
   }
  });
 }

What is the difference between the 10.layout and onLayout methods?

A: layout determines the location of its own view, while onLayout determines the location of all child elements.Inside layout, the position of the four vertices of view itself is set by the serFrame method.These four locations fix the position of your view

onLayout is then called to determine the location of the child elements.Neither the onlayout methods of view nor viewgroup are written.Left to us all to lay out the child elements

How many steps are there for the 11.draw method?

Answer: 1 is a total of 4 steps, drawing background - --------------------------------------------------------------------------------- drawing chrildren - ------ drawing decoration.

12.What is the use of the setWillNotDraw method?

A: This method is in 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(android.graphics.Canvas)}
  * 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);
 }

This means that if your custom view does not require draw, you can set this method to true.This way the system knows that view does not need draw to optimize execution speed.viewgroup 1 generally sets this as true by default, since viewgroup is mostly responsible for layout only

Not responsible for draw.view is usually turned off by default.

13. What do you need to be aware of when customizing view?

A: Essentially to deal with wrap_content and padding.Otherwise, the xml settings for these two properties are useless at all.Also do not use handler in view because the post method has been provided.If inherited from viewGroup, it should also be considered in onMeasure and onLayout

Effects of padding and layout.That is to say specSize counts one.Finally, if the view animation or thread needs to stop, consider doing it inside onDetachedFromWindow.

For the above points, a few simple customizations of view are given for your understanding.

An example of view with a circle is given:


package com.example.administrator.motioneventtest;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

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

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

 private void init() {
  mPaint.setColor(mColor);
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
  int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
  int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
  int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

  // Processing as wrap_content Situation at the time 
  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) {
  super.onDraw(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 = Math.min(width, height) / 2;
  canvas.drawCircle(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's another example. A slightly more complex point is to customize viewgroup (mainly to enhance understanding of onMeasure and onLayout), which requires the following:

A level of viewgroup, the internal subelements for simplicity we assume they are all the same width and height.To write a simple viewgroup like this.


package com.example.administrator.motioneventtest;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

/**
 * Created by Administrator on 2016/2/4.
 */
// Here we're just dealing with padding Status of   Not processed margin The state of view Of margin  Yes measure and layout Impact 
// Leave it for the reader to finish 
public class CustomHorizontalLayout extends ViewGroup {

 // Set the minimum number of default controls   Custom properties are not available here   Write dead in code   You can expand on your own 
 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) {
  super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  int measureWidth = 0;
  int measureHeight = 0;
  final int childCount = getChildCount();
  measureChildren(widthMeasureSpec, heightMeasureSpec);
  int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
  int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
  int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
  int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
  final View childView = getChildAt(0);
  final int paddingLeft = getPaddingLeft();
  final int paddingRight = getPaddingRight();
  final int paddingTop = getPaddingTop();
  final int paddingBottom = getPaddingBottom();
  // No child controls   time   Our width and height need special treatment 
  if (childCount == 0) {
   // When there are no child controls, if there are 1 Are wrap  Let this control be presented in the smallest possible form 
   // Here we set the minimum to 0
   if (widthSpecMode == MeasureSpec.AT_MOST || heightSpecMode == MeasureSpec.AT_MOST) {
    setMeasuredDimension(minWidth, minHeight);
   } else {
    // Otherwise according to our layout Attributes to 
    setMeasuredDimension(getLayoutParams().width, getLayoutParams().height);
   }

  } else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
   measureWidth = childView.getMeasuredWidth() * childCount;
   measureHeight = childView.getMeasuredHeight();
   setMeasuredDimension(paddingLeft + measureWidth + paddingRight, paddingTop + measureHeight + paddingBottom);
  } else if (heightSpecMode == MeasureSpec.AT_MOST) {
   measureHeight = childView.getMeasuredHeight();
   setMeasuredDimension(paddingLeft + paddingRight + widthSpecSize, paddingTop + paddingBottom + measureHeight);
  } else if (widthSpecMode == MeasureSpec.AT_MOST) {
   measureWidth = childView.getMeasuredWidth() * 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 left initial position is 0
  int childLeft = 0 + paddingLeft;
  final int childCount = getChildCount();
  for (int i = 0; i < childCount; i++) {
   final View childView = getChildAt(i);
   if (childView.getVisibility() != View.GONE) {
    final int childWidth = childView.getMeasuredWidth();
    childView.layout(childLeft, 0 + paddingTop, childLeft + childWidth, paddingTop + childView.getMeasuredHeight());
    childLeft += childWidth;
   }
  }
 }
}

This is the whole content of this article, and I hope it will be helpful for everyone to learn.


Related articles: