Android自定义流式布局/自动换行布局

Android自定义流式布局/自动换行布局

最近,Google开源了一个流式排版库“FlexboxLayout”,功能强大,支持多种排版方式,如各种方向的自动换行等,具体资料各位可搜索学习^_^。

由于我的项目中,只需要从左到右S型的自动换行,需求效果图如下:
需求效果图
使用FlexboxLayout这个框架未免显得有些臃肿,所以自己动手写了一个流式ViewGroup。

安卓中自定义ViewGroup的步骤是:

  1. 新建一个类,继承ViewGroup
  2. 重写构造方法
  3. 重写onMeasure、onLayout方法

onMeasuer方法里一般写测量子View宽高、确定此控件宽高的代码;onLayout方法则是确定子View如何摆放(排版)。

代码如下:

    import android.content.Context;
    import android.util.AttributeSet;
    import android.view.View;
    import android.view.ViewGroup;

    public class FlexBoxLayout extends ViewGroup {

        private int mScreenWidth;
        private int horizontalSpace, verticalSpace;
        private float mDensity;//设备密度,用于将dp转为px

        public FlexBoxLayout(Context context) {
            this(context, null);
        }

        public FlexBoxLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
            //获取屏幕宽高、设备密度
            mScreenWidth = context.getResources().getDisplayMetrics().widthPixels;
            mDensity = context.getResources().getDisplayMetrics().density;
        }

       @Override
       protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            //确定此容器的宽高
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);

            //测量子View的宽高
            int childCount = getChildCount();
            View child = null;
            //子view摆放的起始位置
            int left = getPaddingLeft();
            //一行view中将最大的高度存于此变量,用于子view进行换行时高度的计算
            int maxHeightInLine = 0;
            //存储所有行的高度相加,用于确定此容器的高度
            int allHeight = 0;
            for (int i = 0; i < childCount; i++) {
                child = getChildAt(i);
                //测量子View宽高
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
                //两两对比,取得一行中最大的高度
                if (child.getMeasuredHeight() + child.getPaddingTop() + child.getPaddingBottom() > maxHeightInLine) {
                    maxHeightInLine = child.getMeasuredHeight() + child.getPaddingTop() + child.getPaddingBottom();
                }
                left += child.getMeasuredWidth() + dip2px(horizontalSpace) + child.getPaddingLeft() + child.getPaddingRight();
                if (left >= widthSize - getPaddingRight() - getPaddingLeft()) {//换行
                    left = getPaddingLeft();
                    //累积行的总高度
                    allHeight += maxHeightInLine + dip2px(verticalSpace);
                    //因为换行了,所以每行的最大高度置0
                    maxHeightInLine = 0;
                }
            }
            //再加上最后一行的高度,因为之前的高度累积条件是换行
            //最后一行没有换行操作,所以高度应该再加上
            allHeight += maxHeightInLine;

            if (widthMode != MeasureSpec.EXACTLY) {
                widthSize = mScreenWidth;//如果没有指定宽,则默认为屏幕宽
            }

            if (heightMode != MeasureSpec.EXACTLY) {//如果没有指定高度
                heightSize = allHeight + getPaddingBottom() + getPaddingTop();
            }

            setMeasuredDimension(widthSize, heightSize);
        }

        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
          if (changed) {
              //摆放子view
              View child = null;
              //初始子view摆放的左上位置
              int left = getPaddingLeft();
              int top = getPaddingTop();
              //一行view中将最大的高度存于此变量,用于子view进行换行时高度的计算
              int maxHeightInLine = 0;
              for (int i = 0, len = getChildCount(); i < len; i++) {
                  child = getChildAt(i);
                  //从第二个子view开始算起
                  //因为第一个子view默认从头开始摆放
                  if (i > 0) {
                      //两两对比,取得一行中最大的高度
                  if (getChildAt(i - 1).getMeasuredHeight() > maxHeightInLine) {
                          maxHeightInLine = getChildAt(i - 1).getMeasuredHeight();
                     }
                      //当前子view的起始left为 上一个子view的宽度+水平间距
                      left += getChildAt(i - 1).getMeasuredWidth() + dip2px(horizontalSpace);
                      if (left + child.getMeasuredWidth() >= getWidth() - getPaddingRight() - getPaddingLeft()) {//这一行所有子view相加的宽度大于容器的宽度,需要换行
                          //换行的首个子view,起始left应该为0+容器的paddingLeft
                          left = getPaddingLeft();
                          //top的位置为上一行中拥有最大高度的某个View的高度+垂直间距
                          top += maxHeightInLine + dip2px(verticalSpace);
                          //将上一行View的最大高度置0
                          maxHeightInLine = 0;
                      }
                  }
                  //摆放子view
                  child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
              }
          }
      }

        /**
         * dp转为px
         *
         * @param dpValue
         * @return
         */
        private int dip2px(float dpValue) {
            return (int) (dpValue * mDensity + 0.5f);
        }

        /**
         * 设置子view间的水平间距 单位dp
         *
         * @param horizontalSpace
         */
        public void setHorizontalSpace(int horizontalSpace) {
            this.horizontalSpace = horizontalSpace;
        }

        /**
         * 设置子view间的垂直间距 单位dp
         *
         * @param verticalSpace
         */
        public void setVerticalSpace(int verticalSpace) {
            this.verticalSpace = verticalSpace;
        }
    }    

使用如下:
xml文件:

        <com.zengd.FlexBoxLayout
            android:id="@+id/flexBoxLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <!--这里写子View,也可代码动态添加-->
            ……

        </com.zengd.FlexBoxLayout>

Activity里的代码:

    FlexBoxLayout flexBoxLayout = (FlexBoxLayout) findViewById(R.id.flex_box_layout);
    flexBoxLayout.setHorizontalSpace(10);//不设置默认为0
    flexBoxLayout.setVerticalSpace(10);//不设置默认为0

运行效果如图:
这里写图片描述

本项目Demo地址:
https://github.com/zengd0/FlexBoxLayout

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值