Android中的线性布局LinearLayout,只能横向或纵向排列子控件,而且横向排列时不能自动换行。实际上,通过扩展ViewGroup就能够实现控件自动的往右添加,如果当前行剩余空间不足,则自动添加到下一行,也就是所谓的流式布局,上代码




自定义CustomViewGroup.java如下


public class CustomViewGroup extends ViewGroup {
 
 

 
 
    int mCellWidth;
 
 
    int mCellHeight;
 
 

 
 
    public CustomViewGroup(Context context, AttributeSet attrs) {
 
 
        super(context, attrs);
 
 
        mCellWidth = 120;
 
 
        mCellHeight = 120;
 
 
    }
 
 

 
 
    public CustomViewGroup(Context context) {
 
 
        super(context);
 
 
    }
 
 

 
 
    public void setCellWidth(int width) {
 
 
        mCellWidth = width;
 
 
        requestLayout();
 
 
    }
 
 

 
 
    public void setCellHeight(int height) {
 
 
        mCellHeight = height;
 
 
        requestLayout();
 
 
    }
 
 

 
 
    @Override
 
 
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 
 

 
 
        int cellWidthSpec = MeasureSpec.makeMeasureSpec(mCellWidth,
 
 
                MeasureSpec.AT_MOST);
 
 
        int cellHeightSpec = MeasureSpec.makeMeasureSpec(mCellHeight,
 
 
                MeasureSpec.AT_MOST);
 
 

 
 
        int count = getChildCount();
 
 
        for (int i = 0; i < count; i++) {
 
 
            View childView = getChildAt(i);
 
 
            measureChild(childView, cellWidthSpec, cellHeightSpec);
 
 
            // childView.measure(cellWidthSpec, cellHeightSpec);
 
 
        }
 
 
        // 使用父容器给我们的尺寸和计算出的尺寸进行比较,选择正确的尺寸设置容器控件所占区域大小
 
 
        setMeasuredDimension(resolveSize(mCellWidth * count, widthMeasureSpec),
 
 
                resolveSize(mCellHeight * count, heightMeasureSpec));
 
 
    }
 
 

 
 
    @Override
 
 
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
 
 
        int cellWidth = mCellWidth;
 
 
        int cellHeight = mCellHeight;
 
 
        int columns = (r - l) / cellWidth;
 
 
        if (columns < 0) {
 
 
            columns = 1;
 
 
        }
 
 
        int x = 0;// 横坐标
 
 
        int y = 0;// 纵坐标
 
 
        int i = 0;
 
 
        int count = getChildCount();
 
 
        for (int index = 0; index < count; index++) {
 
 
            final View childView = getChildAt(index);
 
 

 
 
            int cWidth = childView.getMeasuredWidth(); // childView的宽度
 
 
            int cHeight = childView.getMeasuredHeight();
 
 

 
 
            int left = x + ((cellWidth - cWidth) / 2);// 相对父容器,左边的位置
 
 
            int top = y + ((cellHeight - cHeight) / 2);
 
 

 
 
            childView.layout(left, top, left + cWidth, top + cHeight);
 
 
            if (i >= (columns - 1)) {
 
 
                // 转到下一行
 
 
                i = 0;
 
 
                x = 0;
 
 
                y += cellHeight;
 
 
            } else {
 
 
                i++;
 
 
                x += cellWidth;
 
 
            }
 
 
        }
 
 
    }
 
 
}


在xml布局中使用CustomViewGroup


<?xml version="1.0" encoding="utf-8"?>
 
 
<com.example.view.CustomViewGroup xmlns:android="http://schemas.android.com/apk/res/android"
 
 
    xmlns:app="http://schemas.android.com/apk/res/com.example.activity"
 
 
    android:id="@+id/viewgroup_main_flow"
 
 
    android:layout_width="match_parent"
 
 
    android:layout_height="match_parent" >
 
 

 
 
    <ImageView
 
 
        android:layout_width="wrap_content"
 
 
        android:layout_height="wrap_content"
 
 
        android:src="@drawable/ic_launcher" />
 
 

 
 
    <ImageView
 
 
        android:layout_width="wrap_content"
 
 
        android:layout_height="wrap_content"
 
 
        android:src="@drawable/ic_launcher" />
 
 

 
 
    <ImageView
 
 
        android:layout_width="wrap_content"
 
 
        android:layout_height="wrap_content"
 
 
        android:src="@drawable/ic_launcher" />
 
 

 
 
    <ImageView
 
 
        android:layout_width="wrap_content"
 
 
        android:layout_height="wrap_content"
 
 
        android:src="@drawable/ic_launcher" />
 
 

 
 
    <ImageView
 
 
        android:layout_width="wrap_content"
 
 
        android:layout_height="wrap_content"
 
 
        android:src="@drawable/ic_launcher" />
 
 

 
 
    <ImageView
 
 
        android:layout_width="wrap_content"
 
 
        android:layout_height="wrap_content"
 
 
        android:src="@drawable/ic_launcher" />
 
 

 
 
</com.example.view.CustomViewGroup>




效果如下:

ViewGroup2——自定义实现流式布局_布局


因为在创建CustomViewGroup时,系统会调用构造函数初始化


public CustomViewGroup(Context context, AttributeSet attrs) {
 
 
        super(context, attrs);
 
 
        mCellWidth = 120;
 
 
        mCellHeight = 120;
 
 
    }


因此所有子控件的单元格都设置为120,当然也可以通过代码改变控件大小,如下:


CustomViewGroup customViewGroup=(CustomViewGroup) findViewById(R.id.viewgroup_main_flow);
 
 
        customViewGroup.setCellWidth(200);
 
 
        customViewGroup.setCellHeight(200);


ViewGroup2——自定义实现流式布局_android开发_02




这个例子的效果很像GridView,但又有些不同,这只是简单的实现,我们还可以更加完善一下它。虽然这样,这个例子也有很大的局限性——那就是每个控件的大小都是一样的,如果我们的子控件不相同怎么办?


直接盗用鸿洋大神的劳动成果了,看代码





CustomViewGroup.java如下



public class CustomViewGroup extends ViewGroup {
 
  

 
  
    public CustomViewGroup(Context context, AttributeSet attrs) {
 
  
        super(context, attrs);
 
  
    }
 
  

 
  
    @Override
 
  
    protected ViewGroup.LayoutParams generateLayoutParams(
 
  
            ViewGroup.LayoutParams p) {
 
  
        return new MarginLayoutParams(p);
 
  
    }
 
  

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

 
  
    @Override
 
  
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
 
  
        return new MarginLayoutParams(LayoutParams.MATCH_PARENT,
 
  
                LayoutParams.MATCH_PARENT);
 
  
    }
 
  

 
  
    /**
 
  
     * 负责设置子控件的测量模式和大小,根据所有子控件设置自己的宽和高
 
  
     */
 
  
    @Override
 
  
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 
  
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
  
        // 获得ViewGroup的父容器为它设置的测量模式和大小
 
  
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
 
  
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
 
  
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
 
  
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
 
  

 
  
        // 如果是warp_content,记录宽和高
 
  
        int width = 0;
 
  
        int height = 0;
 
  
        /**
 
  
         * 记录每一行的宽度,width取lineWidth最大宽度
 
  
         */
 
  
        int lineWidth = 0;
 
  
        /**
 
  
         * 每一行的高度,累加至height
 
  
         */
 
  
        int lineHeight = 0;
 
  

 
  
        int count = getChildCount();
 
  

 
  
        // 遍历每个子元素
 
  
        for (int i = 0; i < count; i++) {
 
  
            View childView = getChildAt(i);
 
  
            // 测量每一个child的宽和高
 
  
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
 
  
            // 得到childView的lp
 
  
            MarginLayoutParams lp = (MarginLayoutParams) childView
 
  
                    .getLayoutParams();
 
  
            // 当前子空间实际占据的宽度
 
  
            int childWidth = childView.getMeasuredWidth() + lp.leftMargin
 
  
                    + lp.rightMargin;
 
  
            // 当前子空间实际占据的高度
 
  
            int childHeight = childView.getMeasuredHeight() + lp.topMargin
 
  
                    + lp.bottomMargin;
 
  
            /**
 
  
             * 如果加入当前childView,则超出最大宽度,则得到目前最大宽度给width,类加height 然后开启新的一行
 
  
             */
 
  
            if (lineWidth + childWidth > sizeWidth) {
 
  
                width = Math.max(lineWidth, childWidth);// 取最大的
 
  
                lineWidth = childWidth; // 重新开启新行,开始记录
 
  
                // 叠加当前高度,
 
  
                height += lineHeight;
 
  
                // 开启记录下一行的高度
 
  
                lineHeight = childHeight;
 
  
            } else
 
  
            // 否则累加值lineWidth,lineHeight取最大高度
 
  
            {
 
  
                lineWidth += childWidth;
 
  
                lineHeight = Math.max(lineHeight, childHeight);
 
  
            }
 
  
            // 如果是最后一个,则将当前记录的最大宽度和当前lineWidth做比较
 
  
            if (i == count - 1) {
 
  
                width = Math.max(width, lineWidth);
 
  
                height += lineHeight;
 
  
            }
 
  

 
  
        }
 
  
        setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth
 
  
                : width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight
 
  
                : height);
 
  

 
  
    }
 
  

 
  
    /**
 
  
     * 存储所有的View,按行记录
 
  
     */
 
  
    private List<List<View>> mAllViews = new ArrayList<List<View>>();
 
  
    /**
 
  
     * 记录每一行的最大高度
 
  
     */
 
  
    private List<Integer> mLineHeight = new ArrayList<Integer>();
 
  

 
  
    @Override
 
  
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
 
  
        mAllViews.clear();
 
  
        mLineHeight.clear();
 
  

 
  
        int width = getWidth();
 
  

 
  
        int lineWidth = 0;
 
  
        int lineHeight = 0;
 
  
        // 存储每一行所有的childView
 
  
        List<View> lineViews = new ArrayList<View>();
 
  
        int count = getChildCount();
 
  
        // 遍历所有的孩子
 
  
        for (int i = 0; i < count; i++) {
 
  
            View childView = getChildAt(i);
 
  
            MarginLayoutParams lp = (MarginLayoutParams) childView
 
  
                    .getLayoutParams();
 
  
            int childWidth = childView.getMeasuredWidth();
 
  
            int childHeight = childView.getMeasuredHeight();
 
  

 
  
            // 如果已经需要换行
 
  
            if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width) {
 
  
                // 记录这一行所有的View以及最大高度
 
  
                mLineHeight.add(lineHeight);
 
  
                // 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView
 
  
                mAllViews.add(lineViews);
 
  
                lineWidth = 0;// 重置行宽
 
  
                lineViews = new ArrayList<View>();
 
  
            }
 
  
            /**
 
  
             * 如果不需要换行,则累加
 
  
             */
 
  
            lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
 
  
            lineHeight = Math.max(lineHeight, childHeight + lp.topMargin
 
  
                    + lp.bottomMargin);
 
  
            lineViews.add(childView);
 
  
        }
 
  
        // 记录最后一行
 
  
        mLineHeight.add(lineHeight);
 
  
        mAllViews.add(lineViews);
 
  

 
  
        int left = 0;
 
  
        int top = 0;
 
  
        // 得到总行数
 
  
        int lineNums = mAllViews.size();
 
  
        for (int i = 0; i < lineNums; i++) {
 
  
            // 每一行的所有的views
 
  
            lineViews = mAllViews.get(i);
 
  
            // 当前行的最大高度
 
  
            lineHeight = mLineHeight.get(i);
 
  

 
  
            // 遍历当前行所有的View
 
  
            for (int j = 0; j < lineViews.size(); j++) {
 
  
                View child = lineViews.get(j);
 
  
                if (child.getVisibility() == View.GONE) {
 
  
                    continue;
 
  
                }
 
  
                MarginLayoutParams lp = (MarginLayoutParams) child
 
  
                        .getLayoutParams();
 
  

 
  
                // 计算childView的left,top,right,bottom
 
  
                int lc = left + lp.leftMargin;
 
  
                int tc = top + lp.topMargin;
 
  
                int rc = lc + child.getMeasuredWidth();
 
  
                int bc = tc + child.getMeasuredHeight();
 
  

 
  
                child.layout(lc, tc, rc, bc);
 
  

 
  
                left += child.getMeasuredWidth() + lp.rightMargin
 
  
                        + lp.leftMargin;
 
  
            }
 
  
            left = 0;
 
  
            top += lineHeight;
 
  
        }
 
  

 
  
    }
 
  
}
 
在onMeasure方法中首先得到父容器传入的测量模式和宽高值,然后遍历所有的childView,使用measureChild方法对所有的childView进行测 量。然后根据所有childView测量得出的宽和高得到该ViewGroup如果设置为wrap_content时的宽和高。最后根据模式,如果是 MeasureSpec.EXACTLY则直接使用父ViewGroup传入的宽和高,否则设置为自己计算的宽和高。然后在onLayout中完成对所有childView的布局。
 

 
 
 看下activity_main.xml 

 
<com.example.view.CustomViewGroup xmlns:android="http://schemas.android.com/apk/res/android"
 
 
    xmlns:tools="http://schemas.android.com/tools"
 
 
    android:layout_width="200dp"
 
 
    android:layout_height="wrap_content" >
 
 

 
 
    <TextView
 
 
        style="@style/text_style"
 
 
        android:background="@drawable/flag_01"
 
 
        android:text="Welcome" />
 
 

 
 
    <TextView
 
 
        style="@style/text_style"
 
 
        android:background="@drawable/flag_01"
 
 
        android:text="You" />
 
 

 
 
    <TextView
 
 
        style="@style/text_style"
 
 
        android:background="@drawable/flag_02"
 
 
        android:text="自定义" />
 
 

 
 
    <TextView
 
 
        style="@style/text_style"
 
 
        android:background="@drawable/flag_02"
 
 
        android:text="ViewGroup" />
 
 

 
 
    <TextView
 
 
        style="@style/text_style"
 
 
        android:background="@drawable/flag_03"
 
 
        android:text="努力" />
 
 

 
 
    <TextView
 
 
        style="@style/text_style"
 
 
        android:background="@drawable/flag_03"
 
 
        android:text="学习" />
 
 

 
 
    <TextView
 
 
        style="@style/text_style"
 
 
        android:background="@drawable/flag_04"
 
 
        android:text="Try you best" />
 
 

 
 
</com.example.view.CustomViewGroup>


实现效果如下:


ViewGroup2——自定义实现流式布局_布局_03

篇幅有限,先说这么多吧


源代码