了解了自定义View的流程,那么接下来最好就是对着一个自定义View去分析,加深一下对步骤的理解,这里拿FlowLayout去分析。这样一个自定义View 是继承的ViewGroup,因为它里面还有一个个的子View 填充,因此我们分析出这是继承自ViewGroup。然后接着分析,这个布局会自动判断 当宽度达到屏幕的宽度时,自动换到下一行,那接下来我们就去实现这样一个布局。
第一步:定义FlowLayout继承ViewGroup,并实现构造方法,以及父类的抽象方法
public class FlowLayout extends ViewGroup {
public FlowLayout(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
}
}
第二部 根据 MeasureSpec (测量规格)拿到 mode(测量模式) 和size(测量大小),然后根据这个大小去measure里面的child。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 1获取with height 以及mode
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec)
- getPaddingBottom() - getPaddingTop();
int widthSize = MeasureSpec.getSize(widthMeasureSpec)
- getPaddingLeft() - getPaddingRight();
restoreLine();
int count = getChildCount();
// 2 测量子View
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
int widthSpec = MeasureSpec.makeMeasureSpec(widthSize,
widthMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST
: widthMode);
int heightSpec = MeasureSpec.makeMeasureSpec(heightSize,
heightMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST
: heightMode);
child.measure(widthSpec, heightSpec);
if(mLine==null){
mLine=new Line();
}
//将childview 添加到每一行中
int childWidth=child.getMeasuredWidth();
//当前行已经占用的宽度
mUsedWidth+=childWidth;
if(mUsedWidth<widthSize){
//当前行还没有达到上限,那么该child就添加进这一行
mLine.addView(child);
mUsedWidth+=mHorizontalSpacing; //添加上两个子View之间水平方向的间隔
}else{
//说明长度超出了当前的最大宽度
if(mLine.getViewCount()==0){
//表示当前行中还没有元素,添加的第一个元素 长度就超过了最大宽度,那么也要把该child 添加进去保证有数据
mLine.addView(child);
}else{
//表示当前行中已经有元素,那么换一行,添加进去
newLine();
mLine.addView(child);
//改变已使用的宽度
mUsedWidth+=mHorizontalSpacing+childWidth;
}
}
}
//前面只有换行的时候才将Line 添加到lines 集合中,这里要判断一下最后一行,将最后一行也添加进去
if(mLine!=null&&mLine.getViewCount()>0&&!mLines.contains(mLine) ){
//表示有数据
mLines.add(mLine);
}
// 设置测量的宽高setMeasuredDimension
int totoalHeight=0;
for(int i=0;i<mLines.size();i++){
totoalHeight+=mLines.get(i).mHeight;//N行的高度
}
//加上 行间距
totoalHeight+=(mLines.size()-1)*mVerticalSpacing;
//加上padding
totoalHeight+=getPaddingBottom()+getPaddingTop();
// 设置FlowLayout的宽度值 高度值 宽度就是默认的宽度,高度是总的高度
int measuredHeight = resolveSize(totoalHeight, heightMeasureSpec);
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
measuredHeight);
}
measure 结束,接下来就是layout 去将view 具体确定view 的位置。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//主要是调用child.layout
int count=mLines.size();
int left = getPaddingLeft();
int top = getPaddingTop();
for(int i=0;i<count;i++){
Line line=mLines.get(i);
line.layout(left,top);
top+=mVerticalSpacing+line.mHeight;
}
}
这个方法主要用来layout 子view。
public void layout(int l, int t) {
int left = l;
int top = t;
// 父布局的宽度
int totoalWidth = getMeasuredWidth() - getPaddingLeft()
- getPaddingRight();
// 当前line 中view的个数
int count = getViewCount();
// 剩余空间平分给每个View
int spaceLast = totoalWidth - mWidth - (count - 1)
* mHorizontalSpacing;
int averageWidth = spaceLast / count;
// 平分的宽度
// int splitSpacing = (int) (spaceLast / count + 0.5);
for (int i = 0; i < count; i++) {
View child = views.get(i);
int childHeight = child.getMeasuredHeight();
int childWidth = child.getMeasuredWidth();
childWidth += averageWidth;
child.getLayoutParams().width = childWidth;
// 改变了原来的宽高,重新测量一次
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth,
MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(
childHeight, MeasureSpec.EXACTLY);
child.measure(widthMeasureSpec, heightMeasureSpec);
// 布局View
child.layout(left, top, left + childWidth, top + childHeight);
left += childWidth + mHorizontalSpacing; // 为下一个View的left赋值
}
}
这个方法主要实现的功能是将每一行剩余的空间,平均分配到该行的每一个View 身上。
这样一个布局在这里就基本实现了。
当然,可以根据自己的需要 进行细微的调整,比如改变子view的 弧度,padding 等等。下面给出具体效果
代码地址: https://github.com/CodingForAndroid/FlowLayoutDemo