##ViewRoot,WindowManager 和 DecorView 的关系
ViewRoot 对应于 ViewRootImpl 类,它是连接 WindowManager 和 DecorView 的纽带,View 的三大流程是通过 ViewRoot 来完成的;
##View 的绘制过程
performTraversals ->
performMeasure -> measure -> onMeasure ->
performLayout -> layout -> onLayout ->
performDraw -> draw -> onDraw
在没有重写的情况下,上述加粗方法的作用:
\ | View | ViewGroup |
---|---|---|
measure | final,测量准备工作 | final,测量准备工作 |
onMeasure | 测量自身,没有处理 wrap_content | 测量自身,没有处理 wrap_content(继承自 View) |
layout | 定位自身 | 定位自身 |
onLayout | 没有实现 | 没有实现 |
而我们需要实现的效果如下:
对于 View:
onMeasure 测量自身(同时处理 wrap_content),layout 定位自身;
对于 ViewGroup:
onMeasure 测量自身(同时处理 wrap_content)及所有子 View,layout 定位自身,onLayout 定位所有子 View;
因此,我们需要…
##自定义 View 需要重写的方法
View 的 onMeasure:需要处理 wrap_content 情况;
ViewGroup 的 onMeasure:需要测量所有子 View 的大小,同时需要处理 wrap_content 情况;
ViewGroup 的 onLayout:需要确定所有子 View 的位置;
(View 的 onLayout 虽然本身没有实现,但是因为 onLayout 负责测量所有子 View 的位置,而 View 没有子 View,因此无需重写)
##何时可以拿到 View 的信息
measure 过程之后,可以拿到测量宽 / 高:
· getMeasuredWidth
· getMeasuredHeight
layout 过程之后,可以拿到顶点位置和最终宽 / 高:
· getTop
· getBottom
· getLeft
· getRight
· getWidth
· getHeight
##MeasureSpec
· 自身大小的一个参考,由 mode(UNSPECIFIED、EXACTLY、AT_MOST)和 size 组成;
· 在 onMeasure 中传进去;
· 由 View 的 LayoutParams 和父容器的 MeasureSpec(SpecMode)共同决定:
childLayoutParams \ parentSpecMode | EXACTLY | AT_MOST | UNSPECIFIED |
---|---|---|---|
dp/px | EXACTLY childSize | EXACTLY childSize | EXACTLY childSize |
match_parent | EXACTLY parentSize | AT_MOST parentSize | UNSPECIFIED 0 |
wrap_content | AT_MOST parentSize | AT_MOST parentSize | UNSPECIFIED 0 |
子 View 的 mode 是 AT_MOST,子 View 要么是 wrap_content,要么是 match_parent 的第二种。
如果是 wrap_content,按照 wrap_content 处理;
如果是 match_parent 的第二种,说明它父 View 的 mode 是 AT_MOST,那么它的父 View 要么是 wrap_content,要么是 match_parent 的第二种,形成一个循环。
最终在 xml 中会形成一种 wrap_content -> match_parent -> match_parent … -> match_parent 的结构,match_parent 可能有一个,也可能有很多个。
这时,一个 wrap_content -> match_parent 结构,后面的 match_parent 可以转换成 wrap_contnet,即上面的结构可以转换成 wrap_contnet -> wrap_content … -> wrap_contnet。
因此,所有 match_parent 的第二种都可以按照 wrap_content 处理。
注意逻辑:a -> b =\= b -> a
##onMeasure 处理 wrap_content 情况
private int mWidth = 200;
private int mHeight = 200;
@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);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, mHeight);
}
}
##关于测量宽 / 高的取值
在 onCreate,onStart 和 onResume 中无法取到测量宽 / 高的值,这是因为 View 的 measure 过程和 Activity 的生命周期不是同步执行的:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("Guu", "onCreate mCustomView: width->" + mCustomView.getWidth()
+ " height->" + mCustomView.getHeight());
}
@Override
protected void onStart() {
super.onStart();
Log.d("Guu", "onStart mCustomView: width->" + mCustomView.getWidth()
+ " height->" + mCustomView.getHeight());
}
@Override
protected void onResume() {
super.onResume();
Log.d("Guu", "onResume mCustomView: width->" + mCustomView.getWidth()
+ " height->" + mCustomView.getHeight());
}
/* 输出结果:
12-24 23:38:21.928 13829-13829/com.example.customview D/Guu: onCreate mCustomView: width->0 height->0
12-24 23:38:21.929 13829-13829/com.example.customview D/Guu: onStart mCustomView: width->0 height->0
12-24 23:38:21.931 13829-13829/com.example.customview D/Guu: onResume mCustomView: width->0 height->0
*/
以下三种方法可以取到测量宽 / 高的值:
// 1. 通过 onWindowFocusChanged
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
Log.d("Guu", "onWindowFocusChanged mCustomView: width->" + mCustomView.getWidth()
+ " height->" + mCustomView.getHeight());
}
}
@Override
protected void onStart() {
super.onStart();
// 2. 通过消息队列
mCustomView.post(new Runnable() {
@Override
public void run() {
Log.d("Guu", "loop mCustomView: width->" + mCustomView.getWidth()
+ " height->" + mCustomView.getHeight());
}
});
// 3. 通过 ViewTreeObserver
ViewTreeObserver observer = mCustomView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@TargetApi(16)
@Override
public void onGlobalLayout() {
mCustomView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
Log.d("Guu", "onGlobalLayout mCustomView: width->" + mCustomView.getWidth()
+ " height->" + mCustomView.getHeight());
}
});
}
/* 输出结果:
12-24 23:38:21.984 13829-13829/com.example.customview D/Guu: onGlobalLayout mCustomView: width->200 height->200
12-24 23:38:22.016 13829-13829/com.example.customview D/Guu: loop mCustomView: width->200 height->200
12-24 23:38:22.016 13829-13829/com.example.customview D/Guu: onWindowFocusChanged mCustomView: width->200 height->200
*/
##九宫格(ViewGroup)示例代码
PS:本例没有在 onMeasure 中处理 wrap_content 情况;
public class NineSquareViewGroup extends ViewGroup {
private static final int PADDING = 100;
private static final int NUM_PER_ROW = 3;
public NineSquareViewGroup(Context context) {
super(context);
}
public NineSquareViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NineSquareViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
LayoutParams lp = child.getLayoutParams();
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
// // 可以自定义子 View 的大小
// int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
// int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
if (child.getVisibility() != GONE) {
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
int left = (childWidth + PADDING) * (i % NUM_PER_ROW) + PADDING;
int top = (childHeight + PADDING) * (i / NUM_PER_ROW) + PADDING;
if (child.getVisibility() != GONE) {
child.layout(left, top, left + childWidth, top + childHeight);
}
}
}
}