Please indicate the source http://blog.csdn.net/yxhuang2008/article/details/51126616
First, the basics
1, ViewRoot and DecorView
ViewRoot corresponds ViewRootImpl class, and it is connected WindowManager DecorView ties, View of the three processes are done by ViewRoot. In ActivityThread, after the Activity object is created, it will be added to the Window DecorView while ViewRoot created objects.
DecorView added to the Window's window procedure.
Pictures from https://yq.aliyun.com/articles/3005
View drawing preformTraversals ViewRootImpl process begins, the following pseudo code is its
// ViewRootImple#performTraverals 的伪代码
private void preformTraverals(){
preformMeasure(...) --------- View.measure(...);
performLayout(...) --------- View.layout(...);
performDraw(...) -------- View.draw(...);
}
2、 MeasureSpec
Two, View workflow
1, measure process
.onMeasure(int widthMeasureSpec, int heightMeasureSpec)
This method is our custom View only when the need to implement the measure to draw logical method, the parameters of this method is widht parent view and the child viewA method of measuring the height required.When Custom View, need to do is calculate more widthMeasureSpec and heightMeasureSpecView width and height, various different processing modes.
.setMeasuredDimension(int measuredWidth, int measureHeith)
The method of measuring the final stage, in onMeasure (int widthMeasureSpec, int heightMeasureSpece) method call, and the resulting calculatedPassing the method size, measuredThe amount of phase ends. This method must be called, otherwise it will report an exception. When the Custom View, the system does not require complicated relationshipMeasure the process, simply callsetMeasuredDimension(int measuredWith, int measuredHeith) 设置根据 MeasureSpecTo calculate the size obtained.
(2) measure the process
Measure the size of the process of passing two parameters
ViewGroup.LayoutParams View own layout parameters;
MeasureSpec class, the child view parent view measurement requirements.
View of the measure process
View of getDefaultSize method
// View#getDefaultSize
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;
}
As can be seen getDefaultSize method, high View width is determined by the specSize.
The direct successor View custom control needs to be rewritten onMeasure (...) method and set its own size when wrap_content, otherwise use wrap_content equivalent to using match_parent in the layout. LayoutParams from MeasureSpece and relational tables can be seen.
Solution, to specify a default View internal width and height (mWith and mHeight), and the width and height can be provided at this wrap_content.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpeceMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSpeceSize = View.MeasureSpec.getSize(widthMeasureSpec);
int heightSepceSize = View.MeasureSpec.getSize(heightMeasureSpec);
if (widthMeasureSpec == MeasureSpec.AT_MOST
&& heightMeasureSpec == MeasureSpec.AT_MOST){
// 设置一个默认的宽高
setMeasuredDimension(mWidth, mHeight);
} else if (widthSpecMode == View.MeasureSpec.AT_MOST){
setMeasuredDimension(mWidth, heightSepceSize);
} else if (heightSpeceMode == View.MeasureSpec.AT_MOST){
setMeasuredDimension(widthSpeceSize, mHeight);
}
}
ViewGroup the measure process.
ViewGroup In addition to completing the process that their measure, but also to call the measure method to traverse all the child elements. ViewGroup is an abstractClass, there is no method of rewriting onMeasure of View. ViewGroup does not define specific process measurement, onMeasure method which requires measurement processEach like to achieve.
After completion of measure can be acquired by measuring the width and height of View getMeasureWidth / getMeasureHeight, to the process to acquire onLayout View measured width and height of the final width or higher.
因为 View 的 measure 过程和 Activity 的生命周期方法不是同步的,因此无法保证 Activity 在 onCreate, onStart, onResume 方法中获取 View 的宽高信息。
解决办法:
1. 在 Activity/View # onWindowFoucsChanged 方法中
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus){
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
}
2. 使用 view.post(Runnable)
@Override
protected void onStart() {
super.onStart();
mTextView.post(new Runnable() {
@Override
public void run() {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
3.使用 ViewTreeObserver
@Override
protected void onStart() {
super.onStart();
ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
int widht = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}
4. 使用 View.measure(int widthMeasureSpec, int heightMeasureSpec)
通过手动对 View 进行 measure 得到 View 的宽高。
View 的 LayoutParams 分:
match_parent:
无法测出;
具体数值(dp/px):
例如宽高都是 100px 时
int widthMesureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
view.measure(widthMesureSpec, heightMeasureSpec);
wrap_parent 时
int widthMesureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) -1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) -1, View.MeasureSpec.AT_MOST);
view.measure(widthMesureSpec, heightMeasureSpec);
2. layout 过程
子视图的具体位置是相对于父视图而言的。View 的 onLayout 方法时空方法,ViewGrop 的 onLayout 方法时 abstract .
如果自定义的 View 继承 ViewGroup ,需要实现 onLayout 方法。
// View#layout
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// setOpticalFrame / setFrame 设定 View 的四个顶点
boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
// 空方法
onLayout(changed, l, t, r, b);
...
}
...
}
getMeasureWidth 和 getWidth 之间的区别:
getMeasureWidth 是 measure() 过程之后获取后,getWidth 是在 layout() 过程之后得到的。getMeasureWidth() 方法中的值是通过 setMeasureDimension() 方法类进行设置的,而 getWidth() 方法中的值是通过视图右边的坐标减去左边的坐标计算出来的。
3.draw 过程
// View#draw
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
// 第一步,绘制背景
drawBackground(canvas);
}
// 正常情况下,跳过第二步和第五步
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
// 第三步, 绘制自身内容
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
// 第四不,绘制子元素
dispatchDraw(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
// 第六步, 绘制 foreground, scrollbars
onDrawForeground(canvas);
// we're done...
return;
}
}
在
不需要绘制 layer 的时候会跳过第二步和第五步。因此在绘制的时候,能不绘制 layer 就尽量不绘制 layer, 以提高绘制效率。
setWillNotDraw
/**
* 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);
}
如果一个 View 不需要绘制任何内容,则将这个标志位设置为 true, 系统会进行相应的优化。当我们在自定义控件继承 ViewGroup 并且本身不具备绘制功能时,可以开启这个标志位从而便于系统进行后续的优化。
invalidate()
请求重绘 View 树,即 draw 过程。假如视图没有发生大小变化就不会调用 layout() 过程,并且只调用那些调用了 invalidate() 方法的 View.
requestLayout()
当布局发生变化时,会调用该方法。在自定义视图中,如果在某些情况下希望重新测量尺寸大小,应该手动去调用该方法,它 会触发 measure() 和 layout() 过程,但不会进行 draw。
三、自定义 View
1、分类
2、注意事项