自定义ViewGroup实现流式布局(支持ViewGroup Padding, 子View margin,每行高度可以不一样)

来源:互联网 发布:火星网络加速器 编辑:程序博客网 时间:2024/04/27 16:21

================================================================

预留填坑

先画一个我理解的margin padding 的图,感觉在写自定义ViewGroup的时候容易混乱:

黑色矩形区域为父ViewGroup区域,蓝色矩形区域为子View的布局区域(如果padding为0,则与黑色矩形区域重合),绿色矩形为每一个子View。
红色短线为父ViewGroup的padding,绿色矩形四周的紫色短线为子View的margin。



================================================================

先直接贴代码,注释真心很详细了:

大体思路,自定义一个ViewGroup,重写onMeasure,onLayout方法,

在onMeasure方法里测量并设置子View和ViewGroup自己的宽高,

在onLayout方法里,为每个子View布局。

这里假设的是每一行的里的每个View的行高是一样的,不同的行行高可以不同。

效果图1: ViewGroup 的padding为5dp



效果图2 是将ViewGroup的padding设为60dp,每个textview  margin为10dp:


是根据hyman大神的流式布局的思路,然后自己实现的,其中onLayout方法和hyman大神的不一样。

支持ViewGroup的padding,和子View的margin属性。


自定的ViewGroup代码如下:

package mcxtzhang.weixin521.leftmenu;import android.content.Context;import android.util.AttributeSet;import android.util.Log;import android.view.View;import android.view.ViewGroup;/** * 流式布局 * Created by zhangxutong on 2016/1/17. */public class FlowViewGroup extends ViewGroup {    private static final String TAG = "zxt/FlowViewGroup";    public FlowViewGroup(Context context) {        this(context, null);    }    public FlowViewGroup(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public FlowViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }    //在onMeasure里,测量所有子View的宽高,以及确定Viewgroup自己的宽高。    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        //获取系统传递过来测量出的宽度 高度,以及相应的测量模式。        //如果测量模式为 EXACTLY( 确定的dp值,match_parent),则可以调用setMeasuredDimension()设置,        //如果测量模式为 AT_MOST(wrap_content),则需要经过计算再去调用setMeasuredDimension()设置        int widthMeasure = MeasureSpec.getSize(widthMeasureSpec);        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int heightMeasure = MeasureSpec.getSize(heightMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        //计算宽度 高度 //wrap_content测量模式下会使用到:        //存储最后计算出的宽度,        int maxLineWidth = 0;        //存储最后计算出的高度        int totalHeight = 0;        //存储当前行的宽度        int curLineWidth = 0;        //存储当前行的高度        int curLineHeight = 0;        // 得到内部元素的个数        int count = getChildCount();        //存储子View        View child =null;        //存储子View的LayoutParams        MarginLayoutParams params =null;        //子View Layout需要的宽高(包含margin),用于计算是否越界        int childWidth;        int childHeight;        //遍历子View 计算父控件宽高        for (int i = 0; i < count; i++) {            child = getChildAt(i);            //如果gone,不测量了            if (View.GONE == child.getVisibility()) {                continue;            }            //先测量子View            measureChild(child, widthMeasureSpec, heightMeasureSpec);            //获取子View的LayoutParams,(子View的LayoutParams的对象类型,取决于其ViewGroup的generateLayoutParams()方法的返回的对象类型,这里返回的是MarginLayoutParams)            params = (MarginLayoutParams) child.getLayoutParams();            //子View需要的宽度 为 子View 本身宽度+marginLeft + marginRight            childWidth = child.getMeasuredWidth() + params.leftMargin + params.rightMargin;            childHeight = child.getMeasuredHeight() + params.topMargin + params.bottomMargin;            Log.i(TAG, "子View Layout需要的宽高(包含margin):childWidth:" + childWidth + "   ,childHeight:" + childHeight);            //如果当前的行宽度大于 父控件允许的最大宽度 则要换行            //父控件允许的最大宽度 如果要适配 padding 这里要- getPaddingLeft() - getPaddingRight()            //即为测量出的宽度减去父控件的左右边距            if (curLineWidth + childWidth > widthMeasure - getPaddingLeft() - getPaddingRight()) {                //通过比较 当前行宽 和以前存储的最大行宽,得到最新的最大行宽,用于设置父控件的宽度                maxLineWidth = Math.max(maxLineWidth, curLineWidth);                //父控件的高度增加了,为当前高度+当前行的高度                totalHeight += curLineHeight;                //换行后 刷新 当前行 宽高数据: 因为新的一行就这一个View,所以为当前这个view占用的宽高(要加上View 的 margin)                curLineWidth = childWidth;                curLineHeight = childHeight;            } else {                //不换行:叠加当前行宽 和 比较当前行高:                curLineWidth += childWidth;                curLineHeight = Math.max(curLineHeight, childHeight);            }            //如果已经是最后一个View,要比较当前行的 宽度和最大宽度,叠加一共的高度            if (i == count - 1) {                maxLineWidth = Math.max(maxLineWidth, curLineWidth);                totalHeight += childHeight;            }        }        Log.i(TAG, "系统测量允许的尺寸最大值:widthMeasure:" + widthMeasure + "   ,heightMeasure:" + heightMeasure);        Log.i(TAG, "经过我们测量实际的尺寸(不包括父控件的padding):maxLineWidth:" + maxLineWidth + "   ,totalHeight:" + totalHeight);        //适配padding,如果是wrap_content,则除了子控件本身占据的控件,还要在加上父控件的padding        setMeasuredDimension(                widthMode != MeasureSpec.EXACTLY? maxLineWidth + getPaddingLeft() + getPaddingRight() : widthMeasure,                heightMode != MeasureSpecEXACTLY ? totalHeight + getPaddingTop() + getPaddingBottom() : heightMeasure);    }    //布局父控件位置以及子控件的位置    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        Log.i(TAG, "changed:" + changed + "   ,l:" + l + "  t:" + t + "  r:" + r + "  b:" + b);        //子控件的个数        int count = getChildCount();        //ViewParent宽度(包含padding)        int width = getWidth();        //ViewParent 的右边x的布局限制值        int rightLimit =  width - getPaddingRight();        //存储基准的left top (子类.layout(),里的坐标是基于父控件的坐标,所以 x应该是从0+父控件左内边距开始,y从0+父控件上内边距开始)        int baseLeft = 0 + getPaddingLeft();        int baseTop = 0 + getPaddingTop();        //存储现在的left top        int curLeft = baseLeft;        int curTop = baseTop;        //子View        View child = null;        //子view用于layout的 l t r b        int viewL,viewT,viewR,viewB;        //子View的LayoutParams        MarginLayoutParams params = null;        //子View Layout需要的宽高(包含margin),用于计算是否越界        int childWidth;        int childHeight;        //子View 本身的宽高        int childW,childH;        //临时增加一个temp 存储上一个View的高度 解决过长的两行View导致显示不正确的bug        int lastChildHeight =0;        //        for (int i = 0; i < count; i++) {            child = getChildAt(i);            //如果gone,不布局了            if (View.GONE == child.getVisibility()) {                continue;            }            //获取子View本身的宽高:            childW = child.getMeasuredWidth();            childH = child.getMeasuredHeight();            //获取子View的LayoutParams,用于获取其margin            params = (MarginLayoutParams) child.getLayoutParams();            //子View需要的宽高 为 本身宽高+marginLeft + marginRight            childWidth =  childW + params.leftMargin + params.rightMargin;            childHeight = childH + params.topMargin + params.bottomMargin;            //这里要考虑padding,所以右边界为 ViewParent宽度(包含padding) -ViewParent右内边距            if (curLeft + childWidth > rightLimit ) {                //如果当前行已经放不下该子View了 需要换行放置:                //在新的一行布局子View,左x就是baseLeft,上y是 top +前一行高(这里假设的是每一行行高一样),                curTop = curTop + lastChildHeight;                //layout时要考虑margin                viewL = baseLeft +params.leftMargin;                viewT = curTop + params.topMargin;                viewR = viewL + childW;                viewB = viewT + childH;                //child.layout(baseLeft + params.leftMargin, curTop + params.topMargin, baseLeft + params.leftMargin + child.getMeasuredWidth(), curTop + params.topMargin + child.getMeasuredHeight());                //Log.i(TAG,"新的一行:" +"   ,baseLeft:"+baseLeft +"  curTop:"+curTop+"  baseLeft+childWidth:"+(baseLeft+childWidth)+"  curTop+childHeight:"+ ( curTop+childHeight));                curLeft = baseLeft + childWidth;            } else {                //当前行可以放下子View:                viewL = curLeft +params.leftMargin;                viewT = curTop + params.topMargin;                viewR = viewL + childW;                viewB = viewT + childH;                //child.layout(curLeft + params.leftMargin, curTop + params.topMargin, curLeft + params.leftMargin + child.getMeasuredWidth(), curTop + params.topMargin + child.getMeasuredHeight());                //Log.i(TAG,"当前行:"+changed +"   ,curLeft:"+curLeft +"  curTop:"+curTop+"  curLeft+childWidth:"+(curLeft+childWidth)+"  curTop+childHeight:"+(curTop+childHeight));                curLeft = curLeft + childWidth;            }            lastChildHeight = childHeight;            //布局子View            child.layout(viewL,viewT,viewR,viewB);        }    }    /**     * @return 当前ViewGroup返回的Params的类型     */    @Override    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {        return new MarginLayoutParams(getContext(), attrs);    }}


使用方法:

一、动态使用:

private FlowViewGroup mFlowViewGroup;private String[] mTexts = new String[]{"BatteryView.txt", "为自定义View",        " 参考attrs.xml", " 定义自定义View属性", " 参考fragment_04.xml",        " 使用自定义view,并传入属性值", " 两张图片为资源", "一张为view背景(白圈)",        "一张为一个圆形图片", "用于遮盖XFerMode","形成圆形波浪效果"};
private void initView() {    mFlowViewGroup = (FlowViewGroup) findViewById(R.id.flowlayout);}private void initDatas() {    TextView tv;    for (int i=0;i<mTexts.length;i++){        tv= (TextView) LayoutInflater.from(this).inflate(R.layout.item_flow,mFlowViewGroup,false);        tv.setText(mTexts[i]);        tv.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Toast.makeText(LeftMenu2Activity.this,""+((TextView)v).getText(),Toast.LENGTH_SHORT).show();            }        });        mFlowViewGroup.addView(tv);    }}

其中布局文件为:

ViewGroup布局文件如下:

<mcxtzhang.weixin521.leftmenu.FlowViewGroup    android:background="#d23c3c"    android:padding="5dp"    android:id="@+id/flowlayout"    android:layout_width="wrap_content"    android:layout_height="wrap_content"></mcxtzhang.weixin521.leftmenu.FlowViewGroup>
每个TextView布局文件如下:item_flow.xml:

<?xml version="1.0" encoding="utf-8"?><TextView xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:text="Demo Test"    android:background="@drawable/textview_bg"    android:layout_margin="10dp"    />

background为一个自写的shape:textview_bg.xml 放在drawable文件夹下

<?xml version="1.0" encoding="utf-8"?><shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">    <corners android:radius="5dp"/>    <gradient android:angle="45" android:startColor="#06fffa" android:endColor="#99aa55"/></shape>


二,静态使用:

直接在布局文件里:

<mcxtzhang.weixin521.leftmenu.FlowViewGroup    android:padding="5dp"    android:background="#00f00f"    android:layout_width="wrap_content"    android:layout_height="wrap_content">    <Button        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="有padding" />    <Button        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="流式" />    <Button        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="Java" />    <Button        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="RxJava" />    <Button        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="CardView" />    <Button        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="OKHttp" />    <Button        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="TabLayout" />    <Button        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="FloatActionBar" />    <Button        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="ViewPager" />    <Button        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="AsynkTask" />    <Button        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="群英传" />    <Button        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="padding" /></mcxtzhang.weixin521.leftmenu.FlowViewGroup>



资源下载地址:
http://download.csdn.net/detail/zxt0601/9618061




10 0
原创粉丝点击