当前位置: 首页 > 科技观察

Android自定义View

时间:2023-03-12 09:31:47 科技观察

前言Android自定义View的详细步骤是每个Android开发者必须掌握的技能,因为在开发中总会有自定义View的需求。为了提高自己的技术水平,自己进行了系统的研究,这里把一些心得记录下来,希望大家及时指出不足之处。Android中布局绘制请求的过程是在Android框架层处理的。绘图从根节点开始,对布局树进行测量和绘制。在RootViewImpl中展开performTraversals。它所做的就是对需要的视图进行测量(测量视图的大小)、布局(确定视图的位置)和绘制(绘制视图)。下图可以很好的展示视图的绘制过程:当用户调用requestLayout时,只会触发measure和layout,但是当系统开始调用时,也会触发draw。下面详细描述这些过程。measuremeasure是View中的final方法,不能重写。它会测量和计算view的尺寸,但是会回调onMeasure方法,所以我们在自定义View的时候,可以根据需要重写onMeasure方法来测量View。它有两个参数widthMeasureSpec和heightMeasureSpec。这两个参数其实包含两部分,即size和mode。size是测量的大小,mode是视图布局的模式。我们可以通过以下代码分别获取:获取的模式类型分为以下三种:MODEEXPLAINUNSPECIFED父视图不约束子视图,子视图的大小可以是任意大小。一般用于自定义ListView、ScrollView等。要EXACTLY父视图给子视图设置一个精确的尺寸,子视图不超过这个尺寸,一般是一个精确的值比如200dp或者使用match_parentAT_MOST的parentview为childview指定一个***大小,以保证childview的所有内容都能以这个大小显示,通常是wrap_content。这种父视图无法获取子视图的大小,只能由子视图自己计算大小。这也是我们需要通过上面实现setMeasuredDimension的逻辑情况逻辑上获取视图的宽高,最好是调用setMeasuredDimension方法将测量的宽高传递出去。事实上,最后调用setMeasuredDimensionRaw方法为传递的值分配属性。调用super.onMeasure()的调用逻辑是一样的。下面以自定义验证码的View为例。其onMeasure方法如下:;if(widthMode==MeasureSpec.EXACTLY){//直接获取准确宽度width=widthSize;}elseif(widthMode==MeasureSpec.AT_MOST){//计算宽度(Textwidth+paddingsize)width=bounds.width()+getPaddingLeft()+getPaddingRight();}if(heightMode==MeasureSpec.EXACTLY){//直接获取准确高度height=heightSize;}elseif(heightMode==MeasureSpec.AT_MOST){//计算高度(theheightofthetext+paddingsize)height=bounds.height()+getPaddingBottom()+getPaddingTop();}//设置获取的宽高setMeasuredDimension(width,height);}可以设置不同的属性针对自定义View的layout_width和layout_height实现不同模式类型,你可以看到不同的效果。还要测量子视图的大小。一般子视图的尺寸是通过measureChildren(intwidthMeasureSpec,intheightMeasureSpec)方法测量的。protectedvoidmeasureChildren(intwidthMeasureSpec,intheightMeasureSpec){finalintsize=mChildrenCount;finalView[]children=mChildren;for(inti=0;i=0){resultSize=childDimension;resultMode=MeasureSpec.EXACTLY;}elseif(childDimension==LayoutParams.MATCH_PARENT){//Childwantstobeoursize.Sobeit.resultSize=size;resultMode=MeasureSpec.EXACTLY;}elseif(childDimension==LayoutParams.WRAP_CONTENT){//Childwantstodeterminiteitsownsize.Itcan'tbe//biggerthanus.resultSize=size;resultMode=MeasureSpec...e,但我们的尺寸不固定}break;//ParentaskedtoseehowbigwewanttobecaseMeasureSpec.UNSPECIFIED:if(childDimension>=0){//Childwantsaspecificsize...lethimhaveitresultSize=childDimension;resultMode=MeasureSpec.EXACTLY;}elseif(childDimension==LayoutParams.MATCH_PARENT){//Childwantstobeoursize...findouthowbigitshould//beresultSize=View.sUseZeroUnspecifiedMeasureSpec?0:size;resultMode=MeasureSpec.UNSPECIFIED;}elseif(childDimension==LayoutParams.WRAP_CONTENT){//Childwantstodeterminitetsownsize....findouthow//bigitshouldberesultSize=View.sUseZeroUnspecifiedMeasureSpec?0:size;resultMode=MeasureSpec.UNSPECIFIED;}break;}//noinspectionResourceTypereturnMeasureSpec.makeMeasureSpec(resultSize,resultMode);}是不是容易理解了点呢它所做的就是根据上面提到的模式类型获取对应的尺寸。根据父视图的模式类型和子视图的LayoutParams类型确定子视图的模式,最后通过MeasureSpec.makeMeasureSpec方法返回得到的尺寸和模式。***传递给measure,这个就是上面提到的widthMeasureSpec和heightMeasureSpec中包含的两部分的值。整个过程是measureChildren->measureChild->getChildMeasureSpec->measure->onMeasure->setMeasuredDimension,所以可以通过measureChildren对子view进行测量计算。layoutlayout也是一样,内部回调onLayout方法,用于确定子view的绘制位置,但是这个方法是ViewGroup中的一个抽象方法,所以如果要自定义的View继承了ViewGroup,就必须实现这个方法。但是如果继承了View,就不需要了。View中有一个空的实现。子视图的位置是通过View的布局方法传递计算出的left、top、right、bottom值来设置的,而这些值一般是借助View的width和height来计算的,而view的宽高可以通过getMeasureWidth和GetMeasureHeight方法获取,这两个方法获取的值是上面onMeasure中setMeasuredDimension传递的值,即子view测量的宽高。getWidth、getHeight不同于getMeasureWidth和getMeasureHeight。前者是onLayout之后才能获取到的值,分别是left-right和top-bottom;而后者是onMeasure之后才能得到的值。只是这两种获取的值一般是一样的,所以要注意调用的时机。下面是定义一个View的例子,它把子视图放在父视图的四个角上:;国际贸易委员会;for(inti=0;i