2023年7月15日发(作者:)
ANDROID⾃定义视图——onMeasure流程,MeasureSpec详解简介:在⾃定义view的时候,其实很简单,只需要知道3步骤:1.测量——onMeasure():决定View的⼤⼩2.布局——onLayout():决定View在ViewGroup中的位置3.绘制——onDraw():如何绘制这个View。⽽第3步的onDraw系统已经封装的很好了,基本不⽤我们来操⼼,只需要专注到1,2两个步骤就中好了。⽽这篇⽂章就来谈谈第⼀步,也是⼗分关键得⼀步:“测量(Measure)测量(Measure)”Measure():Measure的中⽂意思就是测量。所以它的作⽤就是测量View的⼤⼩。⽽决定View的⼤⼩只需要两个值:宽详细测量值(widthMeasureSpec)和⾼详细测量值(heightMeasureSpec)。也可以把详细测量值理解为视图View想要的⼤⼩说明(想要的未必就是最终⼤⼩)。对于详细测量值(measureSpec)需要两样东西来确定它,那就是⼤⼩(size)和模式(mode)。⽽measureSpec,size,mode他们三个的关系,都封装在View类中的⼀个内部类⾥,名叫MeasureSpecMeasureSpec。MeasureSpec:因为MeasureSpec类很⼩,⽽且设计的很巧妙,所以我贴出了全部的源码并进⾏了详细的标注。(掌握MeasureSpec的机制后会对整个Measure⽅法有更深刻的理解。)/*** MeasureSpec封装了⽗布局传递给⼦布局的布局要求,每个MeasureSpec代表了⼀组宽度和⾼度的要求* MeasureSpec由size和mode组成。* 三种Mode:* IFIED* ⽗不没有对⼦施加任何约束,⼦可以是任意⼤⼩(也就是未指定)* (UNSPECIFIED在源码中的处理和EXACTLY⼀样。当View的宽⾼值设置为0的时候或者没有设置宽⾼时,模式为UNSPECIFIED* Y* ⽗决定⼦的确切⼤⼩,⼦被限定在给定的边界⾥,忽略本⾝想要的⼤⼩。* (当设置width或height为match_parent时,模式为EXACTLY,因为⼦view会占据剩余容器的空间,所以它⼤⼩是确定的)* _MOST* ⼦最⼤可以达到的指定⼤⼩* (当设置为wrap_content时,模式为AT_MOST, 表⽰⼦view的⼤⼩最多是多少,这样⼦view会根据这个上限来设置⾃⼰的尺⼨)** MeasureSpecs使⽤了⼆进制去减少对象的分配。*/publicclassMeasureSpec {// 进位⼤⼩为2的30次⽅(int的⼤⼩为32位,所以进位30位就是要使⽤int的最⾼位和倒数第⼆位也就是32和31位做标志位)privatestaticfinalintMODE_SHIFT =30;// 运算遮罩,0x3为16进制,10进制为3,⼆进制为11。3向左进位30,就是11(11后跟30个0)// (遮罩的作⽤是⽤1标注需要的值,0标注不要的值。因为1与任何数做与运算都得任何数,0与任何数做与运算都得0)privatestaticfinalintMODE_MASK =0x3 << MODE_SHIFT;// 0向左进位30,就是00 (00后跟30个0)publicstaticfinalintUNSPECIFIED =0<< MODE_SHIFT;// 1向左进位30,就是01 (01后跟30个0)publicstaticfinalintEXACTLY =1<< MODE_SHIFT;// 2向左进位30,就是10 (10后跟30个0)publicstaticfinalintAT_MOST =2<< MODE_SHIFT;/*** 根据提供的size和mode得到⼀个详细的测量结果*/// measureSpec = size + mode; (注意:⼆进制的加法,不是10进制的加法!)// 这⾥设计的⽬的就是使⽤⼀个32位的⼆进制数,32和31位代表了mode的值,后30位代表size的值// 例如size=100(4),mode=AT_MOST,则measureSpec=00=10000..00100publicstaticintmakeMeasureSpec(intsize,intmode) {returnsize+ mode; }/*** 通过详细测量结果获得mode*/// mode = measureSpec & MODE_MASK;// MODE_MASK = 11 (11后跟30个0),原理是⽤MODE_MASK后30位的0替换掉measureSpec后30位中的1,再保留32和31位的mode值。// 例如10 00..00100 & 11 00..00(11后跟30个0) = 1000..00(AT_MOST),这样就得到了mode的值publicstaticintgetMode(intmeasureSpec) {return(measureSpec & MODE_MASK);
}/*** 通过详细测量结果获得size*/// size = measureSpec & ~MODE_MASK;// 原理同上,不过这次是将MODE_MASK取反,也就是变成了00 111111(00后跟30个1),将32,31替换成0也就是去掉mode,保留后30位的sizepublicstaticintgetSize(intmeasureSpec) {return(measureSpec & ~MODE_MASK);
}/*** 重写的toString⽅法,打印mode和size的信息,这⾥省略*/publicstaticString toString(intmeasureSpec) {returnnull; }}源码中的onMeasure():知道了widthMeasureSpec和heightMeasureSpec是什么以后,我们就可以来看onMeasure⽅法了:/*** 这个⽅法需要被重写,应该由⼦类去决定测量的宽⾼值,*/protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}在onMeasure中只调⽤了setMeasuredDimension()⽅法,接受两个参数,这两个参数是通过getDefaultSize⽅法得到的,我们到源码⾥看看getDefaultSize究竟做了什么。getDefaultSize():/*** 作⽤是返回⼀个默认的值,如果MeasureSpec没有强制限制的话则使⽤提供的⼤⼩.否则在允许范围内可任意指定⼤⼩* 第⼀个参数size为提供的默认⼤⼩,第⼆个参数为测量的⼤⼩*/publicstaticintgetDefaultSize(intsize,intmeasureSpec) {intresult =size;intspecMode =e(measureSpec);intspecSize = e(measureSpec);switch(specMode) {// Mode =UNSPECIFIED,AT_MOST时使⽤提供的默认⼤⼩IFIED:result =size;break;_MOST://Mode = EXACTLY时使⽤测量的⼤⼩Y:result = specSize;break;}returnresult;}getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),这⾥就是获取最⼩宽度作为默认值,然后再根据具体的测量值和选⽤的模式来得到widthMeasureSpec。heightMeasureSpec同理。之后将widthMeasureSpec,heightMeasureSpec传⼊setMeasuredDimension()⽅法。setMeasuredDimension():/*** 这个⽅法必须由onMeasure(int, int)来调⽤,来存储测量的宽,⾼值。*/protectedfinalvoidsetMeasuredDimension(intmeasuredWidth,intmeasuredHeight) {mMeasuredWidth =measuredWidth;mMeasuredHeight = measuredHeight;mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;}这个⽅法就是我们重写onMeasure()所要实现的最终⽬的。它的作⽤就是存储我们测量好的宽⾼值。这下思路清晰了,现在的任务就是计算出准确的measuredWidth和heightMeasureSpec并传递进去,我们所有的测量任务就算完成了。源码中使⽤的getDefaultSize()只是简单的测量了宽⾼值,在实际使⽤时需要精细、具体的测量。⽽具体的测量任务就交给我们在⼦类中重写的onMeasure⽅法。在⼦类中重写的onMeasure:在测量之前⾸先要明确⼀点,需要测量的是⼀个View(例如TextView),还是⼀个ViewGroup(例如LinearLayout),还是多个ViewGroup嵌套。如果只有⼀个View的话我们就测量这⼀个就可以了,如果有多个View或者ViewGroup嵌套我们就需要循环遍历视图中所有的View。下⾯列出⼀个最简单的⼩例⼦,写⼀个⾃定义类CostomViewGroup继承⾃ViewGroup,然后重写它的构造⽅法,onMeasure和onLayout⽅法。⽤这个⾃定义的ViewGroup去写⼀个布局⽂件如下:将⼀个Button放⼊⾃定义的ViewGroup中,然后在MainActivity的onCreate回调⽅法中调⽤setContentView把整个布局⽂件设置进去。最后看⼀下⾃定义CostomViewGroup中的onMeasure⽅法的内容:@OverrideprotectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){//调⽤ViewGroup类中测量⼦类的⽅法measureChildren(widthMeasureSpec, heightMeasureSpec);//调⽤View类中默认的测量⽅法ure(widthMeasureSpec,heightMeasureSpec);}本⽂只是介绍测量,所以onLayout⽅法先省略,下⾯来看看效果图:在⼦类重写的onMeasure中只调⽤两个⽅法,第⼀个是⽗类的onMeasure⽅法,之前已经介绍了它的作⽤,它最后会调⽤setMeasuredDimension()将测量好的宽⾼值传递进去。第⼆个会调⽤measureChildren⽅法,它的作⽤是测量所有的⼦View,下⾯我们看看它是如何⼯作的。measureChildren()/** * 遍历所有的⼦view去测量⾃⼰(跳过GONE类型View) *@paramwidthMeasureSpec ⽗视图的宽详细测量值*@paramheightMeasureSpec ⽗视图的⾼详细测量值*/protectedvoidmeasureChildren(intwidthMeasureSpec,intheightMeasureSpec){finalintsize = mChildrenCount;finalView[] children= mChildren;for(inti =0; i < size; ++i) {finalView child = children[i];if((lags & VISIBILITY_MASK) != GONE){measureChild(child, widthMeasureSpec, heightMeasureSpec);}}}代码很简单,就是遍历所有的⼦View,如果View的状态不是GONE就调⽤measureChild去进⾏下⼀步的测量measureChild()/** * 测量单个视图,将宽⾼和padding加在⼀起后交给getChildMeasureSpec去获得最终的测量值 *@paramchild 需要测量的⼦视图
*@paramparentWidthMeasureSpec ⽗视图的宽详细测量值 *@paramparentHeightMeasureSpec ⽗视图的⾼详细测量值
*/protectedvoidmeasureChild(View child,intparentWidthMeasureSpec,intparentHeightMeasureSpec){// 取得⼦视图的布局参数finalLayoutParams lp = outParams();// 通过getChildMeasureSpec获取最终的宽⾼详细测量值finalintchildWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight,);finalintchildHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom,);// 将计算好的宽⾼详细测量值传⼊measure⽅法,完成最后的测量e(childWidthMeasureSpec,childHeightMeasureSpec); }getChildMeasureSpec()/*** 在measureChildren中最难的部分:找出传递给child的MeasureSpec。* ⽬的是结合⽗view的MeasureSpec与⼦view的LayoutParams信息去找到最好的结果* (也就是说⼦view的确切⼤⼩由两⽅⾯共同决定:1.⽗view的MeasureSpec 2.⼦view的LayoutParams属性)** @param spec ⽗view的详细测量值(MeasureSpec)* @param padding view当前尺⼨的的内边距和外边距(padding,margin)* @param childDimension child在当前尺⼨下的布局参数宽⾼值(,height)*/publicstaticintgetChildMeasureSpec(intspec,intpadding,intchildDimension) {//⽗view的模式和⼤⼩intspecMode =e(spec);intspecSize = e(spec);//通过⽗view计算出的⼦view = ⽗⼤⼩-边距(⽗要求的⼤⼩,但⼦view不⼀定⽤这个值)intsize= (0, specSize - padding);//⼦view想要的实际⼤⼩和模式(需要计算)intresultSize=0;intresultMode =0;//通过1.⽗view的MeasureSpec 2.⼦view的LayoutParams属性这两点来确定⼦view的⼤⼩switch(specMode) {// 当⽗view的模式为EXACITY时,⽗view强加给⼦view确切的值Y:// 当⼦view的LayoutParams>0也就是有确切的值if(childDimension >=0) {//⼦view⼤⼩为⼦⾃⾝所赋的值,模式⼤⼩为EXACTLYresultSize = childDimension;resultMode =Y;// 当⼦view的LayoutParams为MATCH_PARENT时(-1)}elseif(childDimension ==_PARENT) {//⼦view⼤⼩为⽗view⼤⼩,模式为EXACTLYresultSize =size;resultMode =Y;// 当⼦view的LayoutParams为WRAP_CONTENT时(-2)}elseif(childDimension ==_CONTENT) {//⼦view决定⾃⼰的⼤⼩,但最⼤不能超过⽗view,模式为AT_MOSTresultSize =size;resultMode =_MOST;}break;// 当⽗view的模式为AT_MOST时,⽗view强加给⼦view⼀个最⼤的值。_MOST://道理同上if(childDimension >=0) {resultSize = childDimension;resultMode = Y;}elseif(childDimension ==_PARENT) {resultSize =size;resultMode = _MOST;}elseif(childDimension ==_CONTENT) {resultSize =size;resultMode = _MOST;}break;// 当⽗view的模式为UNSPECIFIED时,⼦view为想要的值IFIED:if(childDimension >=0) {// ⼦view⼤⼩为⼦⾃⾝所赋的值resultSize =childDimension;resultMode = Y;}elseif(childDimension == _PARENT) {// 因为⽗view为UNSPECIFIED,所以MATCH_PARENT的话⼦类⼤⼩为0resultSize =0;resultMode =IFIED;}elseif(childDimension == _CONTENT) {// 因为⽗view为UNSPECIFIED,所以WRAP_CONTENT的话⼦类⼤⼩为0resultSize =0;resultMode =IFIED;}break;}asureSpec(resultSize, resultMode);}可能看完后感觉有点迷糊,接下来通过⼏个例⼦演⽰⼀下,可能⼤家就会对getChildMeasureSpec⽅法中的逻辑清晰⼀些。1.当⽗类View中宽⾼都为MATCH_PARENT(EXACTLY)时,宽⾼都为MATCH_PARENT(EXACTLY)时:2.当⽗类View中宽⾼都为MATCH_PARENT(EXACTLY)时,宽⾼都为WRAP_CONTENT(EXACTLY)时:3.当⽗类View中宽⾼都为MATCH_PARENT(EXACTLY)时。⼦类宽WRAP_CONTENT(AT_MOST),⾼为MATCH_PARENT(EXACTLY)时:1.当⽗类View中宽⾼都为WRAP_CONTENT(AT_MOST)时,⼦类宽⾼都为MATCH_PARENT(EXACTLY)时:2.当⽗类View中宽⾼都为WRAP_CONTENT(AT_MOST)时。⼦类宽WRAP_CONTENT(AT_MOST),⾼为MATCH_PARENT(EXACTLY)时:通过这两组简单的对⽐,其实⼤家就可以把测量⼦类⼤⼩的代码理解为:⽗类中MATCH_PARENT,WRAP_CONTENT,指定值和⼦类中的MATCH_PARENT,WRAP_CONTENT,指定值这两对值的相互作⽤。更复杂的情况则需要加上padding内边距和margin外边距等等⼀些其他对于View⼤⼩的约束。总结:今天介绍的都是系统提供的测量⽅法,除了这些以外还有⼀些其他的,⼤家可以看看源码。⽽且在真正的⾃定义View视图时,很⼤⼀部分都是借助这些系统提供的现成⽅法,并且根据需求再加上⾃⼰的特殊逻辑(当然也可以全部⽤⾃⼰的逻辑,但我们不要重复制造轮⼦)。这篇⽂章写了2个礼拜,写之前思路⾮常清晰,但是在写的时候越写越乱。写完以后感觉逻辑仍然不是很清晰,因为有的内容我也是⼀知半解⽐如UNSPECIFIED。如果⼤家⽔平和我差不多都是菜鸟级别的,希望⼤家不要深⼊的去研究源码逻辑,这样会导致越来越来混乱,从应⽤的⾓度出发可能会更好⼀些。下⾯会接着写onLayout和LayoutParams的相关内容。最后再将onMeasure,onLayout结合起来写⼀个完整的例⼦。也许这些都写完以后会对整个流程的思路会更加清晰。
发布者:admin,转转请注明出处:http://www.yc00.com/xiaochengxu/1689428689a246647.html
评论列表(0条)