View工作原理(measure、layout、draw)

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);

SpecMode:测量模式

(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开发艺术探索笔记整理



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值