自定义ViewGroup实现流式布局

要实现的就是这样的效果

自定义ViewGroup

  • 重写OnMeasure方法
    • 指定每个孩子测量规则
    • 实例化内部类【行】,进行逻辑处理(行宽够用,则加入孩子;行宽不够用,则换行)
  • 重写Onlayout方法
    • for循环遍历行集合(拿到每行,调用行类的layout方法分配行内孩子位置,纵坐标t不断加上行宽和间隔)

FlowLayout.java

public class FlowLayout extends ViewGroup {

    private int horizontolSpacing=UIutil.dip2px(13);//横向控件间隔
    private int verticalSpacing=UIutil.dip2px(13);//纵向行间隔

    private Line currentline;// 当前的行
    private int useWidth=0;// 当前行使用的宽度,即当前行拥有的控件孩子所占用的宽度
    private List<Line> mLines=new ArrayList<>();//保存行的行集合
    private int width;//FlowLayout宽度

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

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

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

    // 测量 当前控件Flowlayout
    // 父类是有义务测量每个孩子的
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    /**初始化【行】对象相关的变量*/
        mLines.clear();//清空行集合
        currentline=null;
        useWidth=0;

    /**重新指定每个孩子测量规则*/
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        width = widthSize-getPaddingLeft()-getPaddingRight();
        int height = heightSize-getPaddingBottom()-getPaddingTop(); // 获取到父控件FlowLayout宽和高
        int childeWidthMode;
        int childeHeightMode;
        //  为了测量每个孩子 需要指定每个孩子测量规则
        childeWidthMode = widthMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : widthMode;
        childeHeightMode = heightMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : heightMode;
        int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,childeWidthMode);
        int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,childeHeightMode);

    /**开始【行】操作*/
        currentline=new Line();// 创建了第一行
    /**遍历FlowLayout中的孩子,分配每行拥有的控件*/
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);//拿到FlowLayout的孩子
            System.out.println("孩子的数量:"+getChildCount());
            child.measure(childWidthMeasureSpec,childHeightMeasureSpec);//测量每个孩子

            int measuredWidth = child.getMeasuredWidth();//孩子控件宽度即所占FlowLayout的宽度
            useWidth += measuredWidth;// 让当前行加上使用的长度
            if(useWidth <= width){//控件孩子们占用的宽度小于当前行宽(行宽=FlowLayout的宽)
                currentline.addChild(child);//这时候证明当前的孩子是可以放进当前的行里,放进去
                useWidth += horizontolSpacing;//加上孩子之间的间隔
                if(useWidth>width){//使用宽度大于行宽
                    //换行
                    newLine();
                }
            }else{

                if(currentline.getChildCount() < 1){
                    currentline.addChild(child);// 保证当前行里面最少有一个孩子
                }
                newLine();//换行
            }

        }
        if(!mLines.contains(currentline)){
            mLines.add(currentline);// 添加当前行
        }
        int totalHeight = 0;//总高度,使用的高度
        for(Line line : mLines){
            totalHeight += line.getHeight();//等于所有控件的高度和
        }
        totalHeight += verticalSpacing*(mLines.size() - 1) + getPaddingTop() + getPaddingBottom();//加上所有行与行之间的间隔,paddingtop,paddingbottom。
        System.out.println(totalHeight);
/**设置父视图FlowLayout的尺寸*/     setMeasuredDimension(width+getPaddingLeft()+getPaddingRight(),resolveSize(totalHeight, heightMeasureSpec));//resolveSize方法是通过比较当前的高度判定规则与父视图的规则以及totalHeight与父视图尺寸的大小,选取适当高度尺寸;看源码很容易理解

    }
    /**换行*/
    private void newLine() {
        mLines.add(currentline);// 记录之前的行
        currentline=new Line(); // 创建新的一行
        useWidth=0;//新的一行重置使用宽度
    }

    // 分配每个孩子的位置
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        l+=getPaddingLeft();
        t+=getPaddingTop();//改变起点
        for (int i = 0; i < mLines.size(); i++) {
            Line line = mLines.get(i);
            line.layout(l,t);//交给每一行去分配
            t+= line.getHeight() + verticalSpacing;//改变高度t+=行高、行与行间隔之和
        }
    }

    /**内部类行*/
    private class Line {
        int height = 0; //当前行的高度
        int lineWidth = 0;//行宽
        private List<View> children = new ArrayList<View>();//控件集合

        /**
         * 添加一个孩子
         *
         * @param child
         */
        public void addChild(View child) {
            children.add(child);
            if (child.getMeasuredHeight() > height) {
                height = child.getMeasuredHeight();//行高等于当前行最大孩子的高
            }
            lineWidth += child.getMeasuredWidth();
        }
    /**当前行高*/
        public int getHeight() {
            return height;
        }

        /**
         * 返回孩子的数量
         *
         * @return
         */
        public int getChildCount() {
            return children.size();
        }
        /**在一行内分配各孩子位置*/
        public void layout(int l, int t) {
            lineWidth += horizontolSpacing*(children.size()-1);//行宽+=当前行所有孩子之间的水平间隔
            int surplusChild = 0;//将剩余宽度平均分配给每个孩子的宽度
            int surplus = width - lineWidth;//当前行分配孩子后剩余的宽度
            if(surplus > 0 && children.size() > 0){

                    System.out.println("children:" + children.size());
                    surplusChild = surplus / children.size();//算出将剩余宽度平均分配给每个孩子的宽度

            }

            for (int i = 0; i < children.size(); i++) {
                View child = children.get(i);
                child.layout(l,t,l+child.getMeasuredWidth()+surplusChild,t+child.getMeasuredHeight());//确定当前控件的位置,绘出控件
                l+=child.getMeasuredWidth()+ surplusChild+ horizontolSpacing ;//计算下一个孩子的横坐标(加上当前控件宽度,控件间隔,分给每个控件的剩余宽度)
            }
        }
    }
}
自定义ViewGroup的OnMeasure():
  • 指定控件孩子的判定规则
    • 根据父视图的规则模式确定控件孩子的规则模式
      MeasureSpec判定规则
  • 测量每个孩子
  • 设定父视图尺寸
    ViewGroup测量流程

在Activity中应用自定义布局

  • 创建并实例化一些文本控件显示文本,将文本控件填充到FlowLayout中。
private List<String> datas;
public void onCreate(){
    ScrollView scrollView = new ScrollView(getContext());//最上层布局ScrollView
                         scrollView.setBackgroundResource(R.drawable.grid_item_bg_normal);

        FlowLayout layout= new FlowLayout(getContext());//实例化FlowLayout
        int padding=UIutil.dip2px(13);
        layout.setPadding(padding, padding, padding, padding);

        int backColor = 0xffcecece;//背景色,用于创建Drawable的背景色
        Drawable pressedDrawable = DrawableUtil.createShape(backColor);//创建点击后的显示的Drawable
        /**根据文本数据的数量确定文本控件*/
        for (int i = 0; i < datas.size(); i++) {
            TextView textView = new TextView(getContext());
            final String s = datas.get(i);
            textView.setText(s);

            Random random=new Random();   //创建随机对象
            int red = random.nextInt(255)+1;
            int green = random.nextInt(255)+1;
            int blue = random.nextInt(255)+1;//创建随机色
            int color = Color.rgb(red, green, blue);//范围 0-255 随机色
            GradientDrawable createShape = DrawableUtil.createShape(color);// 默认显示的图片
            StateListDrawable selectorDrawable = DrawableUtil.createSelectorDrawable(pressedDrawable, createShape);//Drawable选择器

/**设置文本控件参数和点击事件*/            textView.setBackground(selectorDrawable);//将选择器设成背景
            textView.setTextColor(Color.WHITE);
            textView.setTextSize(18);
            textView.setGravity(Gravity.CENTER);
            int textPaddingV = UIutil.dip2px(4);
            int textPaddingH = UIutil.dip2px(7);
            textView.setPadding(textPaddingH,textPaddingV,textPaddingH,textPaddingV);
            textView.setClickable(true);
            textView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(getContext(),s,Toast.LENGTH_SHORT).show();
                }
            });
/**FlowLayout添加孩子*/
            layout.addView(textView,new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,-2));// -2 包裹内容

        }
/**scrollView添加FlowLayout为孩子*/
        scrollView.addView(layout);

        setContentView(scrollView);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值