View工作流程图:
一、ViewRoot和DecorView
1、ViewRoot:
(1)ViewRoot对应的是ViewRootImpl,是连接WindowManager和DecorView的纽带,view的三大流程都是viewRoot完成的。
(2)在ActivityThread中,Activity创建完毕,将DecorView添加到window中。同时会创建 ViewRootImpl 对象,并将 ViewRootImpl 对象与 DecorView 进行关联。
(3)整个 View 的绘制流程,是从 ViewRoot 的 performTraversals 方法开始的。 会依次调用 performMeasure、performLayout、performDraw 这三个方法,这三个方法会依次完成顶级 View 的 measure、layout、draw 三大流程。
流程图:
2、DecorView:
DecorView是顶级View,继承于FrameLayout。View 的所有事件,都先经过 DecorView,然后再传递给 View。
DecorView包含一个LinearLayout,上面是标题栏,下面是内容栏content。setContentView就是将布局文件加载到content中。
二 、measure过程:测量,决定了View的宽高。
Measure完成后,可通过getMessuredWidth和getMessuredHeight获取View测量后的宽高。
流程图:
1、MeasureSpec:测量规格(测量模式和规格大小)
MeasureSepc代表一个32位int值。高2位代表SpecMode,测量模式。低30位代表SepcSize,某种测量模式下的规格大小。
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
(1)UNSPECIFIED
父容器不没有对子View施加任何约束,子可以是任意大小(也就是未指定)
(UNSPECIFIED在源码中的处理和EXACTLY一样。当View的宽高值设置为0的时候或者没有设置宽高时,模式为UNSPECIFIED
(2)EXACTLY:精确值模式
父已经测量出子view所需要的确切大小,view的最终大小就是SpecSize所指定的值。
(对应LayoutParams中得match_parent和具体的数值两种模式,match_parent因为子view会占据剩余容器的空间,所以它大小是确定的)
(3)AT_MOST: 最大值模式
父指定一个可用大小即SpecSize,View不能大于这个值
(对应LayoutParams中的wrap_content)
2、MeasureSpec和LayoutParams的对应关系:
(1)LayoutParams是子元素用来用于告诉父元素它(子元素)想怎么摆放。
系统通过LayoutParams和父容器一起决定View的MeasureSpec,进而决定View的宽高。
(2)DecorView是由窗口的尺寸和自身的LayoutParams决定MeassureSpec
(3)一般的View是由由父控件的MeasureSpec和LayoutParams决定子View的MeasureSpec。一旦确定,onMeasure可得到View的宽高。
源码:对子元素进行measure
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
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);
}
注:
先通过getChildMeasureSpec得到子元素的MeasureSepc。子元素MeasureSepc创建与父控件的MeasureSepc和子元素本身的
LayoutParams有关。
3、View的measure的过程:
是由其measure方法完成,内部实现是调用onMeasure方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
注:
setMeasuredDimension(int measureWidth,int measureHeight):用来设置View宽/高的测量值
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宽高由specSize决定。直接继承View的自定义控件要重写onMeasure,设置wrap_content。默认相当于match_parent.
4、ViewGroup的measure过程
完成自身的measure和其所有子view的measure方法。ViewGroup没有onMeasure方法,提供一个measureChildren方法。
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
调用measureChild()方法测量单个视图
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
注:获取子元素的LayoutParams,通过getChildMeasureSpec创建子元素的MeasureSepc。然后将MeasureSepc传给View
measure之后,通过getMeasuredWidth可以获取测试宽度,但是有可能多次measure才能确定最终的宽高,所有最好在onLayout中获取测量宽高。
5、在Activity中获取宽高
无法保证onCreate、onStart、onResume中View已经测试完毕,有可能是0。
(1)onWindowFocusChanged :View初始化完毕,在这个方法获取宽高。会多次执行,得到或失去焦点都会执行
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus){
int height= btn1.getMeasuredHeight();
}
}
(2)view.post(runnable)
Looper调用runnable的时候,View已经初始化好了
btn1.post(new Runnable() {
@Override
public void run() {
int height = btn1.getMeasuredHeight();
}
});
(3)ViewTreeObserver
ViewTreeObserver.OnGlobalLayoutListener :当view的状态发生改变,或内部view可见性发生改变,onGlobalLayout将被回调。
ViewTreeObserver observer = btn1.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
btn1.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int height = btn1.getMeasuredHeight();
}
});
(4)手动对View进行measure获取宽高
match_parent: 无法measure具体宽高。构造MeasureSpec 需要知道父view的剩余空间,因为无法得到,所以无法测出view大小。
具体dp:
int measureheight = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int measureweight = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
btn1.measure(measureweight,measure height);
wrap_content: (1<<30)-1,view理论上能支持的最大值去构造MeasureSpec
int measureheight = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int measureweight = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
btn1.measure(measureweight,measureheight);
三、layout:布局过程,确定所有子元素的位置
layout流程:通过setFrame方法设定View四个顶点的坐标。接着调用onLayout方法确定子元素的位置
布局完成后,可以通过getWidth和getHeight获取View最终的宽高。
流程图:
四、draw:绘制过程
1、绘制背景background.draw(canvas)
2、绘制自己 (onDraw)
3、绘制子view (dispatchDraw)
4、绘制装饰 onDrawScrollBars
流程图:
五、自定义控件:
1、分类:
(1)继承view重写onDraw方法,实现不规则图形
(2)继承ViewGroup派生特殊的Layout(需要处理测量、布局的过程)
(3)继承特定的view组件:
(4)继承特定的ViewGroup(组合控件)
2、注意:
(1)让view支持wrap_content
继承于View或者ViewGroup的控件,需要在onMeasure方法中对wrap_content做特殊处理
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize= MeasureSpec.getSize(heightMeasureSpec);
int widthSize= MeasureSpec.getSize(widthMeasureSpec);
if (heightMode==MeasureSpec.AT_MOST&&widthMode==MeasureSpec.AT_MOST){
setMeasuredDimension(500,800);
}else if(heightMode==MeasureSpec.AT_MOST){
setMeasuredDimension(400,heightSize);
}else if (widthMode==MeasureSpec.AT_MOST){
setMeasuredDimension(widthSize,800);
}
}
(2)让view支持padding
在自定义 View 时,如果是直接继承自 View ,需要在 onDraw 方法中处理 padding 。如果是直接继承自 ViewGrop 需要在 onMeasure 和 onLayout 中考虑 padding 和 margin 对其造成的影响。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int paddingBottom = getPaddingBottom();
int paddingTop = getPaddingTop();
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
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,paint);
}
(3)view嵌套,处理好滑动冲突。
(4)View 本身内部提供了一些列的 post 方法,完全可以替代 Handler 作用。
(5)View 中如果有线程或者动画需要在特定生命周期进行停止
当包含此 View 的 Activity 退出或者当前 View 被 remove 掉时,View 的 onDetachedFromWindow() 方法会被调用,所以如果有需要停止的线程或者动画可以在这个方法中执行,和此方法相对应的是 onAttachedToWindow() 方法,当包含该 View 的 Activity 启动的时候,该方法就会被调用。同时当 View 变得不可见时,我们需要及时停止线程和动画,否则可能造成内存泄露。
六、自定义控件使用:
1、自定义属性:attrs.xml文件
<resources>
<declare-styleable name="MyView">
<attr name="view_color" format="color"/> //颜色
<attr name="view_r" format="dimension"/> //半径大小
</declare-styleable>
</resources>
2、布局文件中引入
(1)加入命名空间(2)使用自定义属性
xmlns:myview="http://schemas.android.com/apk/res-auto
<com.example.hejian.demo2.view.MyView
android:id="@+id/id_view"
android:layout_width="wrap_content"
android:layout_height="300dp"
android:background="@color/color_5ecfd3"
myview:view_color="@color/color_626262"
myview:view_r="50dp"</span>
android:layout_marginTop="30dp"
android:layout_marginLeft="40dp"
android:paddingTop="40dp"
></com.example.hejian.demo2.view.MyView>
3、自定义控件
(1)继承View
(2)重写onMeasure(需要处理wrap_content的情况)
(3)重写onDraw(需要处理padding、margin的边距)
public class MyView extends View {
private int myColor;
private int r;
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
//属性集合,第二个参数为attrs.xml文件下<declare-styleable name="MyView">
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyView);
//获取到颜色的属性,第二个参数为颜色默认值
myColor = array.getColor(R.styleable.MyView_view_color, Color.parseColor("#29a6b6"));
r= array.getDimensionPixelSize(R.styleable.MyView_view_r,10);
//将TypedArray回收
array.recycle();
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int size = 400;
if (heightMode==MeasureSpec.AT_MOST&&widthMode==MeasureSpec.AT_MOST){
setMeasuredDimension(size,size);
}else if(heightMode==MeasureSpec.AT_MOST){
setMeasuredDimension(widthSize,size);
}else if (widthMode==MeasureSpec.AT_MOST){
setMeasuredDimension(size,heightSize);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int centX = getLeft()+r;//左边的距离
int centY = getTop()+getPaddingTop()+r;//上面的距离
Paint paint = new Paint();
paint.setColor(myColor);
//开始绘制
canvas.drawCircle(centX, centY, r, paint);
}
}
注:Android开发艺术探索笔记整理