Android UI绘制流程及原理详解
一、绘制流程源码路径
1、Activity加载ViewRootImpl
ActivityThread.handleResumeActivity() -->WindowManagerImpl.addView(decorView,layoutParams) -->WindowManagerGlobal.addView()
2、ViewRootImpl启动View树的遍历
ViewRootImpl.setView(decorView,layoutParams,parentView) -->ViewRootImpl.requestLayout() -->scheduleTraversals() -->TraversalRunnable.run() -->doTraversal() -->performTraversals()(performMeasure、performLayout、performDraw)
二、View绘制流程
1、measure
(1)MeasureSpec是什么?
重写过onMeasure()方法都知道,测量需要用到MeasureSpec类获取View的测量模式和大小,那么这个类是怎样存储这两个信息呢?
留心观察的话会发现,onMeasure方法的两个参数实际是32位int类型数据,即:
00000000000000000000000000000000
而其结构为mode+size,前2位为mode,而后30位为size。
==>getMode()方法(measureSpec-->mode):
privatestaticfinalintMODE_SHIFT=30; //0x3转换为二进制即为:11 //左移30位后:11000000000000000000000000000000 privatestaticfinalintMODE_MASK=0x3<getSize()方法同理。
==>makeMeasureSpec()方法(mode+size-->measureSpec):
publicstaticintmakeMeasureSpec( @IntRange(from=0, to=(1<这里解释一下,按位或左侧为size的高2位清零后的结果,右侧为mode的低30位清零后的结果,两者按位或运算的结果正好为高2位mode、低30位size,例:
01000000000000000000000000000000| 00001000000010111111010110101101= 01001000000010111111010110101101二进制计算规则可参考:
https://www.nhooo.com/article/166892.htm ==>测量模式:
publicstaticfinalintUNSPECIFIED=0<UNSPECIFIED:父容器不对View作任何限制,系统内部使用。
EXACTLY:精确模式,父容器检测出View大小,即为SpecSize;对应LayoutParams中的match_parent和指定大小的情况。
AT_MOST:最大模式,父容器指定可用大小,View的大小不能超出这个值;对应wrap_content。
(2)ViewGroup的测量流程
回到ViewRootImpl的performMeasure方法,这里传入的参数为顶层DecorView的测量规格,其测量方式为:
privatestaticintgetRootMeasureSpec(intwindowSize,introotDimension){ intmeasureSpec; switch(rootDimension){ caseViewGroup.LayoutParams.MATCH_PARENT: measureSpec=MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY); break; caseViewGroup.LayoutParams.WRAP_CONTENT: measureSpec=MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.AT_MOST); break; default: measureSpec=MeasureSpec.makeMeasureSpec(rootDimension,MeasureSpec.EXACTLY); break; } returnmeasureSpec; }match_parent和具体数值大小为EXACTLY模式,wrap_content则为AT_MOST模式。
往下走,performMeasure方法中调用了DecorView的onMeasure方法,而DecorView继承自FrameLayout,可以看到FL的onMeasure方法中调用了measureChildWithMargins方法,并传入自身的测量规格:
protectedvoidmeasureChildWithMargins(Viewchild, intparentWidthMeasureSpec,intwidthUsed, intparentHeightMeasureSpec,intheightUsed){ finalMarginLayoutParamslp=(MarginLayoutParams)child.getLayoutParams(); finalintchildWidthMeasureSpec=getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft+mPaddingRight+lp.leftMargin+lp.rightMargin +widthUsed,lp.width); finalintchildHeightMeasureSpec=getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop+mPaddingBottom+lp.topMargin+lp.bottomMargin +heightUsed,lp.height); child.measure(childWidthMeasureSpec,childHeightMeasureSpec); }即测量子控件的大小,测量规则详情可看getChildMeasureSpec方法,总结如下:
childLayoutParams\parentSpecMode EXACTLY AT_MOST UNSPECIFIED dp EXACTLY/childSize EXACTLY/childSize EXCATLY/childSize match_parent EXACTLY/parentSize AT_MOST/parentSize UNSPECIFIED/0 wrap_content AT_MOST/parentSize AT_MOST/parentSize UNSPECIFIED/0 回到onMeasure方法,测完子控件之后,ViewGroup会经过一些计算,得出自身大小:
//加上padding maxWidth+=getPaddingLeftWithForeground()+getPaddingRightWithForeground(); maxHeight+=getPaddingTopWithForeground()+getPaddingBottomWithForeground(); //检查是否小于最小宽度、最小高度 maxHeight=Math.max(maxHeight,getSuggestedMinimumHeight()); maxWidth=Math.max(maxWidth,getSuggestedMinimumWidth()); //检查Drawable的最小高度和宽度 finalDrawabledrawable=getForeground(); if(drawable!=null){ maxHeight=Math.max(maxHeight,drawable.getMinimumHeight()); maxWidth=Math.max(maxWidth,drawable.getMinimumWidth()); } setMeasuredDimension(resolveSizeAndState(maxWidth,widthMeasureSpec,childState), resolveSizeAndState(maxHeight,heightMeasureSpec, childState<综上,ViewGroup的测量需要先测量子View的大小,而后结合padding等属性计算得出自身大小。
(3)View的测量流程
View.performMeasure() -->onMeasure(intwidthMeasureSpec,intheightMeasureSpec) -->setMeasuredDimension(intmeasuredWidth,intmeasuredHeight) -->setMeasuredDimensionRaw(intmeasuredWidth,intmeasuredHeight)可以看到setMeasuredDimensionRaw()方法:
privatevoidsetMeasuredDimensionRaw(intmeasuredWidth,intmeasuredHeight){ //存储测量结果 mMeasuredWidth=measuredWidth; mMeasuredHeight=measuredHeight; //设置测量完成的标志位 mPrivateFlags|=PFLAG_MEASURED_DIMENSION_SET; }View不需要考虑子View的大小,根据内容测量得出自身大小即可。
另外,View中的onMeasure方法中调用到getDefaultSize方法:
protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){ setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec)); } publicstaticintgetDefaultSize(intsize,intmeasureSpec){ intresult=size; intspecMode=MeasureSpec.getMode(measureSpec); intspecSize=MeasureSpec.getSize(measureSpec); switch(specMode){ caseMeasureSpec.UNSPECIFIED: result=size; break; caseMeasureSpec.AT_MOST: caseMeasureSpec.EXACTLY: //最终测量的结果都是父容器的大小 result=specSize; break; } returnresult; }这里看到精确模式和最大模式,最终测量的结果都是父容器的大小,即布局中的wrap_content、match_parent以及数值大小效果都一样,这也就是自定义View一定要重写onMeasure方法的原因。
2、layout
布局相对测量而言要简单许多,从ViewRootImpl的performLayout方法出发,可以看到其中调用了DecorView的layout方法:
//实则为DecorView的left,top,right,bottom四个信息 host.layout(0,0,host.getMeasuredWidth(),host.getMeasuredHeight());进入layout方法,发现l、t、r、b被传递到了setFrame方法中,并设置给了成员变量:
mLeft=left; mTop=top; mRight=right; mBottom=bottom;所以,布局实际为调用View的layout方法,设置自身的l、t、r、b值。另外,layout方法中往下走,可以看到调用了onLayout方法,进入后发现为空方法。因而查看FrameLayout的onLayout方法:
@Override protectedvoidonLayout(booleanchanged,intleft,inttop,intright,intbottom){ layoutChildren(left,top,right,bottom,false/*noforceleftgravity*/); } voidlayoutChildren(intleft,inttop,intright,intbottom,booleanforceLeftGravity){ finalintcount=getChildCount(); //省略 for(inti=0;i可以看到,进行一系列计算后,调用了child的layout方法,对子控件进行布局,同时子控件又会继续往下对自己的子控件布局,从而实现遍历。
综上,布局实际为调用layout方法设置View位置,ViewGroup则需要另外实现onLayout方法摆放子控件。
3、draw
(1)绘制过程入口
ViewRootImpl.performDraw() -->ViewRootImpl.draw() -->ViewRootImpl.drawSoftware() -->View.draw()(2)绘制步骤
进入到View的draw方法中,可以看到以下一段注释:
/* *Drawtraversalperformsseveraldrawingstepswhichmustbeexecuted *intheappropriateorder: * *1.Drawthebackground *2.Ifnecessary,savethecanvas'layerstoprepareforfading *3.Drawview'scontent *4.Drawchildren *5.Ifnecessary,drawthefadingedgesandrestorelayers *6.Drawdecorations(scrollbarsforinstance) */结合draw方法的源码,绘制过程的关键步骤如下:
- ==>绘制背景:drawBackground(canvas)
- ==>绘制自己:onDraw(canvas)
- ==>绘制子view:dispatchDraw(canvas)
- ==>绘制滚动条、前景等装饰:onDrawForeground(canvas)
感谢大家的阅读和对毛票票的支持。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。