View的工作原理(Android开发艺术探索读书笔记)

初识ViewRoot和DecorView

ViewRoot对应ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot类完成的,在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。

View的绘制流程是从ViewRoot的performTraversals方法开始。

这里写图片描述

理解MeasureSpec
MeasureSpec(测量规格)是一个32位int值,高2位代表SpecMode,低30位代表SpecSize。

SpecMode有以下三类,每一个类都表示特殊的含义。
UNSPECIFIED
父容器不对 View有任何限制,要多大就给多大,这种情况一般用于系统内部,表示一种测量的状态。

EXACTLY
父容器已经检测出View的所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。 它对应于LayoutParams中的match_parent和具体数值这两种模式。

AT_MOST
父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中的wrap_content。

对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定;对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同确定。

这里写图片描述

View的工作流程

1.Measure流程
View 的measure过程
View.measure–>View.onMeasure

protected void onMeasure(int widthMeasureSpec , int heightMeasureSpec){
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec));
}

(具体分析得多看书上源码分析,一定要看啊!)

*注意:直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于match_parent.可以结合specMode 和specSize作相应分析。
解决办法如下:*

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

在上面代码中,我们只需要给View指定一个默认的内部宽/高(mWidth和mHeight),并在wrap_content时设置此宽/高即可,对于非wrap_content情形,我们沿用系统的测量值即可,至于这个默认的内部宽/高的大小如何指定,这个没有固定的依据,根据需要灵活指定即可。 如果查看TextView,ImageView等的源码就可以知道,针对wrap_content情形,它们的onMeasure方法均做了特殊处理。

ViewGroup的measure过程

ViewGroup.measureChildren–>ViewGroup.measureChild
–>View.measure
onMeasure是一个抽象方法,各种ViewGroup测量细节和逻辑在onMeasure中具体体现,大家可以参考某些ViewGroup源码。

获取某个View的宽/高的几种方法
(1)Activity/View.onWindowFocusChanged

onWindowFocusChanged这个方法的含义是:View已经初始化完毕了,宽/高已经准备好了,这个时候去获取宽/高是没问题的。注意这个回调由于焦点的得到和失去会被频繁调用。

public void onWindowFocusChanged(boolean hasFocus){
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus){
        int width = view.getMeasuredWidth();
        int height = view.getMeasuredHeight();
    }
}

(2)view.post(runnable)
通过post可以将一个runnable投递到消息队列的尾部,然后的等待Looper调用此runnable的时候,View也已经初始化好了。

protected void onStart(){
    super.onStart();
    view.post(new Runnable(){
        @Override
        public void run(){
            int width = view.getMeasuredWidth();
            int height = view.getMeasuredHeight();
        }
    });
}

(3)ViewTreeObserver
ViewTreeObserver的众多回调可以完成这个功能,不如使用OnGlobalLayoutListener这个接口当View树的状态发生改变或者View树内部的View的可见性发现改变时,onGlobalLayout方法会被回调,因此这是获取View的宽/高一个很好的时机。需要注意的是,伴随着View树的状态改变等,onGlobalLayou会被调用多次。

protected void onStart(){
    super.onStart();
    ViewTreeObserver observer = view.getViewTreeObserver();
    observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener(){
        @SuppressWarning("deprecation")
        @Override
        public void onGlobalLayout(){
            view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            int width = view.getMeasuredWidth();
            int height = view.getMeasuredHeight();
        }
    });
}

(4)view.measure(int widthMeasureSpec,int heightMeasureSpec)
通过手动进行View的measure来提前获取宽高,根据View的LayoutParams来分:
match_parent(直接放弃 ,因为构造此MeasureSpec需要根据parentSize(父容器剩余空间))
具体的数值(dp/px)
比如宽/高都是100px,如下measure

int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec,heightMeasureSpec);

wrap_content

int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30)-1,MeasureSpec.AT_MOST);
int heightMeasureSpec= MeasureSpec.makeMeasureSpec((1 << 30)-1,MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec,heightMeasureSpec);

2.Layout过程
具体过程参照书本上代码分析

getMeasuredWidth/getMeasuredHeight
getWidth/getHeight
注意:在View的默认实现中,View的测量宽/高和最终宽/高是相等的,只不过测量宽/高形成于View的measure过程,而最终的宽/高形成于View的layout过程,即两者的赋值时机不同,测量宽/高的赋值时机稍微早一些。

3.Draw过程
dispatchDraw –>draw –>onDraw
(1)绘制背景 (background.draw(canvas))
(2)绘制自己 (onDraw)
(3)绘制children (dispatchDraw)
(4)绘制装饰 (onDrawScrollBars)

注意:setWillNotDraw这个方法的作用是:如果一个View不需要绘制任何内容,那么设置整个标记位为true以后,系统会进行相应的优化。默认情况下,View没有启用整个优化标记位,但是ViewGroup会默认启动这个优化标记位。所以,当我梦自定义控件继承于ViewGroup并且不具备绘制的功能时 ,可以开启这个标记位 从而便于系统进行的后续的优化。

自定义View
注意几点:
1.让View 支持wrap_content,找好默认的宽高。
2.如果有必要,让你的View 支持padding
3.尽量不要在view中使用handler,没必要,因为View内部本身有一系列的post方法
4.View 中如果有线程或者动画,需要及时停止,View.onDetachedFromWindow()是个很好的时机。
5.View带有滑动嵌套的情形,需要处理好滑动冲突。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值