如何简单的自定义一个自动换行的流式布局(TagLayout)

批注 2020-02-10 212314.png

先上完整代码:

public class TagLayout extends ViewGroup {

  List childrenBounds = new ArrayList<>();

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

  @Override
  protected void onLayout(boolean changed, int l, int t, int r, int b) {
    for (int i = 0; i < getChildCount(); i++) {
      View child = getChildAt(i);
      Rect childBound = childrenBounds.get(i);
      child.layout(childBound.left, childBound.top, childBound.right, childBound.bottom);
    }
  }

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

    int lineMaxWidth = 0;
    int heightUse = 0;
    int widthUse = 0;
    int lineMaxHeight = 0;

    int specMode=MeasureSpec.getMode(widthMeasureSpec);

    int specWidth = MeasureSpec.getSize(widthMeasureSpec);

    for (int i = 0; i < getChildCount(); i++) {
      View child = getChildAt(i);

      measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, heightUse);

      if (specMode!=MeasureSpec.UNSPECIFIED&&widthUse + child.getMeasuredWidth() > specWidth) {
        // 另起一行
        widthUse = 0;
        heightUse += lineMaxHeight;
        lineMaxHeight=0;
        measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, heightUse);
      }

      Rect childBound;
      if (childrenBounds.size() <= i) {
        childBound = new Rect();
        childrenBounds.add(childBound);
      } else {
        childBound = childrenBounds.get(i);
      }

      childBound.set(
          widthUse,
          heightUse,
          widthUse + getMeasuredWidth(),
          heightUse + getMeasuredHeight());

      widthUse += child.getMeasuredWidth();
      lineMaxWidth = Math.max(lineMaxWidth, widthUse);
      lineMaxHeight = Math.max(lineMaxHeight, child.getMeasuredHeight());
    }

    int width = lineMaxWidth;
    int height = lineMaxHeight+heightUse;
    setMeasuredDimension(width, height);
  }

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

## 准备工作
定义一个容器来存储子View的位置信息:
```java
  List childrenBounds = new ArrayList<>();

先从onMeasure()阶段开始

定义了四个量:

    int widthUse = 0;//使用了多少宽度
    int heightUse = 0;//使用了多少高度
    int lineMaxWidth = 0; //当前最大宽度
    int lineMaxHeight = 0;//当前最大高度

这个布局是个横向排列的过程,我们用一个widthUse来记录当前行横向使用的宽度,则计算下个子View位置时候,只需在此基础上累加即可。
lineMaxHeight用来记录当前行的子View中的最大高度,当换行时候传递给heightUse,使下一阶段的子View添加下沉高度。

这段代码是什么意义:

   measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, heightUse);

这其实是安卓帮我们做了一个自动计算的过程:

    LayoutParams layoutParams = child.getLayoutParams();

      int specWidthMode = MeasureSpec.getMode(widthMeasureSpec);
      int specWidthSize = MeasureSpec.getSize(widthMeasureSpec);

      int childWidthMode;
      int childWidthSize;

      switch (layoutParams.width) {
        case LayoutParams.MATCH_PARENT:
          switch (specWidthMode) {
            case MeasureSpec.EXACTLY:
            case MeasureSpec.AT_MOST:
              childWidthMode = MeasureSpec.EXACTLY;
              childWidthSize = specWidthSize - useWidth;
              break;
            case MeasureSpec.UNSPECIFIED:
              childWidthMode = MeasureSpec.UNSPECIFIED;
              childWidthSize = 0;
              break;
          }
          break;
        case LayoutParams.WRAP_CONTENT:
          break;
      }

可以类比为如上的代码,根据开发者传入的参数类型进行数值的自动适配

onLayout()阶段

循环设置布局参数

    for (int i = 0; i < getChildCount(); i++) {
      View child = getChildAt(i);
      Rect childBound = childrenBounds.get(i);
      child.layout(childBound.left, childBound.top, childBound.right, childBound.bottom);
    }

你可能感兴趣的:(如何简单的自定义一个自动换行的流式布局(TagLayout))