前言:生命总是要有信仰,有梦想才能一直前行,哪怕走的再慢,也是在前行。
相关文章:
《Android自定义控件三部曲文章索引》:http://blog.csdn.net/harvic880925/article/details/50995268
今天给大家讲讲有关自定义布局控件的问题,大家来看这样一个需求,你需要设计一个container,实现内部控件自动换行。即里面的控件能够根据长度来判断当前行是否容得下它,进而决定是否转到下一行显示。效果图如下
在上图中,所有的紫色部分是FlowLayout控件,明显可以看出,内部的每个TextView控件,可以根据大小自动排列。
效果图就是这样子了,第一篇先讲下预备知识。
一、ViewGroup绘制流程
注意,View及ViewGroup基本相同,只是在ViewGroup中不仅要绘制自己还是绘制其中的子控件,而View则只需要绘制自己就可以了,所以我们这里就以ViewGroup为例来讲述整个绘制流程。
绘制流程分为三步:测量、布局、绘制
分别对应:onMeasure()、onLayout()、onDraw()
其中,他们三个的作用分别如下:
onMeasure():测量自己的大小,为正式布局提供建议。(注意,只是建议,至于用不用,要看onLayout);
onLayout():使用layout()函数对所有子控件布局;
onDraw():根据布局的位置绘图;
有关绘图的部分,大家可以参考我的系列博客《android Graphics(一):概述及基本几何图形绘制》共有四篇,讲述了有关android 绘图的90%内容,大家可以参考。
这篇文章着重将内容放在分析onMeasure()和onLayout()上。
二、onMeasure与MeasureSpec
布局绘画涉及两个过程:测量过程和布局过程。测量过程通过measure方法实现,是View树自顶向下的遍历,每个View在循环过程中将尺寸细节往下传递,当测量过程完成之后,所有的View都存储了自己的尺寸。第二个过程则是通过方法layout来实现的,也是自顶向下的。在这个过程中,每个父View负责通过计算好的尺寸放置它的子View。
前面讲过,onMeasure()是用来测量当前控件大小的,给onLayout()提供数值参考,需要特别注意的是:测量完成以后通过setMeasuredDimension(int,int)设置给系统。
1、onMeasure
首先,看一下onMeasure()的声明:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
这里我们主要关注传进来的两个参数:int widthMeasureSpec, int heightMeasureSpec
与这两个参数有关的是两个问题:意义和组成。即他们是怎么来的,表示什么意思;还有,他们是组成方式是怎样的。
我们先说他们的意义:
他们是父类传递过来给当前view的一个建议值,即想把当前view的尺寸设置为宽widthMeasureSpec,高heightMeasureSpec
有关他们的组成,我们就直接转到MeasureSpec部分。
2、MeasureSpec
虽然表面上看起来他们是int类型的数字,其实他们是由mode+size两部分组成的。
widthMeasureSpec和heightMeasureSpec转化成二进制数字表示,他们都是32位的。前两位代表mode(测量模式),后面30位才是他们的实际数值(size)。
(1)模式分类
它有三种模式:
①、UNSPECIFIED(未指定),父元素部队自元素施加任何束缚,子元素可以得到任意想要的大小;
②、EXACTLY(完全),父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小;
③、AT_MOST(至多),子元素至多达到指定大小的值。
他们对应的二进制值分别是:
UNSPECIFIED=00000000000000000000000000000000
EXACTLY =01000000000000000000000000000000
AT_MOST =10000000000000000000000000000000
由于最前面两位代表模式,所以他们分别对应十进制的0,1,2;
(2)模式提取
现在我们知道了widthMeasureSpec和heightMeasureSpec是由模式和数值组成的,而且二进制的前两位代表模式,后28位代表数字。
我们先想想,如果我们自己来提取widthMeasureSpec和heightMeasureSpec中的模式和数值是怎么提取呢?
首先想到的肯定是通过MASK和与运算去掉不需要的部分而得到对应的模式或数值。
说到这大家可能会迷茫,我们写段代码来提取模式部分吧:
//对应11000000000000000000000000000000;总共32位,前两位是1
int MODE_MASK = 0xc0000000;
//提取模式
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
//提取数值
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
相信大家看了代码就应该清楚模式和数值提取的方法了吧,主要用到了MASK的与、非运算,难度不大,如果有问题,自行谷歌一下与、非运算方法吧。
(3)、MeasureSpec
上面我们自已实现了模式和数值的提取。但在强大的andorid面前,肯定有提供提取模式和数值的类。这个类就是MeasureSpec
下面两个函数就可以实现这个功能:
MeasureSpec.getMode(int spec) //获取MODE
MeasureSpec.getSize(int spec) //获取数值
另外MODE的取值为:
MeasureSpec.AT_MOST
MeasureSpec.EXACTLY
MeasureSpec.UNSPECIFIED
通过下面的代码就可以分别获取widthMeasureSpec和heightMeasureSpec的MODE和数值
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);
其实大家通过查看代码可以知道,我们的实现就是MeasureSpec.getSize()和MeasureSpec.getMode()的实现代码。
(4)、模式有什么用呢
我们知道这里有三个模式:EXACTLY、AT_MOST、UNSPECIFIED
需要注意的是widthMeasureSpec和heightMeasureSpec各自都有它对应的模式,模式的由来分别来自于XML定义:
简单来说,XML布局和模式有如下对应关系:
- wrap_content-> MeasureSpec.AT_MOST
- match_parent -> MeasureSpec.EXACTLY
- 具体值 -> MeasureSpec.EXACTLY
例如,下面这个XML
<com.example.harvic.myapplication.FlowLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.example.harvic.myapplication.FlowLayout>
那FlowLayout在onMeasure()中传值时widthMeasureSpec的模式就是 MeasureSpec.EXACTLY,即父窗口宽度值。heightMeasureSpec的模式就是 MeasureSpec.AT_MOST,即不确定的。
一定要注意是,当模式是MeasureSpec.EXACTLY时,我们就不必要设定我们计算的大小了,因为这个大小是用户指定的,我们不应更改。但当模式是MeasureSpec.AT_MOST时,也就是说用户将布局设置成了wrap_content,我们就需要将大小设定为我们计算的数值,因为用户根本没有设置具体值是多少