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

来源:互联网 发布:淘宝好吃的点心店铺 编辑:程序博客网 时间:2024/06/04 20:27

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

1 0
原创粉丝点击