長度自適應 自動換行Flowlayout

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

/**
 * 根據子View的寬度排列的流式佈局
 */
public class FlowLayout extends ViewGroup {
   public FlowLayout(Context context) {
      super(context);
   }

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

   public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
      super(context, attrs, defStyleAttr);
   }

   /**
    * 父容器生成 子view 的佈局LayoutParams;
    * 一句話道出LayoutParams的本質:LayoutParams是Layout提供給其中的Children使用的。
    * 如果要自定義ViewGroup支持子控件的layout_margin參數,
    * 則自定義的ViewGroup類必須重載generateLayoutParams()函數,
    * 並且在該函數中返回一個ViewGroup.MarginLayoutParams派生類對象,這樣才能使用margin參數。
    */
   @Override
   protected LayoutParams generateLayoutParams(LayoutParams p) {
      return new MarginLayoutParams(p);
   }

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

   @Override
   protected LayoutParams generateDefaultLayoutParams() {
      return new MarginLayoutParams(LayoutParams.MATCH_PARENT,
            LayoutParams.MATCH_PARENT);
   }

   @SuppressLint("DrawAllocation")
   @Override
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
      int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
      int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);
      int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);

      int lineWidth = 0;
      int lineHeight = 0;
      int height = 0;
      int width = 0;
      int count = getChildCount();
      for (int i = 0; i < count; i++) {
         View child = getChildAt(i);
         measureChild(child, widthMeasureSpec, heightMeasureSpec);
         // 如果忘記重寫generateLayoutParams,則hild.getLayoutParams()將不是MarginLayoutParams的實例
         // 在強制轉換時就會出錯,此時我們把左右間距設置爲0,但由於在計算佈局寬高時沒有加上間距值,
         // 就是計算出的寬高要比實際小,所以是onLayout時就會出錯
         MarginLayoutParams lp = null;
         if (child.getLayoutParams() instanceof MarginLayoutParams) {
            lp = (MarginLayoutParams) child.getLayoutParams();
         } else {
            lp = new MarginLayoutParams(0, 0);
         }
         int childWidth = child.getMeasuredWidth() + lp.leftMargin
               + lp.rightMargin;
         int childHeight = child.getMeasuredHeight() + lp.topMargin
               + lp.bottomMargin;

         if (lineWidth + childWidth > measureWidth) {
            // 需要換行
            width = Math.max(lineWidth, width);
            height += lineHeight;
            // 因爲由於盛不下當前控件,而將此控件調到下一行,所以將此控件的高度和寬度初始化給lineHeight、lineWidth
            lineHeight = childHeight;
            lineWidth = childWidth;
         } else {
            // 否則累加值lineWidth,lineHeight取最大高度
            lineHeight = Math.max(lineHeight, childHeight);
            lineWidth += childWidth;
         }

         // 最後一行是不會超出width範圍的,所以要單獨處理
         if (i == count - 1) {
            height += lineHeight;
            width = Math.max(width, lineWidth);
         }
      }
      /*
       * 當屬性是MeasureSpec.EXACTLY時,那麼它的高度就是確定的,
       * 只有當是wrap_content時,根據內部控件的大小來確定它的大小時,
       * 大小是不確定的,屬性是AT_MOST,此時,就需要我們自己計算它的應當的大小,並設置進去
       */
      setMeasuredDimension(
            (measureWidthMode == MeasureSpec.EXACTLY) ? measureWidth
                  : width,
            (measureHeightMode == MeasureSpec.EXACTLY) ? measureHeight
                  : height);
   }

   @Override
   protected void onLayout(boolean changed, int l, int t, int r, int b) {
      int count = getChildCount();
      int lineWidth = 0;
      int lineHeight = 0;
      int top = 0, left = 0;
      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 > getMeasuredWidth()) {
            // 如果換行,當前控件將跑到下一行,從最左邊開始,所以left就是0,而top則需要加上上一行的行高,纔是這個控件的top點;
            top += lineHeight;
            left = 0;
            // 同樣,重新初始化lineHeight和lineWidth
            lineHeight = childHeight;
            lineWidth = childWidth;
         } else {
            lineHeight = Math.max(lineHeight, childHeight);
            lineWidth += childWidth;
         }
         // 計算childView的left,top,right,bottom
         int lc = left + lp.leftMargin;
         int tc = top + lp.topMargin;
         int rc = lc + child.getMeasuredWidth();
         int bc = tc + child.getMeasuredHeight();
         child.layout(lc, tc, rc, bc);
         // 將left置爲下一子控件的起始點
         left += childWidth;
      }
   }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章