自定义ViewGroup容器,实现自动换行的流式布局的效果

/**
 * 自定义ViewGroup容器,实现自动换行的流式布局的效果
 *   核心需要复写onMeasure和onLayout方法
 * 先写大小测算,在写位置的摆放
 * 扩展
 * ViewGroup难度点比较高
 */
public class FlowLayout extends ViewGroup {
    private List<Integer> lineMaxHeightList; //{40,80,33}
    //6个child
    //{{view1,view2},{view3},{view4,view5,view5}}
    //List<View>每一行的View的情况
    private List<List<View>> mAllViewList;


    public FlowLayout(Context context) {
        super(context);
        init();
    }


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


    private void init(){
        lineMaxHeightList = new ArrayList<Integer>();
        mAllViewList = new ArrayList<List<View>>();
    }


    /**
     * 测算容器宽度和高度 (这里针对于自适应的情况需要处理)
     * onMeasure方法系统会多次调用,最后一次测算的值是最终的值
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.i("123","onMeasure");
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);  //包含测试的大小和模式
        //由于方法调用的此时比较多,只需要记录最后一次有意义的数据,没有意义的数据都不保留
        lineMaxHeightList.clear();
        mAllViewList.clear();
        //解析widthMeasureSpec和heightMeasureSpec(size的记录,mode的记录)
        // match_parent (如果在外层的,系统给你屏幕的跨度)
        // wrap_content (child的宽度的累加还要加上margin(left,right))
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        //UNSPECIFIED模式:parent容器不做任何限制
        //AT_MOST模式:child可以尽可能的大,但不能超过parent
        //EXACTLY模式:parent给定义的大小
        int widthMode = MeasureSpec.getMode(widthMeasureSpec); //三种模式
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);


        //处理自适应的情况
        int wrapWidth = 0, wrapHeight = 0;
        //定义每一行的宽度,高度
        int lineWidth = 0, lineHeight=0;


        List<View> mList = new ArrayList<View>();
        //由child决定
        int cCount = getChildCount();
        for (int i = 0; i <cCount ; i++) {
            View child = getChildAt(i);
            //child填写自适应怎么处理
            //容器去测量child是自适应的情况
            measureChild(child,widthMeasureSpec,heightMeasureSpec); //可以测试child
            //获取该child实际的宽度和高度
            //补上外边距
            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //动态代码获取外边距的代码
            int cWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            int cHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
            //实现自动换行的效果 (什么时候需要换行,并记录自适应的宽度)
            if(lineWidth + cWidth <= widthSize){ //没有超过容器的最大宽度
                lineWidth += cWidth;
                //去最大的高度
                lineHeight = Math.max(lineHeight,cHeight); //保持最大值
                //每次加入当前child
                mList.add(child);
            }else {
                //加入该行的作答高度
                lineMaxHeightList.add(lineHeight); //记录上一行的最大高度
                //换行的时候加入到容器里,上一行所有的child
                mAllViewList.add(mList);
                //不可以清除集合,要创建新的对象
                mList = new ArrayList<View>(); //新的空间地址
                //上一行的lineWidth,考虑每一行宽度的情况,最终自适应的宽度应该是每一行的最大值
                wrapWidth = Math.max(wrapWidth,lineWidth);  //最大行的值
                wrapHeight += lineHeight; //高度不断累加(上一行自适应的高度)
                //行的宽度和高度设置成当前的child宽度和高度
                lineWidth = cWidth;
                lineHeight = cHeight;
                mList.add(child); //换行的第一个控件
            }
            //最后一个child情况
            if(i == cCount -1){
                //加入该行的作答高度
                lineMaxHeightList.add(lineHeight); //记录该行的最大高度
                mAllViewList.add(mList); //补上当前行的集合,放入到全局的
                //加入最后一个child的时候补上当前行的自适应的宽度和高度
                wrapWidth = Math.max(wrapWidth,lineWidth);  //最大行的值
                wrapHeight += lineHeight; //高度不断累加(上一行自适应的高度)
            }
            //自适应的宽度和高度计算就结束
            //包含mode,size情况 (通过模式判断知道什么时候使用自己自适应的值)
            Log.i("123","wrapWidth-->"+ wrapWidth);
            Log.i("123","wrapHeight-->"+ wrapHeight);
            int lastWidthSizeMode = widthMode == MeasureSpec.EXACTLY ? widthSize : wrapWidth;
            int lastHeightSizeMode = heightMode == MeasureSpec.EXACTLY ? heightSize :wrapHeight;
            //重新测试 (符合自己设定的规则)
            setMeasuredDimension(lastWidthSizeMode,lastHeightSizeMode);
        }
    }


    /**
     * 让容器支持MarginLayoutParams的使用
     */
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        //让MarginLayoutParams可以使用 (第一个参数:上下文 ,第二个参数:AttributeSet属性)
        return new MarginLayoutParams(getContext(),attrs);
    }


    /**
     * 确定容器中child的位置摆放
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //onMeasure可以记录 ,onLayout就可以使用了
        //每一行的View必须要保存下来
        //每一行的最大高度记录下来 (全局变量定义容器)
        //这样的话就可以放置正确的位置了
        int lineCount = mAllViewList.size(); //全局集合的size都可以
        //定义坐上叫的坐标
        int left =0, top =0, right = 0, bottom = 0;
        Log.i("123","view size-->" + lineCount);
        for (int i = 0; i < lineCount; i++) {
            List<View> views = mAllViewList.get(i);
            //每一个单行的空间应该如何放置正确坐标
            for (int j = 0; j < views.size(); j++) {
                View child = views.get(j);
                MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
                //child的左外边距 (下一次要补上的)
                /**
                 * 举例 : left,10 right,10  child 100
                 */
                if(i == 0){
                    top = lp.topMargin;
                }
                left += lp.leftMargin;    //10      //2次 130  250
                //右下角
                right =  left + child.getMeasuredWidth();  //230
                bottom = top + child.getMeasuredHeight();
                //child放置位置的方法
                child.layout(left,top,right,bottom);  //每个child根据自己计算的上下左右的坐标,放置正确的位置
                //改变left  (130 + 100+ 10)  240 (不需要多加left)
                left += child.getMeasuredWidth() + lp.rightMargin;
                //顶部的改变换行就有效果
            }
            //换行
            top += lineMaxHeightList.get(i); //补上最大高度
            Log.i("123","每行高度的变化"+top);
            left = 0 ; //还原回0
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值