转载请注明出处 http://blog.csdn.net/yxhuang2008/article/details/51126616
一、基础知识
1、ViewRoot 和 DecorView
ViewRoot 对应 ViewRootImpl 类,它是连接 WindowManager 和 DecorView 的纽带,View 的三大流程都是通过 ViewRoot 来完成的。在ActivityThread 中,当 Activity 对象被创建完毕后,会将 DecorView 添加到 Window 中,同时会创建 ViewRoot 对象。
DecorView 添加到窗口 Window 的过程。
图片来自 https://yq.aliyun.com/articles/3005
View 的绘制流程从 ViewRootImpl 的 preformTraversals 开始,下面是它的伪码
// ViewRootImple#performTraverals 的伪代码
private void preformTraverals(){
preformMeasure(...) --------- View.measure(...);
performLayout(...) --------- View.layout(...);
performDraw(...) -------- View.draw(...);
}
2、 MeasureSpec
二、View 的工作流程
1、measure 过程
.onMeasure(int widthMeasureSpec, int heightMeasureSpec)
该方法就是我们只自定义 View 的时候需要实现测量绘制逻辑的方法,该方法的参数是父视图对子视图的 widht 和height 测量方法的要求。在自定义 View 时,需要做的就是更加 widthMeasureSpec 和 heightMeasureSpec 计算View 的width 和 height ,不同的处理模式不同。
.setMeasuredDimension(int measuredWidth, int measureHeith)
测量阶段的终极方法,在 onMeasure(int widthMeasureSpec, int heightMeasureSpece) 方法中调用,将计算的得到尺寸传递给该方法,测量阶段结束。该方法必须调用,否则会报异常。在自定义 View 的时候,不需要关系系统复杂的 Measure 过程,只需调用setMeasuredDimension(int measuredWith, int measuredHeith) 设置根据 MeasureSpec计算得到的尺寸即可。
(2)measure 过程
Measure 过程传递尺寸的两个参数
ViewGroup.LayoutParams View 自身的布局参数;
MeasureSpec 类, 父视图对子视图的测量要求。
View 的 measure 过程
View 的 getDefaultSize 方法
// 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;
}
从 getDefaultSize 方法可以看出, View 的宽高是由 specSize 决定的。
直接继承 View 的自定义控件需要重写 onMeasure(...) 方法并设置 wrap_content 时的自身大小,否则在布局中使用 wrap_content 相当于使用 match_parent. 从 MeasureSpece 和 LayoutParams 关系表格中可看出。
解决方法,给 View 指定一个默认的内部宽高(mWith 和 mHeight),并在 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 的 measure 过程.
ViewGroup 除了完成自己的 measure 过程以为,还会遍历去调用所有子元素的 measure 方法。 ViewGroup 是一个抽象类,没有重写 View 的 onMeasure 方法。ViewGroup 也没有定义其测量的具体过程,其测量过程的 onMeasure 方法需要各个之类去实现。
measure 完成以后,可以通过 getMeasureWidth / getMeasureHeight 获取 View 的测量宽高, 要在 onLayout 方法中去获取 View 的测量宽高或者最终宽高。
因为 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、注意事项