自定义流布局FloatLayout(一)

有没有遇到过这种效果,那么这种排布是怎么实现的呢

自定义流布局FloatLayout(一)_第1张图片

首先自定义一个类继承ViewGroup ,因为android提供的ViewGroup子类都不能实现这种效果

然后要有一个思路:就是ViewGroup的measure()方法的执行过程是怎么样的--如果有子view的话就先测量子view--然后再测量自己的宽高

1.在onMeasure()方法中获取到所有的子View ,然后对子view进行测量, 然后累加宽度,判断当宽度大于ViewGroup的款丢的时候,进行换行

2.把每行的子view都添加到一个集合line中, 最后再把所有的line都添加到集合lines中

3.在onLayout()方法中遍历所有的集合,对每一行的子view进行layout()操作,然后再换行的时候再把参数左 上 右 下重新累加赋值,继续执行layout()操作

具体的代码如下:

package zz.itcast.googleplay09.view;

import java.util.ArrayList;
import java.util.List;

import zz.itcast.googleplay09.utils.UIUtils;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/**
 * 排行界面的流布局(特点,按行显示,如果当前行已经显示满了,才会开辟新行)
 * @author wangdh
 *
 */
public class FlowLayout extends ViewGroup {
    
    //标签的间隔:水平、垂直
    private int hSpace = UIUtils.dip2px(13);
    private int vSpace = UIUtils.dip2px(13);
    
    public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

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

    public FlowLayout(Context context) {
        super(context);
    }
    /**
     * 分配子view位置(标签)
     * 主要由左上角点,右下角点,确定控件位置
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //1. 分配每一行的位置
        for (int i = 0; i < lines.size(); i++) {
            Line currentLine = lines.get(i);
            //分配每一个内容位置,都是只确定左上角点即可
            currentLine.layout(l+getPaddingLeft(),t+getPaddingTop());
            //非第一行,都需要添加行高,间距
            t += currentLine.getLineHeight();
            t += vSpace;
        }
    }
    /**
     * 确定FlowLayout的测量标准
     * 父亲有义务测量孩子的大小(宽度、高度)
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //1.测量孩子的大小(宽高的测量标准(宽度高度的mode、size))
        //(1).孩子的测量标准,与父亲的测量标准有一定的对应关系。获取父亲的测量标准
        //获取父亲 宽高的mode和size
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight();
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom();
        //(2)获取孩子的宽度高度的mode
        int childWidthMode = widthMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST :widthMode;
        int childHeightMode = heightMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST :heightMode;
        //(3)获取孩子的宽度高度的size(孩子的模式:At_most(size=1000)、未指定(未指定一般size=0,但是这里大小其实给多少都可以。))
//        int childWidthSize = 1000;
//        int childHeightSize = 1000; 
        int childWidthSize = widthSize;
        int childHeightSize = heightSize; 
        //(4)测量孩子
        for (int i = 0; i < getChildCount(); i++) {
            System.out.println("当前孩子:"+i);
            View child = getChildAt(i);
            child.measure(MeasureSpec.makeMeasureSpec(childWidthSize, childWidthMode), 
                    MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode));
            //获取孩子的宽度
            int childMeasuredWidth = child.getMeasuredWidth();
//            2. 将孩子放入行内
            currentLineUseWidth +=childMeasuredWidth;
            //如果当前已用宽度小于父亲宽度
            if(currentLineUseWidth<widthSize){
                //将孩子放入
                currentLine.addChild(child);
                System.out.println("1放入孩子");
                //添加水平间隔
                currentLineUseWidth+=hSpace;
                if(currentLineUseWidth>=widthSize){
                    //换行
                    newLine();
                    System.out.println("1换行");
                }
            }else{
                //如果当前行内没有孩子,强制加入
                if(currentLine.getChildCount()==0){
                    currentLine.addChild(child);
                    System.out.println("2放入孩子");
                }
                //换行
                newLine();
                //(避免换行时,漏掉孩子)
                i -- ;
                System.out.println("2换行");
            }
        }
        
        //如果最后一行没有在集合内,添加
        if(!lines.contains(currentLine)){
            lines.add(currentLine);
        }
        //3.测量本身FlowLayout
        int totalHeight = 0;
        //添加行高
        for (int i = 0; i < lines.size(); i++) {
            totalHeight+=lines.get(i).getLineHeight();
        }
        //间距比行数少一个
        totalHeight+=vSpace*(lines.size()-1);
        setMeasuredDimension(widthSize+getPaddingLeft()+getPaddingRight(),
                totalHeight+getPaddingTop()+getPaddingBottom());
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    /**
     * 换行
     */
    private void newLine() {
        //将当前行,放入集合,保存
        lines.add(currentLine);
        currentLine = new Line();
        currentLineUseWidth = 0;
    }
    //当前行
    private Line currentLine = new Line();
    
    //行集合
    private List<Line> lines= new ArrayList<FlowLayout.Line>();
    
    //当前行已用宽度
    private int currentLineUseWidth = 0;
    
    
    //行:用于存放标签Textview
    public class Line{
        
        private List<View> childs = new ArrayList<View>();
        
        //1.获取当前行剩余空间
        //先获取已经使用的空间(子view的宽度+间隔(子view个数-1))
        private int lineUseWidth = 0;
        /**
         * 添加孩子
         * @param child
         */
        public void addChild(View child) {
            childs.add(child);
            //当前行高应该是最高孩子的高度
            if(currentLineHeight<child.getMeasuredHeight()){
                //当前行高=孩子的高度
                currentLineHeight = child.getMeasuredHeight();
            }
            //(1)添加子view的宽度
            lineUseWidth +=child.getMeasuredWidth();
        }
        public void layout(int l, int t) {
            //(2)添加间隔
            lineUseWidth += (childs.size()-1)*hSpace;
//            System.out.println("lineUseWidth:"+lineUseWidth);
            //(3)获取剩余空间
            int leftUseWidth = getMeasuredWidth() -getPaddingLeft() - getPaddingRight() - lineUseWidth;
            int avgLeftUseWidth = leftUseWidth/childs.size();
//            System.out.println("childs.size():"+childs.size());
//            System.out.println("avgLeftUseWidth:"+avgLeftUseWidth);
            
            
            //通过行,分配每一个标签的位置
            for (int i = 0; i < childs.size(); i++) {
                View currentView = childs.get(i);
                //确定具体位置
                currentView.layout(l, t, l+currentView.getMeasuredWidth()+avgLeftUseWidth, t+currentView.getMeasuredHeight());
                //currentView.layout(l, t, getMeasuredWidth(), getMeasuredHeight());
                //非第一行的left不一样
                l += currentView.getMeasuredWidth()+avgLeftUseWidth;//子view的宽度(+剩余空间的宽度)
                l += hSpace;
            }
            
        }
        private int currentLineHeight = 0;
        /**
         * 当前行高度
         * @return
         */
        public int getLineHeight() {
            
            return currentLineHeight;
        }
        /**
         * 当前行有几个孩子
         * @return
         */
        public int getChildCount() {
            return childs.size();
        }
        
    }
    
}

那么这个空间就算是完成了,可以直接new出来,然后在代码中动态的添加TextView,效果还是很炫的


注意在每个子view的layout()方法的时候,修改一行代码,那么效果就会变得更加绚丽

自定义流布局FloatLayout(一)_第2张图片

  //通过行,分配每一个标签的位置
            for (int i = 0; i < childs.size(); i++) {
                View currentView = childs.get(i);
                //确定具体位置
            //currentView.layout(l, t, l+currentView.getMeasuredWidth()+avgLeftUseWidth, t+currentView.getMeasuredHeight());把这一行注释掉,打开下面一行
                currentView.layout(l, t, getMeasuredWidth(), getMeasuredHeight());
                //非第一行的left不一样
                l += currentView.getMeasuredWidth()+avgLeftUseWidth;//子view的宽度(+剩余空间的宽度)
                l += hSpace;
            }

具体的原理跟上面的类似,至于textView的背景颜色,就可以在代码中动态的设置随机的十六进制颜色值,至于具体操作放到下一次博客里面了

你可能感兴趣的:(自定义流布局FloatLayout(一))