本文的目的是教大家如何自定义viewgroup,如何编写自定义layout和自定义measurement。很多在网上随便搜到的概念和流程图,这里不再赘述。建议大家在看这篇文章之前先看一下自定义viewgroup的基本流程,心里有个大概的了解。本文侧重实践。Viewgroup的测量和布局流程基本梳理完毕。回顾一下基本的viewgroup绘制和布局过程中的关键点:view是自己测量并保存在onMeasure()方法中的,也就是说对于view(不是viewgroup)来说,必须在onMeasure方法中计算自己的大小并保存它。其实viewgroup最终还是从上层size调用了子view的measure方法。注意,子视图的measure实际上是调用了子视图的onMeasure方法。所以我们把这个过程理解为:viewgroup循环调用所有子view的onmeasure方法,用onmeasure方法计算出的尺寸来决定这些子view的最终尺寸和布局位置。measuremethod是一个finalmethod,可以理解为measurement工作的准备。由于它是final方法,我们不能重写它。我们不需要太在意它,因为measure最终会调用onmeasure,我们可以重写。.密切关注这一点。layout和onlayout是同一种关系。父视图在调用子视图的布局方法时,会将上一个measure阶段确定的位置和大小传递给子视图。对于自定义view/viewgroup,我们几乎只需要关注以下三个需求:对于android自带的现有view,我们只需要重写它的onMeasure方法即可。修改此大小以满足要求。对于android系统来说,属于我们自定义的view,比上面的稍微复杂一点,onMeasure方法要完全重写。第三种最复杂,需要重写onmeasure和onlayout方法才能完成复杂viewgroup的测量和布局。onMeasure方法特别说明:如何理解父视图对子视图的限制?既然onMeasure的两个参数是父视图对子视图的限制,那么这个限制的值从何而来呢?实际上,父视图对子视图施加了限制。绝大多数视图限制来自我们开发人员设置的布局开头的属性。比如我们给一个imageview设置了layout_width和layout_height这两个属性,这两个属性其实就是我们开发者所期望的。width和height属性,但是注意,这两个属性是为父视图设置的。其实对于布局开始的大部分属性,这些属性都是为父视图设置的。为什么要将它们显示给父视图??因为父视图需要知道这些属性,才知道应该对子视图的测量进行哪些限制?它是无限的(未指定)吗?或者限制一个最大值(AT_MOST),让子view不超过这个值?或者直接限制死亡,我让你想怎么样就怎么样(EXACTLY)。自定义一个BannerImageView,修改onMeasure方法。所谓bannerImageview就是很多电商实际投放的广告图片。该广告图像的宽高比是可变的。我们在日常的开发过程中经常会遇到这样的需求:imageview中高保真标注了纵横比,但是考虑到很多手机的屏幕宽度或者高度是不确定的,所以我们通常要手动计算imageview的高度或者宽度,然后动态改变宽度或高度的值。这个方法可以用但是很麻烦。这里给出一个自定义的imageview,通过设置一个ratio属性可以动态设置iv的高度。看效果很方便***看代码,重要的部分都写在注释里了,就不多说了。publicclassBannerImageViewextendsImageView{//长宽比floatratio;publicBannerImageView(Contextcontext){super(context);}publicBannerImageView(Contextcontext,AttributeSetattrs){super(context,attrs);TypedArraytypedArray=context.obtainStyledAttributes(attrs,R.styleable.BannerImageView);ratio=typedArray.getFloat(R.styleable.BannerImageView_ratio,1.0f);typedArray.recycle();}publicBannerImageView(Contextcontext,AttributeSetattrs,intdefStyleAttr){super(context,attrs,defStyleAttr);}@OverrideprotectedvoidonMeasure(intwidthMeasure/Spec,intheightMeasure)/人还是要自己过measurement,因为这个方法内部会调用setMeasuredDimension方法保存测量结果//我们只有保存后才能得到这个测量结果,否则下面super.onMeasure(widthMeasureSpec,heightMeasureSpec);//是获取不到的获取测量结果intmWidth=getMeasuredWidth();intmHeight=(int)(mWidth*ratio);//保存后,父视图即可获取测量的宽高。不保存就得不到。setMeasuredDimension(mWidth,mHeight);}}自定义view,onMeasure方法完全自己写首先做一个结论:完全自定义的view,onMeasure方法完全自己写,保存的宽高必须符合parentviewLimit,否则会出现bug,而且父view的limit保存在子view上的方法也很简单,直接调用resolveSize方法即可。所以写一个完全自定义视图的onMeasure方法并不难。首先计算你想要的宽度和高度。例如,如果你画一个圆,宽度和高度必须是半径大小的两倍。如果圆下面有文字,那么高度除了半径的两倍外,还必须有字号。正确的。很简单。这完全取决于您的自定义视图是什么样的。计算出自己想要的宽高后,可以直接使用resolveSize方法进行处理。***setMeasuredDimension保存。例子:publicclassLoadingViewextendsView{//圆的半径intradius;//圆的外矩形rect的起点intleft=10,top=30;PaintmPaint=newPaint();publicLoadingView(Contextcontext){super(context);}publicLoadingView(Contextcontext,AttributeSetattrs){super(context,attrs);TypedArraytypedArray=context.obtainStyledAttributes(attrs,R.styleable.LoadingView);radius=typedArray.getInt(R.styleable.LoadingView_radius,0);}publicLoadingView(Contextcontext,AttributeSetattrs,intdefStyleAttr){super(context,attrs,defStyleAttr);}@OverrideprotectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){super.onMeasure(widthMeasureSpec,heightMeasureSpec);intwidth=left+radius*2;intheight=top+radius*2;//一定要使用resolveSize方法来格式化你view的宽高,否则遇到某些布局会出现奇怪的bug。//因为没有这个你根本就没有父视图的感觉***强调width=resolveSize(width,widthMeasureSpec);height=resolveSize(height,heightMeasureSpec);setMeasuredDimension(width,height);}@OverrideprotectedvoidonDraw(Canvascanvas){super.onDraw(canvas);RectFoval=newRectF(left,top,left+radius*2,top+radius*2);mPaint.setColor(Color.BLUE);canvas.drawRect(oval,mPaint);//先画圆弧mPaint.setColor(Color.RED);mPaint.setStyle(Paint.Style.STROKE);mPaint.setStrokeWidth(2);canvas.drawArc(oval,-90,♂,false,mPaint);}}布局文件:"wrap_content"android:layout_height="wrap_content"android:src="@mipmap/dly"app:radius="200"> *比如这里没有对padding或者margin做特殊处理。自己写viewgroup的时候,记得加上对这些属性的处理*否则,一旦有人用了这些属性,发现不生效,那就丑了。.....*/publicclassSimpleFlowLayoutextendsViewGroup{publicSimpleFlowLayout(Contextcontext){super(context);}publicSimpleFlowLayout(Contextcontext,AttributeSetattrs){super(context,attrs);}publicSimpleFlowLayout(Contextcontext,AttributeSetattrs,intdefStyleAttr){super(context/attrs,attrdef)Style}***布局算法其实放不下剩下的那一行,还得再写一行来理解这个过程。*每个人都有自己的写法,也许你的写法比开源项目好*其实不夸张的,不可能是前面的onMeasure结束后,就可以拿到所有的子view和自己测量宽高,然后计算**@paramchanged*@paraml*@paramt*@paramr*@paramb*/@OverrideprotectedvoidonLayout(booleanchanged,intl,intt,intr,intb){intchildTop=0;intchildLeft=0;intchildRight=0;intchildBottom=0;//usedwidthintusedWidth=0;//customlayout自己的可用宽度intlayoutWidth=getMeasuredWidth();Log.v("wuyue","layoutWidth=="+layoutWidth);for(inti=0;i
