自己实现FlowLayout来教你自定义ViewGroup

来源:互联网 发布:icloud优化存储空间 编辑:程序博客网 时间:2024/05/17 22:54

关于FlowLayout的实现网上的文章已经太多了,但是,俗话说,实践出真知,对于我这样的新手,自己写一个还是很有必要的,写完发现很多大神们不曾提及的小知识点,在这里还是要分享一下。
首先先看一张典型的View的在viewgroup中的摆放图view在viewgroup中的摆放
这是一个viewgroup和view的标配属性,接下来我们的自定义ViewGroup将围绕这张图展开。
自定义一个完整的viewgroup(支持margin和padding属性),我们需要实现三个函数
边框范围测量函数onMeasure,子view的布局函数onLayout以及配置Params信息的函数generateLayoutParams;
下面来一一完成并解析这些函数
首先generateLayoutParams

 @Override    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs)    {        return new MarginLayoutParams(getContext(), attrs);    }

很简单,就返回一个MarginLayoutParams对象,这个对象可以帮助我们获取子view的布局信息。
然后是onMeasure,这个函数的作用是对viewgroup所需的宽高进行测量,并决定最后的宽高

 @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int selfWidth = resolveSize(0, widthMeasureSpec);        //获取设置的padding属性        int paddingLeft = getPaddingLeft();        int paddingTop = getPaddingTop();        int paddingRight = getPaddingRight();        int paddingBottom = getPaddingBottom();        int childLeft = paddingLeft;        int childTop = paddingTop;        int lineHeight = 0;        //通过计算每一个子控件的高度,得到自己的高度        for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {            View childView = getChildAt(i);            MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();            childView.measure(                    getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight,                            layoutParams.width),                    getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom,                            layoutParams.height));            int childWidth = childView.getMeasuredWidth()+layoutParams.leftMargin+layoutParams.rightMargin;            int childHeight = childView.getMeasuredHeight()+layoutParams.bottomMargin+layoutParams.topMargin;            lineHeight = Math.max(childHeight, lineHeight);            int totalWidth=childLeft+childWidth+paddingRight;            if (totalWidth>selfWidth){                childLeft = paddingLeft+childWidth;                childTop += lineHeight;                lineHeight = childHeight;            }  else {                childLeft += childWidth ;            }        }             int wantedHeight = childTop + lineHeight + paddingBottom;        setMeasuredDimension(selfWidth, resolveSize(wantedHeight, heightMeasureSpec));    }

代码的思路就是通过计算每一个view的宽高,得出自己所需要的宽高,最后通过调用setMeasuredDimension来设置所需的宽高。其中resolveSize函数的作用就是根据计算出的宽高和自身的specMode和specSize进行计算得出的值对比,返回一个最合适的值。

最后就是进行view 的位置摆放onLayout函数

 @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        int myWidth = r - l;        //获取设置的padding属性        int paddingLeft = getPaddingLeft();        int paddingTop = getPaddingTop();        int paddingRight = getPaddingRight();        int childLeft = paddingLeft;        int childTop = paddingTop;        int lineHeight = 0;        //根据子控件的宽高,计算子控件应该出现的位置。        for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {            View childView = getChildAt(i);            if (childView.getVisibility() == View.GONE) {                continue;            }            //通过MarginLayoutParams可以获得子view的margin属性            MarginLayoutParams layoutParams= (MarginLayoutParams) childView.getLayoutParams();            int childWidth = childView.getMeasuredWidth();            int childHeight = childView.getMeasuredHeight();            lineHeight = Math.max(childHeight+layoutParams.bottomMargin, lineHeight);            if (childLeft+ childWidth+layoutParams.leftMargin +layoutParams.rightMargin+ paddingRight > myWidth) {                childLeft = paddingLeft+layoutParams.leftMargin;                childTop += lineHeight+layoutParams.topMargin;                lineHeight = childHeight;            }            if (i==0){childLeft+=layoutParams.leftMargin;            childTop+=layoutParams.topMargin;}            childView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);            childLeft += childWidth +layoutParams.rightMargin;        }    }

代码的总思路很简单(onMeasure也参考这个思路)
if(某行所有的view加起来的总宽度超过viewgroup所能接受的最大宽度){
换行,重置view放置的启始位置(左边从起点开始,top值则为加上上一行行高的值),重置记录的行高
}else{
不换行,将当前的view摆放在前一个view的后面
}
然后是关于其他人博客中没有注明的自己理解的点,
1.margin属性是由控件所在的viewgroup进行测量并绘制的,这也是我们要重写generateLayoutParams的原因,配置以后才能读取子view的margin信息并绘制,padding属性是由view或者viewgroup本身测量并绘制的
2.子view的onLayout函数中的四个值childView.layout(childLeft, childTop, childright, childbottom)不是与margin和padding无关的,也就是刚刚说的,margin和padding都是有专门绘制的,四个点如下图
这里写图片描述
最后,贴上完整的代码,以及效果图

import android.content.Context;import android.util.AttributeSet;import android.view.View;import android.view.ViewGroup;import com.starrynight.huhu.utils.LogUtil;import java.util.ArrayList;import java.util.List;/** * Created by 四脚朝天的卡比兽 on 2016/9/14. */public class FlowLayout extends ViewGroup{    public FlowLayout(Context context) {        super(context);    }    public FlowLayout(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int selfWidth = resolveSize(0, widthMeasureSpec);        int paddingLeft = getPaddingLeft();        int paddingTop = getPaddingTop();        int paddingRight = getPaddingRight();        int paddingBottom = getPaddingBottom();        int childLeft = paddingLeft;        int childTop = paddingTop;        int lineHeight = 0;        //通过计算每一个子控件的高度,得到自己的高度        for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {            View childView = getChildAt(i);            MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();            childView.measure(                    getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight,                            layoutParams.width),                    getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom,                            layoutParams.height));            int childWidth = childView.getMeasuredWidth()+layoutParams.leftMargin+layoutParams.rightMargin;            int childHeight = childView.getMeasuredHeight()+layoutParams.bottomMargin+layoutParams.topMargin;            lineHeight = Math.max(childHeight, lineHeight);            int totalWidth=childLeft+childWidth+paddingRight;            if (totalWidth>selfWidth){                childLeft = paddingLeft+childWidth;                childTop += lineHeight;                lineHeight = childHeight;            }  else {                childLeft += childWidth ;            }        }        int wantedHeight = childTop + lineHeight + paddingBottom;        setMeasuredDimension(selfWidth, resolveSize(wantedHeight, heightMeasureSpec));    }    @Override    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs)    {        return new MarginLayoutParams(getContext(), attrs);    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        int myWidth = r - l;        int paddingLeft = getPaddingLeft();        int paddingTop = getPaddingTop();        int paddingRight = getPaddingRight();        int childLeft = paddingLeft;        int childTop = paddingTop;        int lineHeight = 0;        //根据子控件的宽高,计算子控件应该出现的位置。        for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {            View childView = getChildAt(i);            if (childView.getVisibility() == View.GONE) {                continue;            }            MarginLayoutParams layoutParams= (MarginLayoutParams) childView.getLayoutParams();            int childWidth = childView.getMeasuredWidth();            int childHeight = childView.getMeasuredHeight();            lineHeight = Math.max(childHeight+layoutParams.bottomMargin, lineHeight);            if (childLeft+ childWidth+layoutParams.leftMargin +layoutParams.rightMargin+ paddingRight > myWidth) {                childLeft = paddingLeft+layoutParams.leftMargin;                childTop += lineHeight+layoutParams.topMargin;                lineHeight = childHeight;            }            if (i==0){childLeft+=layoutParams.leftMargin;            childTop+=layoutParams.topMargin;}            childView.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);            childLeft += childWidth +layoutParams.rightMargin;        }    }}

这里写图片描述

1 0