【View】FlowLayout

drawing

FlowLayout 使用场景

热门标签,历史搜索记录

官方的 FlowLayout

位于 com.google.android.material.internal 包下的 FlowLayout 无法直接使用。需要单独复制一份代码出来。

//设置item距离
app:itemSpacing="8dp"
//设置行间距
app:lineSpacing="8dp"

源码

基于 implementation 'com.google.android.material:material:1.7.0'

/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.block.androidui.widget;

import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.core.view.MarginLayoutParamsCompat;
import androidx.core.view.ViewCompat;

import com.google.android.material.R;

/**
 * Horizontally lay out children until the row is filled and then moved to the next line. Call
 * {@link FlowLayout#setSingleLine(boolean)} to disable reflow and lay all children out in one line.
 *
 * @hide
 */
public class FlowLayout extends ViewGroup {
  private int lineSpacing;
  private int itemSpacing;
  private boolean singleLine;
  private int rowCount;

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

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

  public FlowLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    singleLine = false;
    loadFromAttributes(context, attrs);
  }

  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  public FlowLayout(
      @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    singleLine = false;
    loadFromAttributes(context, attrs);
  }

  private void loadFromAttributes(@NonNull Context context, @Nullable AttributeSet attrs) {
    final TypedArray array =
        context.getTheme().obtainStyledAttributes(attrs, R.styleable.FlowLayout, 0, 0);
    lineSpacing = array.getDimensionPixelSize(R.styleable.FlowLayout_lineSpacing, 0);
    itemSpacing = array.getDimensionPixelSize(R.styleable.FlowLayout_itemSpacing, 0);
    array.recycle();
  }

  protected int getLineSpacing() {
    return lineSpacing;
  }

  protected void setLineSpacing(int lineSpacing) {
    this.lineSpacing = lineSpacing;
  }

  protected int getItemSpacing() {
    return itemSpacing;
  }

  protected void setItemSpacing(int itemSpacing) {
    this.itemSpacing = itemSpacing;
  }

  /** Returns whether this chip group is single line or reflowed multiline. */
  public boolean isSingleLine() {
    return singleLine;
  }

  /** Sets whether this chip group is single line, or reflowed multiline. */
  public void setSingleLine(boolean singleLine) {
    this.singleLine = singleLine;
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    final int width = MeasureSpec.getSize(widthMeasureSpec);
    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);

    final int height = MeasureSpec.getSize(heightMeasureSpec);
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

    final int maxWidth =
        widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.EXACTLY
            ? width
            : Integer.MAX_VALUE;

    int childLeft = getPaddingLeft();
    int childTop = getPaddingTop();
    int childBottom = childTop;
    int childRight = childLeft;
    int maxChildRight = 0;
    final int maxRight = maxWidth - getPaddingRight();
    for (int i = 0; i < getChildCount(); i++) {
      View child = getChildAt(i);

      if (child.getVisibility() == View.GONE) {
        continue;
      }
      measureChild(child, widthMeasureSpec, heightMeasureSpec);

      LayoutParams lp = child.getLayoutParams();
      int leftMargin = 0;
      int rightMargin = 0;
      if (lp instanceof MarginLayoutParams) {
        MarginLayoutParams marginLp = (MarginLayoutParams) lp;
        leftMargin += marginLp.leftMargin;
        rightMargin += marginLp.rightMargin;
      }

      childRight = childLeft + leftMargin + child.getMeasuredWidth();

      // If the current child's right bound exceeds Flowlayout's max right bound and flowlayout is
      // not confined to a single line, move this child to the next line and reset its left bound to
      // flowlayout's left bound.
      if (childRight > maxRight && !isSingleLine()) {
        childLeft = getPaddingLeft();
        childTop = childBottom + lineSpacing;
      }

      childRight = childLeft + leftMargin + child.getMeasuredWidth();
      childBottom = childTop + child.getMeasuredHeight();

      // Updates Flowlayout's max right bound if current child's right bound exceeds it.
      if (childRight > maxChildRight) {
        maxChildRight = childRight;
      }

      childLeft += (leftMargin + rightMargin + child.getMeasuredWidth()) + itemSpacing;

      // For all preceding children, the child's right margin is taken into account in the next
      // child's left bound (childLeft). However, childLeft is ignored after the last child so the
      // last child's right margin needs to be explicitly added to Flowlayout's max right bound.
      if (i == (getChildCount() - 1)) {
        maxChildRight += rightMargin;
      }
    }

    maxChildRight += getPaddingRight();
    childBottom += getPaddingBottom();

    int finalWidth = getMeasuredDimension(width, widthMode, maxChildRight);
    int finalHeight = getMeasuredDimension(height, heightMode, childBottom);
    setMeasuredDimension(finalWidth, finalHeight);
  }

  private static int getMeasuredDimension(int size, int mode, int childrenEdge) {
    switch (mode) {
      case MeasureSpec.EXACTLY:
        return size;
      case MeasureSpec.AT_MOST:
        return Math.min(childrenEdge, size);
      default: // UNSPECIFIED:
        return childrenEdge;
    }
  }

  @Override
  protected void onLayout(boolean sizeChanged, int left, int top, int right, int bottom) {
    if (getChildCount() == 0) {
      // Do not re-layout when there are no children.
      rowCount = 0;
      return;
    }
    rowCount = 1;

    boolean isRtl = ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL;
    int paddingStart = isRtl ? getPaddingRight() : getPaddingLeft();
    int paddingEnd = isRtl ? getPaddingLeft() : getPaddingRight();
    int childStart = paddingStart;
    int childTop = getPaddingTop();
    int childBottom = childTop;
    int childEnd;

    final int maxChildEnd = right - left - paddingEnd;

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

      if (child.getVisibility() == View.GONE) {
        child.setTag(R.id.row_index_key, -1);
        continue;
      }

      LayoutParams lp = child.getLayoutParams();
      int startMargin = 0;
      int endMargin = 0;
      if (lp instanceof MarginLayoutParams) {
        MarginLayoutParams marginLp = (MarginLayoutParams) lp;
        startMargin = MarginLayoutParamsCompat.getMarginStart(marginLp);
        endMargin = MarginLayoutParamsCompat.getMarginEnd(marginLp);
      }

      childEnd = childStart + startMargin + child.getMeasuredWidth();

      if (!singleLine && (childEnd > maxChildEnd)) {
        childStart = paddingStart;
        childTop = childBottom + lineSpacing;
        rowCount++;
      }
      child.setTag(R.id.row_index_key, rowCount - 1);

      childEnd = childStart + startMargin + child.getMeasuredWidth();
      childBottom = childTop + child.getMeasuredHeight();

      if (isRtl) {
        child.layout(
            maxChildEnd - childEnd, childTop, maxChildEnd - childStart - startMargin, childBottom);
      } else {
        child.layout(childStart + startMargin, childTop, childEnd, childBottom);
      }

      childStart += (startMargin + endMargin + child.getMeasuredWidth()) + itemSpacing;
    }
  }

  protected int getRowCount() {
    return rowCount;
  }

  /** Gets the row index of the child, primarily for accessibility.   */
  public int getRowIndex(@NonNull View child) {
    Object index = child.getTag(R.id.row_index_key);
    if (!(index instanceof Integer)) {
      return -1;
    }
    return (int) index;
  }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值