View的工作原理

ViewRoot和DecorView

在正式了解View的三大流程(measure,layout,draw)之前,我们先认识以下ViewRoot和DecorView

ViewRoot对应于ViewRootImpl类,它是连接WindowManager与DecorView是纽带,View的三大流程都是通过ViewRootImpl来完成的。在ActivityThread中,当Activity被创建的时,会将DecorView添加到Window中,同时创建一个ViewRootImpl与其关联

View的绘制流程是从ViewRoot的performTraversals方法开始的,经过measure(测量宽高),layout(布局位置),draw(内容绘制)

PerformTraversals方法会依次调用performMeasure,performLayout与performDraw方法,这三个方法分别完成顶级View的measure,layout,draw过程。其中performMeasure会调用measure方法完成顶级View自身的测量过程,紧接着调用onMeasure方法对所有子元素进行测量,接着子元素重新measure过程,如此反复完成整个view树的遍历。同理performLayout与performDraw也是同样的过程,只不过performDraw的传递过程是在draw中调用dispatchDraw方法,但是本质上都是一样的。


Measure过程决定了View的测量宽高,完成后可以通过getMeasureWidth/Height获得测量宽高,测量宽高一般与最终宽高一致,但是也有例外情况

Layout过程决定了View的四个顶点的位置以及最终宽高,完成后可以通过getLeft/Right/Top/Bottom获取四个顶点坐标,可通过getWidth/Height获取View的最终宽高

Draw过程是对View内容的绘制,在draw完成后View的内容才会最终显示是屏幕上


DecorView是顶级View(继承自FrameLayout),一般情况下它内部包含一个LinearLayout里面分为上下两部分(具体与Android版本与主题有关),上部分为标题栏,下部分为内容栏(FrameLayout)。SetContentView就是将布局添加到内容栏中,其id就是android.R.id.content。那么我们可以通过如下代码获取ContentView

ViewGroup décor = getWindow().getDecorView(); // get décor

View content = décor.findViewById(android.R.id); // get content


理解MeasureSpec

MeasureSpec是View进行测量过程的“测量规格”。它里面主要存储32位的int值,高2位代表测量模式,低30位代表测量大小。

View$MeasureSpec#makeMeasureSpec/getMode/getSize

MeasureSpec通过将SpecMode与SpecSize打包成一个int值来避免过多的内存消耗,并且提供了打包与解包的方法

打包:makeMeasureSpec

解包:getMode与getSize

SpecMode分为三类:

1. UNSPECIFIED

表示父容器不对View有任何限制

2. EXACTLY

精确值模式,表示View使用具体宽高

3. AT_MOST

最大模式,表示View可以根据自身需求设定宽高,但是不可超过当前的可用值

MeasureSpec与LayoutParams的关系

MeasureSpec是用于定义对View的测量规范的,而View的宽高属性定义在LayoutParams中。那么在测量时系统会将LayoutParams中的相关属性在父容器的约束下转换成对应的MeasureSpec(即View的MeasureSpec不是由LayoutParams单独决定的,是由LayoutParams与父容器的MeasureSpec共同决定)。

而DecorView的MeasureSpec是由窗口尺寸与自身LayoutParams决定

一旦MeasureSpec确定后,在onMeasure就可以确定View的测量宽高

ViewRootImpl# measureHierarchy_1214


LayoutParams中宽高参数与SpecMode

MATCH_PARENT:精确模式,大小为父容器可用大小

WRAP_CONTENT:最大模式,不可大于父容器可用大小

固定大小:精确模式,大小为属性指定的value值


源码分析:ViewGroup到View的Measure过程

控件的测量主要两个情况,如果只是一个原始View,那么直接measure过程完成测量,如果是ViewGroup除了自身测量外还会执行onMeasure遍历所有子View的measure过程

View的measure过程

View的measure过程是由其measure方法完成,measure是final方法,意味着子类无法重写,在measure方法中会调用View的onMeasure方法

View#onMeasure

getDefaultSize方法获取默认大小

getSuggestedMinimumWidth方法获取建议的最小大小


ViewGroup并没有覆写View的onMeasure方法,它只是抽象的规范,这需要在具体子类中根据自身规则完成ViewGroup自身的测量

ViewGroup#measureChildren

ViewGroup#measureChild

ViewGroup #getChildMeasureSpec


例如垂直的LinearLayout会在onMeasure中遍历所有元素,依次调用子元素的measure方法,并且通过mTotalLength存储在垂直方向的所有子元素占据的高度(子元素高度(包括padding) + margin )

总高度 = 子元素总高度 + 自身padding

测量完子元素后根据自身lp与子元素总高度决定自身的测量


获取控件测量宽高的时机

由于View的测量过程和Activity的生命周期是不一致的,不是同步方式执行的。即我们无法在Activity中某个生命周期时获取View的测量宽高(因为此时View可能还没有测量结束)

方式1.Activity/View#WindowFocusChanged

当该方法触发时候证明View已经初始化完毕了,这个时候就可以去获取宽高

@Overridepublic void onWindowFocusChanged(booleanhasFocus) {

super.onWindowFocusChanged(hasFocus);

if(hasFocus && mTextView!=null){

Toast.makeText(this,"mTextView.getMeasuredHeight():"+ mTextView.getMeasuredHeight(), Toast.LENGTH_SHORT).show();

}

}

方式2.view.post(runnable)

通过post将一个runnable对象投递到消息队列的尾部,等待Looper执行(此时View已经完成初始化)

mTextView.post(newRunnable() {

@Override    public voidrun() {

mWidth mTextView.getMeasuredWidth(); }});

方式3.ViewTreeObserver

使用ViewTreeObserver的众多回调均可以实现该功能,比如使用OnGlobalLayoutListener接口,当View树的状态发生改变或者View树内部的View的可见性发现改变,onGlobalLayout会被回调(多次)

ViewTreeObserver observer = mTextView.getViewTreeObserver();

observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

@TargetApi(Build.VERSION_CODES.JELLY_BEAN)

@Override    public voidonGlobalLayout() {

mTextView.getViewTreeObserver().removeOnGlobalLayoutListener(this);

mWidth mTextView.getMeasuredWidth();

}

});

方式4.view.measure(int widthSpec,heightSpec);

这种情况需要根据LayoutParams分情况处理

match_parent

无法使用该方式,因为此时无法知道父容器剩余空间

具体的数值

int widthSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);

int heightSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);

mTextView.measure(widthSpec,heightSpec);

wrap_content

int widthSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);

int heightSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);

mTextView.measure(widthSpec,heightSpec);

View的尺寸大小是由30位二进制表示,那么最大即为2^30-1


注意:View的measure与onMeasure以及layout与onLayout方法在ViewGroup中均没有被覆写,因为这四个方法只是定义了一个流程,而ViewGroup只是布局控件的统一父类,只有在具体的ViewGroup中才会去覆写onMeasure与onLayout方法,比如LinearLayout

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342