View的绘制流程(三):ViewRootImpl.performTraversals()方法

引言

        上期View的绘制流程(二):从Activity的生命周期到显示页面讲到在WindowManagerGlobal.addView()调用了ViewRootImpl.setView()方法,然后在ViewRootImpl的setView()方法中调用requestLayout(),然后调用performTraversals()进入View的测量、布局、绘制流程。这期将从performTraversals(),详细分析performTraversals()中的三个重要的方法 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec) 、performLayout(lp, mWidth, mHeight) 、performDraw()。

所以,一个完整的绘制流程包括measure、layout、draw三个步骤,其中:

measure:测量。系统会先根据xml布局文件和代码中对控件属性的设置,来获取或者计算出每个View和ViewGrop的尺寸,并将这些尺寸保存下来

layout:布局。根据测量出的结果以及对应的参数,来确定每一个控件应该显示的位置

draw:绘制。确定好位置后,就将这些控件绘制到屏幕上

每个View负责绘制自己,而ViewGroup还需要负责通知自己的子View进行绘制操作

Measure — 测量

MeasureSpec

从上面的源码我们可以发现,系统会用一个int型的值(childWidthMeasureSpec和childHeightMeasureSpec)来存储View的宽高的信息

上文中用于生成childWidthMeasureSpec和childHeightMeasureSpec的getRootMeasureSpec()方法

这个方法实际上调用的就是MeasureSpec.makeMeasureSpec()

那么这个MeasureSpec到底是什么意思呢?MeasureSpec概括了从父布局传递给子view的布局要求,包括了测量模式和测量大小。分析代码可知,int长度为32位,高2位表示mode(模式),后30位用于表示size(大小)

有三种mode:

UNSPECIFIED:不对View大小做限制,如:ListView,ScrollView

EXACTLY:确切的大小,如:100dp或者march_parent

AT_MOST:大小不可超过某数值,如:wrap_content

子View的LayoutParams / 父view的MeasureSpecEXACTLYAT_MOSTUNSPECIFIED

具体大小(如100dp)EXACTLYEXACTLYEXACTLY

match_parentEXACTLYAT_MOSTUNSPECIFIED

wrap_contentAT_MOSTAT_MOSTUNSPECIFIED

View采用固定宽高时(即设置固定的dp/px),不管父容器是什么模式,View都是EXACTLY模式,并且大小遵循我们设置的值

View的宽高是match_parent时,如果父容器的是EXACTLY模式,那么View也是EXACTLY模式且其大小是父容器的剩余空间;如果父容器是AT_MOST模式那么View也是AT_MOST模式并且其大小不会超过父容器的剩余空间

View的宽高是wrap_content时,View都是AT_MOST模式并且其大小不能超过父容器的剩余空间

只要提供父容器的MeasureSpec和子元素的LayoutParams,就可以确定出子元素的MeasureSpec,进一步便可以确定出测量后的大小

onMeasure

mView.measure()内部会调用onMeasure()

一个View的实际测量工作是在onMeasure()中实现的,onMeasure()已经默认为我们的控件测量了宽高

在自定义ViewGroup时,默认的onMeasure()往往不能满足我们的需求,这时候就要重写该方法,在该方法内测量子View的尺寸。当重写onMeasure()时,必须调用setMeasuredDimension(width,height)来存储该View测量出的宽和高。如果不这样做将会触发IllegalStateException

ViewGroup提供了三个方法测量子View的宽高

measureChildren(intwidthMeasureSpec,intheightMeasureSpec)

measureChild(Viewchild,intparentWidthMeasureSpec)

protectedvoidmeasureChildWithMargins(Viewchild,..)

View和ViewGroup重写onMeasure的差异

下面用两个例子分别来展示一下View和ViewGroup重写onMeasure的差异

View

View一般只关心自身尺寸的测量

ViewGroup

ViewGroup一般会先遍历子View,调用子View的测量方法,然后在再结合子View的尺寸来确定自身的大小

Layout - 布局

前面measure的作用是测量每个View的尺寸,而layout的作用是根据前面测量的尺寸以及设置的其它属性值,共同来确定View的位置

ViewGroup的layout()

从源码中可以看出实际上调用的还是View的layout()方法

View的layout()

从源码可以看出layout()最终通过setFrame()方法对view的四个属性(mLeft、mTop、mRight、mBottom)进行了赋值,从而去确定View的大小和位置,如果发生改变则调用onLayout()方法

onLayout

我们先来看看ViewGroup的onLayout()方法,该方法是一个抽象方法。因为layout过程是父布局容器布局子View的过程,onLayout()方法对子View没有意义,只有ViewGroup才有用,所以ViewGroup应该重写该方法并为每一个子View调用layout()

protectedabstractvoidonLayout(booleanchanged,intl,intt,intr,intb);

我们再来看看顶层ViewGroup,也就是DecorView的onLayout()方法。DecerView继承自FrameLayout,所以我们直接看FrameLayout的onLayout()方法

@OverrideprotectedvoidonLayout(booleanchanged,intleft,inttop,intright,intbottom){layoutChildren(left,top,right,bottom,false/* no force left gravity */);}voidlayoutChildren(intleft,inttop,intright,intbottom,booleanforceLeftGravity){finalintcount=getChildCount();......for(inti=0;i<count;i++){finalViewchild=getChildAt(i);if(child.getVisibility()!=GONE){finalLayoutParamslp=(LayoutParams)child.getLayoutParams();finalintwidth=child.getMeasuredWidth();finalintheight=child.getMeasuredHeight();......child.layout(childLeft,childTop,childLeft+width,childTop+height);}}

我们可以看到,这里面会对每一个child调用layout()方法。如果该child仍然是ViewGroup,会继续递归下去;如果是叶子View,则会走到View的onLayout空方法,该叶子View布局流程就走完了。另外,width和height分别来源于measure阶段存储的测量值

Draw - 绘制

当layout完成后,就进入到draw阶段了,在这个阶段,会根据layout中确定的各个view的位置将它们画出来

前面说过,mView就是DecorView,所以我们直接来看DecorView的draw()方法

@Overridepublicvoiddraw(Canvascanvas){super.draw(canvas);if(mMenuBackground!=null){mMenuBackground.draw(canvas);}}

调用完super.draw()后,还画了菜单背景。我们继续关注super.draw()方法,会发现FrameLayout和ViewGroup都没有重写该方法,直接进到了View的draw()方法

@CallSuperpublicvoiddraw(Canvascanvas){......intsaveCount;// Step 1, draw the background, if neededif(!dirtyOpaque){drawBackground(canvas);}// Step 2, If necessary, save the canvas' layers to prepare for fading......// Step 3, draw the contentif(!dirtyOpaque)onDraw(canvas);// Step 4, draw the childrendispatchDraw(canvas);//Step 5, If necessary, draw the fading edges and restore layers......// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);45......}

主要是就是如下几步,其中最重要的就是画内容画子View

画背景。对应我我们在xml布局文件中设置的android:background属性

画内容。通过重写onDraw()方法

画子View。dispatchDraw()方法用于帮助ViewGroup来递归画它的子View

画装饰。这里指画滚动条和前景。其实平时的每一个View都有滚动条,只是没有显示而已

onDraw()

当自定义View需要进行绘制的时候,我们往往会重写onDraw()方法,这里放一个简单的例子感受一下

PaintmPaint=newPaint(Paint.ANTI_ALIAS_FLAG);@OverrideprotectedvoidonDraw(Canvascanvas){mPaint.setColor(Color.YELLOW);canvas.drawRect(0,0,getWidth(),getHeight(),mPaint);mPaint.setColor(Color.BLUE);mPaint.setTextSize(20);Stringtext="Hello View";canvas.drawText(text,0,getHeight()/2,mPaint);}

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

推荐阅读更多精彩内容