初识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带有滑动嵌套的情形,需要处理好滑动冲突。

更多相关文章

  1. Android(安卓)触摸事件的传递过程
  2. View视图绘制流程,View工作原理(二)
  3. Android(安卓)ORM数据库框架之-greenDao(四)
  4. [置顶] android6.0源码分析之Activity启动过程
  5. android通讯录之联系人
  6. Android绘制(二):来用Path绘出想要的图形吧!
  7. 菜鸟在android中密码框的纠结过程
  8. Opengl ES系列学习--glDrawArrays API使用
  9. 【笔记】Android(安卓)App 运行的过程

随机推荐

  1. 美团,大众点评,悬浮窗功能代码
  2. Android(安卓)工程编译 Unsupported majo
  3. android配置信息类-Configuration
  4. 一种动态水平ProgressBar的实现
  5. Android知识点及资料汇总(不断更新中)
  6. 解决eclipse ADT编译NDK报NDK和minSdkVer
  7. android下使用Fragment实现左侧3级菜单+
  8. android的系统存储与软件安装
  9. 重复导入联系人
  10. Android make sdk出错问题的解决