安卓流布局

package com.ui;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;

public class FlowLayout extends ViewGroup {
   public static final int DEFAULT_SPACING = 15;
   /** 横向间隔 */
   private int mHorizontalSpacing = 11;
   /** 纵向间隔 */
   private int mVerticalSpacing = DEFAULT_SPACING;
   /** 是否需要布局,只用于第一次 */
   boolean mNeedLayout = true;
   /** 当前行已用的宽度,由子View宽度加上横向间隔 */
   private int mUsedWidth = 0;
   /** 代表每一行的集合 */
   private final List<Line> mLines = new ArrayList<Line>();
   private Line mLine = null;
   /** 最大的行数 */
   private int mMaxLinesCount = -1;

   private Context context;
   /**
    * 画笔
    */
   private Paint paint;
   /**
    * 是否需要分割线
    */
   private boolean needDivider;
   /**
    * 是否需要右侧分割线
    */
   private boolean needRightDivider;
   /**
    * 是否需要底部分割线
    */
   private boolean needBottomDivider;

   public int count = 0;

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

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

   public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
      super(context, attrs, defStyleAttr);
      this.context = context;
      paint = new Paint();
      paint.setAntiAlias(true);
      paint.setStyle(Style.STROKE);
      int dp =dp2px(1);
      paint.setStrokeWidth(dp);
      paint.setColor(Color.BLACK);
      paint.setAlpha(255);
   }

   public void setHorizontalSpacing(int spacing) {
      if (mHorizontalSpacing != spacing) {
         mHorizontalSpacing = spacing;
         requestLayoutInner();
      }
   }

   public void setVerticalSpacing(int spacing) {
      if (mVerticalSpacing != spacing) {
         mVerticalSpacing = spacing;
         requestLayoutInner();
      }
   }

   public void setMaxLines(int count) {
      if (mMaxLinesCount != count) {
         mMaxLinesCount = count;
         requestLayoutInner();
      }
   }

   private void requestLayoutInner() {
      requestLayout();
   }

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

      int sizeWidth = MeasureSpec.getSize(widthMeasureSpec) - getPaddingRight() - getPaddingLeft();
      int sizeHeight = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom();

      int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
      int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

      restoreLine();// 还原数据,以便重新记录
      final int count = getChildCount();
      for (int i = 0; i < count; i++) {
         final View child = getChildAt(i);
         if (child.getVisibility() == GONE) {
            continue;
         }
         int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(sizeWidth, modeWidth == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : modeWidth);
         int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(sizeHeight, modeHeight == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : modeHeight);
         // 测量child
         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

         if (mLine == null) {
            mLine = new Line();
         }
         int childWidth = child.getMeasuredWidth();
         mUsedWidth += childWidth;// 增加使用的宽度
         if (mUsedWidth <= sizeWidth) {// 使用宽度小于总宽度,该child属于这一行。
            mLine.addView(child);// 添加child
            mUsedWidth += mHorizontalSpacing;// 加上间隔
            if (mUsedWidth >= sizeWidth) {// 加上间隔后如果大于等于总宽度,需要换行
               if (!newLine()) {
                  break;
               }
            }
         } else {// 使用宽度大于总宽度。需要换行
            if (mLine.getViewCount() == 0) {// 如果这行一个child都没有,即使占用长度超过了总长度,也要加上去,保证每行都有至少有一个child
               mLine.addView(child);// 添加child
               if (!newLine()) {// 换行
                  break;
               }
            } else {// 如果该行有数据了,就直接换行
               if (!newLine()) {// 换行
                  break;
               }
               // 在新的一行,不管是否超过长度,先加上去,因为这一行一个child都没有,所以必须满足每行至少有一个child
               mLine.addView(child);
               mUsedWidth += childWidth + mHorizontalSpacing;
            }
         }
      }

      if (mLine != null && mLine.getViewCount() > 0 && !mLines.contains(mLine)) {
         // 由于前面采用判断长度是否超过最大宽度来决定是否换行,则最后一行可能因为还没达到最大宽度,所以需要验证后加入集合中
         mLines.add(mLine);
      }

      int totalWidth = MeasureSpec.getSize(widthMeasureSpec);
      int totalHeight = 0;
      final int linesCount = mLines.size();
      for (int i = 0; i < linesCount; i++) {// 加上所有行的高度
         totalHeight += mLines.get(i).mHeight;
         if(i==linesCount-1){
            totalHeight += mLines.get(i).mHeight;
         }
      }
      totalHeight += mVerticalSpacing * (linesCount - 1);// 加上所有间隔的高度
      totalHeight += getPaddingTop() + getPaddingBottom();// 加上padding
      // 设置布局的宽高,宽度直接采用父view传递过来的最大宽度,而不用考虑子view是否填满宽度,因为该布局的特性就是填满一行后,再换行
      // 高度根据设置的模式来决定采用所有子View的高度之和还是采用父view传递过来的高度
      setMeasuredDimension(totalWidth, resolveSize(totalHeight, heightMeasureSpec));
   }

   public int getLineCount(){
      return count;
   }

   @Override
   protected void onLayout(boolean changed, int l, int t, int r, int b) {
      if (!mNeedLayout || changed) {// 没有发生改变就不重新布局
         mNeedLayout = false;
         int left = getPaddingLeft();// 获取最初的左上点
         int top = getPaddingTop()+20;
         final int linesCount = mLines.size();
         count = mLines.size();
         for (int i = 0; i < linesCount; i++) {
            final Line oneLine = mLines.get(i);
            oneLine.layoutView(left, top);// 布局每一行
            top += oneLine.mHeight + mVerticalSpacing;// 为下一行的top赋值
         }
      }
   }

   @Override
   protected void onDraw(Canvas canvas) {
      if (needDivider)
         drawDividers(canvas);
   }

   void drawDividers(Canvas canvas) {
      int viewPaddingHorizontal =dp2px(2);
      int viewPaddingVertical =dp2px(2);

      for (int i = 0; i < mLines.size(); i++) {
         List<View> views = mLines.get(i).getViews();
         int startLineX = 0;
         int startLineY = 0;
         int endLineX = 0;
         int endLineY = 0;
         for (int j = 0; j < views.size(); j++) {
            View view = views.get(j);
            int top = view.getTop() - mVerticalSpacing / 2 + viewPaddingVertical;
            int bottom = view.getBottom() + mVerticalSpacing / 2 - viewPaddingVertical;
            int left = view.getLeft() - mHorizontalSpacing / 2 + viewPaddingHorizontal;
            int right = view.getRight() + mHorizontalSpacing / 2 - viewPaddingHorizontal;

            if (j == 0) {
               startLineX = left;
               startLineY = bottom;
            }

            if (j == views.size() - 1) {
               endLineX = right;
               endLineY = bottom;
            }
            if (j < views.size() - 1) {
               if (needRightDivider)
                  drawLine(canvas, right, top, right, bottom);
            } else {
               if (needBottomDivider)
                  drawLine(canvas, startLineX, startLineY, endLineX, endLineY);
            }

         }
      }

   }

   void drawLine(Canvas canvas, int startX, int startY, int endX, int endY) {
      canvas.drawLine(startX, startY, endX, endY, paint);
   }

   /** 还原所有数据 */
   private void restoreLine() {
      mLines.clear();
      mLine = new Line();
      mUsedWidth = 0;
   }

   /** 新增加一行 */
   private boolean newLine() {
      mLines.add(mLine);
      if (mMaxLinesCount == -1 || mLines.size() < mMaxLinesCount) {
         mLine = new Line();
         mUsedWidth = 0;
         return true;
      }
      return false;
   }
    public int dp2px(float dpValue) {
        final float scale = this.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
   public void setPaint(Paint paint) {
      this.paint = paint;
   }

   public void needDivider(boolean flag) {
      this.needDivider = flag;
      this.needBottomDivider = flag;
      this.needRightDivider = flag;
      setWillNotDraw(false);
   }

   public void needRightDivider(boolean flag) {
      this.needDivider = flag;
      this.needRightDivider = flag;
      setWillNotDraw(false);
   }

   public void needBottomDivider(boolean flag) {
      this.needDivider = flag;
      this.needBottomDivider = flag;
      setWillNotDraw(false);
   }


   /**
    * 代表着一行,封装了一行所占高度,该行子View的集合,以及所有View的宽度总和
    */
   public class Line {
      int mWidth = 0;// 该行中所有的子View累加的宽度
      int mHeight = 0;// 该行中所有的子View中高度的那个子View的高度
      List<View> views = new ArrayList<View>();

      public void addView(View view) {// 往该行中添加一个
         views.add(view);
         mWidth += view.getMeasuredWidth();
         int childHeight = view.getMeasuredHeight();
         mHeight = mHeight < childHeight ? childHeight : mHeight;// 高度等于一行中最高的View
      }

      public int getViewCount() {
         return views.size();
      }

      public List<View> getViews() {
         return views;
      }

      public void layoutView(int l, int t) {// 布局
         int left = l;
         int top = t;
         int count = getViewCount();
         // 总宽度
         int layoutWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
         // 剩余的宽度,是除了View和间隙的剩余空间
         int surplusWidth = layoutWidth - mWidth - mHorizontalSpacing * (count - 1);
         if (surplusWidth >= 0) {// 剩余空间
            // 采用float类型数据计算后四舍五入能减少int类型计算带来的误差
//          int splitSpacing = (int) (surplusWidth / count + 0.5);
            for (int i = 0; i < count; i++) {
               final View view = views.get(i);
               int childWidth = view.getMeasuredWidth();
               int childHeight = view.getMeasuredHeight();
               // 计算出每个View的顶点,是由最高的View和该View高度的差值除以2
               int topOffset = (int) ((mHeight - childHeight) / 2.0 + 0.5);
               if (topOffset < 0) {
                  topOffset = 0;
               }
/*             // 把剩余空间平均到每个View上
               childWidth = childWidth + splitSpacing;
               view.getLayoutParams().width = childWidth;
               if (splitSpacing > 0) {// View的长度改变了,需要重新measure
                  int widthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
                  int heightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY);
                  view.measure(widthMeasureSpec, heightMeasureSpec);
               }*/
               // 布局View
               view.layout(left, top + topOffset, left + childWidth, top + topOffset + childHeight);
               left += childWidth + mHorizontalSpacing; // 为下一个View的left赋值
            }
         } else {
            if (count == 1) {
               View view = views.get(0);
               view.layout(left, top, left + view.getMeasuredWidth(), top + view.getMeasuredHeight());
            } else {
               // 走到这里来,应该是代码出问题了,目前按照逻辑来看,是不可能走到这一步
            }
         }
      }
   }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值