自定义FlowLayout控件实现自定义宽度并换行

最近的需求是实现添加购物车页面,展示规格的时候会出现显示不全,数据会自动剪切掉,后边重新自定义了FlowLayout问题得到解决,下面直接上代码

public class FlowLayoutView extends ViewGroup {
    private final int DEFAULT_SPACING = 15;
    private int horizontalSpacing = DEFAULT_SPACING;//水平间距
    public FlowLayoutView(Context context) {
        this(context, null);
    }

    public FlowLayoutView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public FlowLayoutView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //获取所有和自定义属性和样式
        final TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.FlowLayoutView, defStyleAttr, 0);
        final int indexCount = typedArray.getIndexCount();
        for (int i = 0; i < indexCount; i++) {
            final int indexAttr = typedArray.getIndex(i);
            switch (indexAttr) {
                case R.styleable.FlowLayoutView_horizontalSpacing:
                    horizontalSpacing = typedArray.getInt(indexAttr,10);
                    break;
            }
        }
        typedArray.recycle();
    }
    /**
     * 设置子View直接的水平间距
     * @param horizontalSpacing
     */
    public void setHorizontalSpacing(int horizontalSpacing){
        if(horizontalSpacing>0){
            this.horizontalSpacing = horizontalSpacing;
        }
    }
    private ArrayList<Line> lineList = new ArrayList<FlowLayoutView.Line>();
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        lineList.clear();
        //1.获取FlowLayout的宽度
        int width = MeasureSpec.getSize(widthMeasureSpec);
        //2.计算用于实际比较的宽度,就是width减去左右的padding值
        int noPaddingWidth = width - getPaddingLeft()- getPaddingRight();
        //3.遍历所有的子TextView,在遍历过程中进行比较,进行分行操作
        //创建一个行
        //3.遍历所有的子TextView,进行分行操作
        Line line = new Line();//只要不换行,始终都是同一个Line对象
        for (int i = 0; i<getChildCount(); i++) {
            //获取子TextView
            View childView = getChildAt(i);
            //引起view的onMeasure方法回调,从而保证后面的方法能够有值
            childView.measure(0,0);

            //4.如果当前line中 没有TextView,则直接放入当前Line中
            if(line.getViewList().size()==0){
                line.addLineView(childView);
            }else if(line.getWidth()+horizontalSpacing+childView.getMeasuredWidth()>noPaddingWidth) {
                //5.如果当前line的宽+水平间距+childView的宽大于noPaddingWidth,则换行
                lineList.add(line);//先保存之前的line对象

                line = new Line();//重新创建Line
                line.addLineView(childView);//将chidlView放入新的Line
            }else {
                //6.如果小于noPaddingWidth,则将childView放入当前Line中
                line.addLineView(childView);
            }

            //7.如果当前childView是最后一个,那么就会造成最后的一个Line对象丢失,
            if(i==(getChildCount()-1)){
                lineList.add(line);//保存最后的line对象
            }
        }

        //for循环结束后,lineList就存放了所有的Line对象,而每个line中有记录自己的所有TextView
        //为了能够垂直的摆放所有的Line的TextView,所以要给当前FlowLayout设置对应的宽高,
        //计算所需要的高度:上下的padding + 所有line的高度   + 所有line之间的垂直间距
        int height = getPaddingTop()+getPaddingBottom();
        for (int i = 0; i < lineList.size(); i++) {
            height += lineList.get(i).getHeight();
        }
        height += (lineList.size()-1)*verticalSpacing;
        //向父View申请对应的宽高
        setMeasuredDimension(width,height);
    }
    //行与行之间的垂直间距
    private int verticalSpacing = 10;
    /**
     * 设置行与行之间的垂直间距
     * @param verticalSpacing
     */
    public void setVerticalSpacing(int verticalSpacing){
        if(verticalSpacing>0){
            this.verticalSpacing = verticalSpacing;
        }
    }
    /**
     * 摆放操作,让所有的子TextView摆放到指定的位置上面
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int paddingLeft = getPaddingLeft();
        int paddingTop = getPaddingTop();
        for (int i = 0; i < lineList.size(); i++) {
            Line line = lineList.get(i);//获取line对象
            //从第二行开始,他们的top总是比上一行多一个行高+垂直间距
            if(i>0){
                paddingTop += lineList.get(i-1).getHeight()+verticalSpacing;
            }
            ArrayList<View> viewList = line.getViewList();//获取line所有的TextView
            //1.计算出当前line的留白区域的值
            int remainSpacing = getLineRemainSpacing(line);
            //2.计算每个TextView分到多少留白
            float perSpacing = remainSpacing/viewList.size();

            for (int j = 0; j < viewList.size(); j++) {
                View childView = viewList.get(j);//获取每个TextView
                //3.将perSpacing增加到每个TextView的宽度上
                int widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (childView.getMeasuredWidth()+perSpacing),MeasureSpec.EXACTLY);
                childView.measure(widthMeasureSpec,0);

                if(j==0){
                    //摆放每行的第一个TextView
                    childView.layout(paddingLeft,paddingTop,paddingLeft+childView.getMeasuredWidth()
                            ,paddingTop+childView.getMeasuredHeight());
                }else {
                    //摆放后面的TextView,需要参照前一个View
                    View preView = viewList.get(j-1);
                    int left = preView.getRight()+horizontalSpacing;
                    childView.layout(left,preView.getTop(),left+childView.getMeasuredWidth(),
                            preView.getBottom());
                }
            }
        }
    }
    /**
     * 获取line的留白区域
     * @param line
     * @return
     */
    private int getLineRemainSpacing(Line line){
        return getMeasuredWidth()-getPaddingLeft()-getPaddingRight()-line.getWidth();
    }
    /**
     * 定义行对象,用来封装每行的所有TextView,以及宽和高
     * @author Administrator
     *
     */
    class Line{
        //用来记录当前行的所有TextView
        private ArrayList<View> viewList = new ArrayList<View>();
        //表示当前行所有TextView的宽,还有他们之间的水平间距
        private int width;
        //当前行的高度
        private int height;

        /**
         * 获取当前Line中的所有TextView
         * @return
         */
        public ArrayList<View> getViewList() {
            return viewList;
        }
        /**
         * 获取当前Line的宽度
         * @return
         */
        public int getWidth() {
            return width;
        }
        /**
         * 获取当前Line的高度
         * @return
         */
        public int getHeight() {
            return height;
        }

        /**
         * 添加一个TextView到viewList中
         * @param lineView
         */
        public void addLineView(View lineView){
            if(!viewList.contains(lineView)){
                viewList.add(lineView);

                //更新width
                if(viewList.size()==1){
                    //如果是第一个TextView,那么width就是lineView的宽度
                    width = lineView.getMeasuredWidth();
                }else {
                    //如果不是第一个,则要在当前width的基础上+水平间距+lineView的宽度
                    width += horizontalSpacing + lineView.getMeasuredWidth();
                }
                //更新height,在此所有的TextView的高度都是一样的
                height = Math.max(height,lineView.getMeasuredHeight());
            }
        }
    }
}
<com.xxxx.app.widget.FlowLayoutView
    android:id="@+id/flowLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="20px"
    app:horizontal_spacing="20px"
    app:vertical_spacing="20px" />
<attr name="horizontalSpacing" format="integer"/>
<declare-styleable name="FlowLayoutView">
    <attr name="horizontalSpacing"/>
</declare-styleable>

 

最后实现图片:

 

即可。

FlowLayoutPanel 一些应用程序需要一个布局可随窗体大小的调整或其中内容大小的改变而自动进行适当排列的窗体。在需要动态布局并且不希望在代码中显式处理 Layout 事件时,可考虑使用布局面板。 FlowLayoutPanel是.NET Framework的新增控件。顾名思义,面板可以采用Web窗体的方式给Windows窗体布局。FlowLayoutPanel是一个容器,允许以垂直或水平的方式放置包含的控件。除了放置控件之外,还可以剪辑控件。放置的方向使用FlowDirection属性和FlowDirection枚举来设置。WrapContents属性确定在重新设置窗体的大小时,控件是放在下一行、下一列,还是剪辑控件FlowLayoutPanel 按特定的流方向排列其内容:水平或垂直。其内容可从一行换到下一行,或者从一列换到下一列。另一种情况是不换行,而是将其内容截掉。 相信大家在做WinForm项目的时候,要对大量的控件进行排序(位置摆放),这个容器肯定最受欢迎,但很遗憾的是,此容器本身虽支持Dock和Anchor属性,但不支持放入此容器内的控件的Dock和Anchor属性(自动调整宽度),也就说,但窗体伸缩,FlowLayoutPanel容器自身可以缩放,但是里面的控件就没那么幸运了,不支持自动缩放,这样就必须写方法来触发新的事件来调整控件的大小,这样就会导致窗体的闪烁(重绘)。 借助ManagedSpy工具,我们可以看到此容器里面的器件的结构,我们可以在Form1里面添加一个事件SizeChanged 对容器里面每个器件重新给它大小 就行了。 附件:FlowLayoutPanel的Demo
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值