自定义ViewGroup之流式布局的实现

效果图预览
自定义ViewGroup之流式布局的实现_第1张图片

1. 分析

1. 流式布局就是一行可能有多个控件超过一行自动换行 
2. 在onMeasure中计算子控件的宽度,如果子控件的宽度已经超过父控件的宽度了
就自动换行,高度累加。
3. onLayout中控制每一个子控件的左上右下的布局

2. onMeasure处理

 //记录每一行的宽度,width不断取最大宽度但是不能大于行的宽度
 int lineWidth = 0;
 //每一行的高度,累加至height
 int lineHeight = 0;

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heighSize = MeasureSpec.getSize(heightMeasureSpec);

    // 如果是warp_content情况下,记录宽和高
    int width = 0;
    int height = 0;

    //记录每一行的宽度,width不断取最大宽度
    int lineWidth = 0;
    //每一行的高度,累加至height
    int lineHeight = 0;

    int count = getChildCount();

    for (int i = 0; i < count; i++) {

        View childView = getChildAt(i);
        //测量子View的大小
        measureChild(childView,widthMeasureSpec,heightMeasureSpec);

        MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();

        int childWidth = childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
        int childHeight = childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;

        //如果加入当前child,超出最大宽度(父控件的宽度),则目前最大宽度给width,累加height 然后开启新行
        if(lineWidth + childWidth > widthSize){
            width = Math.max(lineWidth,widthSize);// 取最大的
            lineWidth = childWidth;  //重新开启新行,开始记录
            height += lineHeight;    //// 叠加当前高度,
            lineHeight = childHeight;  // 开启记录下一行的高度
        }else{
            // 否则累加值lineWidth,lineHeight取最大高度
            lineWidth += childWidth;
            lineHeight = Math.max(lineHeight,childHeight);
        }

        if(i == count - 1){
            width = Math.max(width,lineWidth);
            height += lineHeight;
        }
    }

    int measureWidth = (widthMode == MeasureSpec.AT_MOST) ? width : widthSize;
    int measureHeight = (heightMode == MeasureSpec.AT_MOST) ? height : heighSize;
    setMeasuredDimension(measureWidth,measureHeight);
}

3. onLayout处理

// 存储每一行所有的childView
List lineViews = new ArrayList<>();
/**
 * 存储所有的View,按行记录
 */
  private List> mAllViews = new ArrayList<>();
 /**
  * 记录每一行的最大高度
  */
 private List mLineHeight = new ArrayList<>();

//按行存储所有的子View
int count = getChildCount();
for (int i = 0; i < count ; i++) {

    View child = getChildAt(i);
    MarginLayoutParams lp = (MarginLayoutParams) child
            .getLayoutParams();
    int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
    int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;

    // 如果已经需要换行
    if(childWidth + lineWidth > width){
        // 记录这一行所有的View以及最大高度
        mAllViews.add(lineViews);
        mLineHeight.add(lineHeight);
        // 重置下一行宽度
        lineWidth = 0;
        lineViews = new ArrayList<>();
    }else{
        //如果不需要换行,则累加
        lineWidth += childWidth;
        lineViews.add(child);
        lineHeight = Math.max(lineHeight,childHeight);
    }

    // 记录最后一行  最后一行肯定没有换行
    if(i == count - 1){
        mLineHeight.add(lineHeight);
        mAllViews.add(lineViews);
    }
}

//按行读取每一行的子View,layout指定显示位置
int left = 0;
int top = 0;
// 得到子View总行数
int lineNums = mAllViews.size();
for (int i = 0; i < lineNums; i++) {

    //获取每一行的子View
    List lineChildViews = mAllViews.get(i);
    // 当前行的最大高度
    int currentLineHeight  = mLineHeight.get(i);

    //遍历每一行的子View 分别确定位置
    for (int j = 0; j < lineChildViews.size(); j++) {

        View child = lineChildViews.get(j);
        if(child.getVisibility() == View.GONE){
            continue;
        }

        int cWidth = child.getMeasuredWidth();
        int cHeight = child.getMeasuredHeight();
        MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        int cLeft = left + lp.leftMargin;
        int cTop = top + lp.topMargin;
        int cRight = cLeft + cWidth;
        int cBottom = cTop + cHeight;
        //后一个的子View的左边间距是前一个子View的宽度加左右间距
        left += cWidth + lp.leftMargin + lp.rightMargin;

        child.layout(cLeft,cTop,cRight,cBottom);
    }

    //换行左边间距重置  高度累加
    left = 0;
    top += currentLineHeight;
}

//onLayout全部代码

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {

   mAllViews.clear();
   mLineHeight.clear();

   //父控件的大小
   int width = getMeasuredWidth();
   int lineWidth = 0;
   int lineHeight = 0;
   // 存储每一行所有的childView
   List lineViews = new ArrayList<>();

   int count = getChildCount();
   for (int i = 0; i < count ; i++) {

       View child = getChildAt(i);
       MarginLayoutParams lp = (MarginLayoutParams) child
               .getLayoutParams();
       int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
       int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;

       // 如果已经需要换行
       if(childWidth + lineWidth > width){
           // 记录这一行所有的View以及最大高度
           mAllViews.add(lineViews);
           mLineHeight.add(lineHeight);
           // 重置下一行宽度
           lineWidth = 0;
           lineViews = new ArrayList<>();
       }else{
           //如果不需要换行,则累加
           lineWidth += childWidth;
           lineViews.add(child);
           lineHeight = Math.max(lineHeight,childHeight);
       }

       // 记录最后一行  最后一行肯定没有换行
       if(i == count - 1){
           mLineHeight.add(lineHeight);
           mAllViews.add(lineViews);
       }
   }

   int left = 0;
   int top = 0;
   // 得到子View总行数
   int lineNums = mAllViews.size();
   for (int i = 0; i < lineNums; i++) {

       //获取每一行的子View
       List lineChildViews = mAllViews.get(i);
       // 当前行的最大高度
       int currentLineHeight  = mLineHeight.get(i);

       //遍历每一行的子View 分别确定位置
       for (int j = 0; j < lineChildViews.size(); j++) {

           View child = lineChildViews.get(j);
           if(child.getVisibility() == View.GONE){
               continue;
           }

           int cWidth = child.getMeasuredWidth();
           int cHeight = child.getMeasuredHeight();
           MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

           int cLeft = left + lp.leftMargin;
           int cTop = top + lp.topMargin;
           int cRight = cLeft + cWidth;
           int cBottom = cTop + cHeight;
           //后一个的子View的左边间距是前一个子View的宽度加左右间距
           left += cWidth + lp.leftMargin + lp.rightMargin;

           child.layout(cLeft,cTop,cRight,cBottom);
       }

       //换行左边间距重置  高度累加
       left = 0;
       top += currentLineHeight;
   }
}

4. 复写generateLayoutParams 因为计算子View之间的margin值


@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
    return new MarginLayoutParams(getContext(),attrs);
}

5. 布局xml



<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:background="#E1E6F6"
             android:orientation="vertical" >

   <com.ssdk.viewgroup1.viewgroup.FlowLayout
       android:layout_width="fill_parent"
       android:layout_height="wrap_content" >

       <TextView
           style="@style/text_flag_01"
           android:text="Welcome" />

       <TextView
           style="@style/text_flag_01"
           android:text="工程师" />

       <TextView
           style="@style/text_flag_01"
           android:text="学习ing" />

       <TextView
           style="@style/text_flag_01"
           android:text="恋爱" />

       <TextView
           style="@style/text_flag_01"
           android:text="挣钱..." />

       <TextView
           style="@style/text_flag_01"
           android:text="努力ing" />

       <TextView
           style="@style/text_flag_01"
           android:text="I think i can" />
   com.ssdk.viewgroup1.viewgroup.FlowLayout>
LinearLayout>

6. 小结和源码下载

小结:
主要是onMeasure测量和onLayout布局的处理

源码下载:
后期统一提供源代码下载

参考
洪洋的Android 自定义ViewGroup 实战篇 -> 实现FlowLayout

7.联系方式

QQ:1509815887

你可能感兴趣的:(自定义View,开发类)