时间过得真快,又到了写博客的时候了(/▽╲)。这次按照计划记录一个简单的自定义ViewGroup:流布局FlowLayout
的实现过程,将自定义控件知识储备-View的绘制流程和自定义控件知识储备-LayoutParams的那些事里的知识点结合起来,付诸实践。
1. 前言
早在学习Java的Swing基础知识的时候,就见到过里面的流布局FlowLayout,基本的效果就是让加入此容器的控件自左往右依次排列,如果当前行的宽度不足以容纳下一个控件,就会将此控件放置到下一行。其实这也跟css里向左浮动的效果很相似。
在Android的世界里,系统是没有提供类似FlowLayout布局的容器的。当然了,现在官方给我们提供了更强大也更复杂的FlexLayout
了。不过嘛,本篇博客是总结一个自定义ViewGroup的实现流程,所以需要找一个难易适中的实例来进行分析,也就是FlowLayout了。(是的,我就是挑软柿子捏︿( ̄︶ ̄)︿)。
2. 效果
闲话少说,还是先来看看蘑菇君写的FlowLayout的功能:
- 支持最基本的从左至右的排序,空间不足则换行
- 支持设置子控件间的水平和竖直的间隔(也可以通过给每个child设置margin来实现,不过没有统一设置来的方便)
- 支持绘制行之间的分割线
- 支持FlowLayout本身的
Gravity
和child views的Gravity
- 处理好FlowLayout的padding和child views的margin
这些都是FlowLayout基本的功能,效果如下图所示:
是不是感觉还行?至少一般的情况下是能满足大部分人的需求滴。o( ̄▽ ̄)d
3. 分析
列举一下自定义ViewGroup的流程:
- 自定义属性:如果ViewGroup需要用到自定义属性,则需要声明、设置、解析并获取自定义属性值。
- 测量:在
onMeasure
方法里处理AT_MOST
和EXACTLY
两种测量模式下ViewGroup的宽高和children的宽高。(UNSPECIFIED
模式可以暂不考虑) - 布局:在
onLayout
方法里确定children的位置。 - 绘制:如果ViewGroup里需要绘制,则重写onDraw方法,按逻辑绘制。比如FlowLayout可以在每一行之间绘制一条分隔线。
- 处理LayoutParams:如果要为children定义布局属性,如
layout_gravity
,则需要自定义LayoutParams,并且重写ViewGroup相关的方法。 - 处理滑动事件:在本FlowLayout里暂时用不上…( ╯▽╰)
上面的步骤可能有所遗漏,不过也差不多啦。下面蘑菇君要根据上述的流程来一步一步的分析FlowLayout的源码,源码可能有点长,有些细节上的逻辑看不懂也莫方,只要了解流程对应的实现方式和注意事项就好,有兴趣的话可以稍后自己下载源码分析具体的逻辑实现。
好滴,那就让我们来一步一步的看,这个FlowLayout是如何在我手里…被玩残的…
3.1 自定义属性
3.1.1 声明属性
首先,自定义属性的第一步当然是声明属性,而最常使用的方式当然是在xml资源文件里(一般来说就是attrs.xml文件)声明需要使用的属性:
<declare-styleable name="FlowLayout">
<attr name="android:gravity"/>
<attr name="horizonSpacing" format="dimension|reference"/>
<attr name="verticalSpacing" format="dimension|reference"/>
<attr name="dividerColor" format="color|reference"/>
<attr name="dividerWidth" format="dimension|reference"/>
</declare-styleable>
<declare-styleable name="FlowLayout_Layout">
<attr name="android:layout_gravity"/>
</declare-styleable>
这里需要注意两个地方:
我们声明了两个
declare-styleable
,一个是为FlowLayout
自身设置自定义属性;另一个是为孩子们提供额外属性,需要在自定义的LayoutParams
里解析获取属性值。大家都知道,我们在xml布局文件里使用自定义属性时,需要引入命名空间
xmlns:app="http://schemas.android.com/apk/res-auto"
使用自定义属性时,需要加上前缀app(或者是其它命名,只要一一对应)。但是有时候啊,我们自定义的属性名已经在系统中存在了,而且语义与我们想要的也很符合,比如如andrioid:text
、android:gravity
等等。这个时候估计谁都会有一种“拿来主义”的冲动:直接使用系统里已经存在的属性名就好了嘛,多“原生”!既然有这种“邪恶”的需求,那Google工程师自然是要满足滴(~ ̄▽ ̄)~。
以gravity
属性为例,我们只要在declare-styleable
里直接写上<attr name="android:gravity"/>
即可,不过这里要注意的是不需要也不能再加上format
属性,加上format
属性就代表着这是在声明一个新的属性,不加则代表这是在使用已存在的一个属性。
3.1.2 使用属性
使用属性就比较简单了:
<wang.mogujun.widget.FlowLayout
android:id="@+id/flow2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="#6A6A6A"
android:gravity="start"
android:padding="8dp"
app:horizonSpacing="8dp"
app:verticalSpacing="12dp"
app:dividerColor="#cccccc"
app:dividerWidth="2dp"
>
3.1.3 解析并获取属性
在xml设置了相应的属性后,就需要在FlowLayout里解析并获取属性值了:
public static final int DEFAULT_SPACING = 8;
public static final int DEFAULT_DIVIDER_COLOR = Color.parseColor(