Interviewer: Do you know the correct answers to the 13 "Getting Started" questions drawn by View just now?

 

1. View drawing process is divided into several steps, where to start? After which process can I see the view?

Answer: Starting from the performTraversals of ViewRoot, there are three processes of measure, layout, and draw. After the draw process is over, the view can be seen on the screen.

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

Answer: In 99% of the cases, it can be considered that there is no difference. There are two situations, there is a difference. The first is that sometimes the view will measure multiple times for some reasons. The width and height of the first measurement must not necessarily be the same as the actual width and height at the end, but in this case

The last measured width and height are consistent with the actual width and height. In addition, the actual width and height are determined in the layout process. We can hardcode the actual width and height in the layout process, so that the measured width and height are definitely different from the actual width and height, although it is meaningless and not good too.

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

Answer: The view's own layoutparams and the parent container determine its own measureSpec together. Once the spec is determined, the width and height of the view can be determined in onMeasure.

The top-level view is slightly special. The measurement of decorView is in the source code of ViewRootImpl:

//desire的这2个参数就代表屏幕的宽高,
  childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
  childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
  performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  //decorView的measureSpec就是在这里确定的,其实比普通view的measurespec要简单的多
  //代码就不分析了 一目了然的东西
  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 an ordinary view, is it related to the parent view during the measurement process? If relevant, what role does this parent view or viewgroup play?

Answer: Look at the source code:​

//对于普通view的measure来说 是由这个view的 父view ,也就是viewgroup来触发的。
//也就是下面这个measureChildWithMargins方法
protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
         //第一步 先取得子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的方法 看上去一大串 但是真的逻辑非常简单 就是根据父亲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的spec一定是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 view's meal and onMeasure?

Answer: Look at the source code:

//view的measure是final 方法 我们子类无法修改的。
 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
    }
//不过可以看到的是在measure方法里调用了onMeasure方法
//所以就能知道 我们在自定义view的时候一定是重写这个方法!
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

6. Briefly analyze the measure process of view?

Answer: First review question 4. After the 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 in question 5 that the onMeasure method is actually called.

So we only need to analyze the onMeasure method. Note that the parameters of the onMeasure method are the values ​​of the two specs calculated by his parent view
(the measure method of the view here will slightly modify the specSize value in this spec. No analysis is done because the measure method is very simple to modify the specSize part).

//可以看出来这个就是setMeasuredDimension方法的调用 这个方法看名字就知道就是确定view的测量宽高的
//所以我们分析的重点就是看这个getDefaultSize 方法 是怎么确定view的测量宽高的
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
//这个方法特别简单 基本可以认为就是近似的返回spec中的specSize,除非你的specMode是UNSPECIFIED
//UNSPECIFIED 这个一般都是系统内部测量才用的到,这种时候返回size 也就是getSuggestedMinimumWidth的返回值
 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;
}
//跟view的背景相关 这里不多做分析了
protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

7. What happens if the onMeasure method does not handle wrap_content in the custom view? why? How to deal with 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 own layout is wrap, and the mode is at_most (regardless of the specmode of the parent view).

In this mode, the width and height are equal to specSize (analysis of the getDefaultSize function), and the specSize here is obviously the size of the parentSize. That is, the remaining size of the parent container. Isn't it the same effect as if we set it directly to match_parent?

The solution is to do special processing for wrap in onMeasure, such as specifying a default width and height. When it is found to be wrap_content, just set the default width and height.

8. Does ViewGroup have an onMeasure method? why?

Answer: No, this method is handed over to the subclass to implement it. Different viewgroup subclasses must have different layouts, so onMeasure will simply be implemented by themselves.

9. Why can't the measurement width and height be obtained during the life cycle of the 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 can't determine in which life cycle the measure process of the view must be completed after the execution is completed. You can try the following methods to obtain the measured width and height of the view.

//重写activity的这个方法
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 rewrite 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);
            }
        });
    }

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

Answer: layout is to determine the position of its own view and onLayout is to determine the position of all child elements. In the 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 position of the child element. The onlayout methods of view and viewgroup are not written. Leave it to us to lay out the child elements

11. How many steps does the draw method have?

Answer: There are 4 steps in total, drawing background---------drawing yourself--------drawing chrildren----drawing decoration.

12. What is the use of setWillNotDraw 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(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);
    }

Used to set the flag, 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 need draw and can optimize the execution speed. Viewgroups generally set this to true by default, because most viewgroups are only responsible for layout

Not responsible for draw. The view flag is generally closed by default.

13. What are the points to pay attention to in custom view?

Answer: Mainly to deal with wrap_content and padding . Otherwise, setting these two attributes on the xml side is useless at all. And don't use handler in the view because they already provide the post method. If it is inherited from viewGroup, then also consider in onMeasure and onLayout

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

In view of the above points, a few simple custom views are given for everyone to understand.

Give an example of a circular view:

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);
        //处理为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) {
        super.onDraw(canvas);
        //处理padding的情况
        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, an example is given below. It is a little more complicated to customize the viewgroup (mainly to strengthen the understanding of onMeasure and onLayout). The requirements are as follows:

For a horizontal viewgroup, the inner child elements are assumed to be the same in width and height for simplicity. Let’s write such a simple viewgroup.

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);
        //处理为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) {
        super.onDraw(canvas);
        //处理padding的情况
        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();
    }
}


After reading the likes, develop a habit, search "Programming Ape Development Center" on WeChat to follow this programmer who likes to write dry goods.

In addition , the complete collection of Android first-line interview test sites and materials [full version] have been updated in my [Github] . Friends who need interviews can refer to them. If it is helpful to you, you can order a Star!

Address: [https://github.com/733gh/xiongfan]

Guess you like

Origin blog.csdn.net/qq_39477770/article/details/109160726