Android关于自定义ViewGroup,onMeasure的一些实验结论。
下文仅代表个人想法,如有错误,欢迎指正,不胜感激。
我们在写android程序的过程中,通常都是使用自带的控件,亦或者从网上查找别人写好的控件,但是用控件容易,写控件难,抱着求知的态度,自定义了一个ViewGroup(很简单)。现在说下在自定义ViewGroup过程中遇到的问题。
- 自定义ViewGroup的过程
- onMeasure方法
- measureChildren方法
- onLayout方法
关于上面四点的描述较为繁琐,只是为了我这样的小白能更深刻的理解。
直接看问题总结,请移步这里。
自定义ViewGroup的过程
首先来说一下自定义View。让我们从机器的角度出发,要想在一个屏幕上绘制一个控件(以矩形图形为例),我们需要什么必要的参数?没错,是长和宽。只有获得长和宽,我们才能知道控件的大小。那么系统如何获得控件的大小的,onMeasure方法就是完成这件事的。然后再说自定义ViewGroup,ViewGroup内部有很多子View,它不仅需要知道自身的大小,还需要为子View安排位置,比如说我需要让一个Button显示的底部,这时候就需要onLayout方法来为子View设置位置。好,现在让我们想一下,在我们写xml的时候,我们其实有写过控件的大小,比如 android:layout_width=”150dp” android:layout_height=”150dp”。既然我们有了
onMeasure
首先说一下onMeasure实际上会被measure方法调用。
onMeasure,是用来测量自身的方法,这个听起来会感觉很奇怪,因为在我们写xml的时候,我们是写了控件的大小的:android:layout_width=”150dp” android:layout_height=”150dp”,或者用Java代码调用View.setLayoutParams(),这些都直接指定了view的大小,那么为社么还需要onMeasure呢?
现在我们想一下wrap_content这个这个属性。包裹内容。如果我们写了一个LinearLayout,并且设置了wrap_content值,那么这个LinearLayout的大小就会由它内部控件的大小来确定,此时,onMeasure方法便派上了用场。由于现在没有像上面150dp那样,直接给定大小,那么我们就需要知道内部子空间的大小,然后来计算自身的大小。实际上,measure方法的调用是一个递归的过程,从根view,一直到每个子view,通过这样的方法,我们就能知道每一个子view的大小,然后就可以测量出ViewGroup自身的大小。
onMeasure实际上只在ViewGroup的长或者宽是wrap_content时,才发挥真正作用,否则仅仅是递归调用子view的measure而已。
onMeasure方法的参数,widthMeasureSpec,heightMeasureSpec。关于这两个参数的意义,网上有很多,这里不再赘述,只谈一下下文要用到的东西。这两个参数包含了父布局想要子布局设置的大小,就是告诉子布局,我还有多少地方可以给你用(at_most),或者你必须用这么多地方(exactly)。这个两个参数是由父布局,子布局的layout_width,layout_height共同决定的。下面是一个关于该结论的测试记录。size,和mode是子布局widthMeasureSpec的内容。
1、外层 wrapped 内层wrapped size = 1080 mode = at_most
2、外层 wrapped 内层50dp size = 150 mode = exactly
3、外层 match_parent 内层50dp size = 150 mode = exactly
4、外层 match_parent 内层wrapped size = 1080 mode = at_most
measureChildren
既然谈到测量子控件,那么就需要用到measureChildren方法。这个方法的参数和onMeasure方法是完全相同的。当我们需要测量子控件的时候,直接调用measureChildren(widthMeasureSpec,heightMeasureSpec),参数来源于自身的onMeasure。在我写的过程中一直有个疑问,难道我们不应该给子布局重新设置下widthMeasureSpec,heightMeasureSpec的值吗?直接使用父布局的不会有问题吗?最后通过实验发现,measureChildren()内部已经为我们做了这些事,我们完全没有必要担心。
自定义ViewGroup的过程
onLayout方法,用来指定子控件的位置。这个方法比较好理解,通过调用getChildAt()方法获取子控件,然后调用子控件的layout方法,参数就是子控件左,上,右,下的位置。同样,layout方法也是会递归调用的。
总结
onMeasure调用是递归的。
onMeasure用来测量自身。
当ViewGroup是wrapp_content的时候,onMeasure内部需要获取子控件大小,来测量自身,这也是我们自定义控件重写onMeasure方法的意义。
measureChildren()的参数无需改变,传入自身onMeasure的参数即可。通过
child.getMeasureWidth获得子控件的测量宽度值。
onLayout调用是递归的。
onLayout用于指定内部控件的位置,通过child.layout(l,t,r,b)来实现。
自定义View只需要重写onMeasure方法,来告知父布局,“我”的大小。
自定义ViewGroup还需要重写onLayout来控制子控件的位置。
希望可以对学习android的各位有帮助。