/*
* Copyright 2016 Google Inc. All rights reserved.
*
* 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
*
* http://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.sxjs.common.widget.flexboxlayout;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.v4.view.MarginLayoutParamsCompat;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.util.SparseIntArray;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.sxjs.common.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* A layout that arranges its children in a way its attributes can be specified like the
* CSS Flexible Box Layout Module.
* This class extends the {@link ViewGroup} like other layout classes such as {@link LinearLayout}
* or {@link RelativeLayout}, the attributes can be specified from a layout XML or from code.
*
* The supported attributes that you can use are:
* <ul>
* <li>{@code flexDirection}</li>
* <li>{@code flexWrap}</li>
* <li>{@code justifyContent}</li>
* <li>{@code alignItems}</li>
* <li>{@code alignContent}</li>
* <li>{@code showDivider}</li>
* <li>{@code showDividerHorizontal}</li>
* <li>{@code showDividerVertical}</li>
* <li>{@code dividerDrawable}</li>
* <li>{@code dividerDrawableHorizontal}</li>
* <li>{@code dividerDrawableVertical}</li>
* </ul>
* for the FlexboxLayout.
*
* And for the children of the FlexboxLayout, you can use:
* <ul>
* <li>{@code layout_order}</li>
* <li>{@code layout_flexGrow}</li>
* <li>{@code layout_flexShrink}</li>
* <li>{@code layout_flexBasisPercent}</li>
* <li>{@code layout_alignSelf}</li>
* <li>{@code layout_minWidth}</li>
* <li>{@code layout_minHeight}</li>
* <li>{@code layout_maxWidth}</li>
* <li>{@code layout_maxHeight}</li>
* <li>{@code layout_wrapBefore}</li>
* </ul>
*/
public class FlexboxLayout extends ViewGroup {
@IntDef({FLEX_DIRECTION_ROW, FLEX_DIRECTION_ROW_REVERSE, FLEX_DIRECTION_COLUMN,
FLEX_DIRECTION_COLUMN_REVERSE})
@Retention(RetentionPolicy.SOURCE)
public @interface FlexDirection {
}
public static final int FLEX_DIRECTION_ROW = 0;
public static final int FLEX_DIRECTION_ROW_REVERSE = 1;
public static final int FLEX_DIRECTION_COLUMN = 2;
public static final int FLEX_DIRECTION_COLUMN_REVERSE = 3;
/**
* The direction children items are placed inside the Flexbox layout, it determines the
* direction of the main axis (and the cross axis, perpendicular to the main axis).
* <ul>
* <li>
* {@link #FLEX_DIRECTION_ROW}: Main axis direction -> horizontal. Main start to
* main end -> Left to right (in LTR languages).
* Cross start to cross end -> Top to bottom
* </li>
* <li>
* {@link #FLEX_DIRECTION_ROW_REVERSE}: Main axis direction -> horizontal. Main start
* to main end -> Right to left (in LTR languages). Cross start to cross end ->
* Top to bottom.
* </li>
* <li>
* {@link #FLEX_DIRECTION_COLUMN}: Main axis direction -> vertical. Main start
* to main end -> Top to bottom. Cross start to cross end ->
* Left to right (In LTR languages).
* </li>
* <li>
* {@link #FLEX_DIRECTION_COLUMN_REVERSE}: Main axis direction -> vertical. Main start
* to main end -> Bottom to top. Cross start to cross end -> Left to right
* (In LTR languages)
* </li>
* </ul>
* The default value is {@link #FLEX_DIRECTION_ROW}.
*/
private int mFlexDirection;
@IntDef({FLEX_WRAP_NOWRAP, FLEX_WRAP_WRAP, FLEX_WRAP_WRAP_REVERSE})
@Retention(RetentionPolicy.SOURCE)
public @interface FlexWrap {
}
public static final int FLEX_WRAP_NOWRAP = 0;
public static final int FLEX_WRAP_WRAP = 1;
public static final int FLEX_WRAP_WRAP_REVERSE = 2;
/**
* This attribute controls whether the flex container is single-line or multi-line, and the
* direction of the cross axis.
* <ul>
* <li>{@link #FLEX_WRAP_NOWRAP}: The flex container is single-line.</li>
* <li>{@link #FLEX_WRAP_WRAP}: The flex container is multi-line.</li>
* <li>{@link #FLEX_WRAP_WRAP_REVERSE}: The flex container is multi-line. The direction of the
* cross axis is opposed to the direction as the {@link #FLEX_WRAP_WRAP}</li>
* </ul>
* The default value is {@link #FLEX_WRAP_NOWRAP}.
*/
private int mFlexWrap;
@IntDef({JUSTIFY_CONTENT_FLEX_START, JUSTIFY_CONTENT_FLEX_END, JUSTIFY_CONTENT_CENTER,
JUSTIFY_CONTENT_SPACE_BETWEEN, JUSTIFY_CONTENT_SPACE_AROUND})
@Retention(RetentionPolicy.SOURCE)
public @interface JustifyContent {
}
public static final int JUSTIFY_CONTENT_FLEX_START = 0;
public static final int JUSTIFY_CONTENT_FLEX_END = 1;
public static final int JUSTIFY_CONTENT_CENTER = 2;
public static final int JUSTIFY_CONTENT_SPACE_BETWEEN = 3;
public static final int JUSTIFY_CONTENT_SPACE_AROUND = 4;
/**
* This attribute controls the alignment along the main axis.
* The default value is {@link #JUSTIFY_CONTENT_FLEX_START}.
*/
private int mJustifyContent;
@IntDef({ALIGN_ITEMS_FLEX_START, ALIGN_ITEMS_FLEX_END, ALIGN_ITEMS_CENTER,
ALIGN_ITEMS_BASELINE, ALIGN_ITEMS_STRETCH})
@Retention(RetentionPolicy.SOURCE)
public @interface AlignItems {
}
public static final int ALIGN_ITEMS_FLEX_START = 0;
public static final int ALIGN_ITEMS_FLEX_END = 1;
public static final int ALIGN_ITEMS_CENTER = 2;
public static final int ALIGN_ITEMS_BASELINE = 3;
public static final int ALIGN_ITEMS_STRETCH = 4;
/**
* This attribute controls the alignment along the cross axis.
* The default value is {@link #ALIGN_ITEMS_STRETCH}.
*/
private int mAlignItems;
@IntDef({ALIGN_CONTENT_FLEX_START, ALIGN_CONTENT_FLEX_END, ALIGN_CONTENT_CENTER,
ALIGN_CONTENT_SPACE_BETWEEN, ALIGN_CONTENT_SPACE_AROUND, ALIGN_CONTENT_STRETCH})
@Retention(RetentionPolicy.SOURCE)
public @interface AlignContent {
}
public static final int ALIGN_CONTENT_FLEX_START = 0;
public static final int ALIGN_CONTENT_FLEX_END = 1;
public static final int ALIGN_CONTENT_CENTER = 2;
public static final int ALIGN_CONTENT_SPACE_BETWEEN = 3;
public static final int ALIGN_CONTENT_SPACE_AROUND = 4;
public static final int ALIGN_CONTENT_STRETCH = 5;
/**
* This attribute controls the alignment of the flex lines in the flex container.
* The default value is {@link #ALIGN_CONTENT_STRETCH}.
*/
private int mAlignContent;
/**
* The int definition to be used as the arguments for the {@link #setShowDivider(int)},
* {@link #setShowDividerHorizontal(int)} or {@link #setShowDividerVertical(int)}.
* One or more of the values (such as
* {@link #SHOW_DIVIDER_BEGINNING} | {@link #SHOW_DIVIDER_MIDDLE}) can be passed to those set
* methods.
*/
@IntDef(flag = true,
value = {
SHOW_DIVIDER_NONE,
SHOW_DIVIDER_BEGINNING,
SHOW_DIVIDER_MIDDLE,
SHOW_DIVIDER_END
})
@Retention(RetentionPolicy.SOURCE)
public @interface DividerMode {
}
/** Constant to how no dividers */
public static final int SHOW_DIVIDER_NONE = 0;
/** Constant to show a divider at the beginning of the flex lines (or flex items). */
public static final int SHOW_DIVIDER_BEGINNING = 1;
/** Constant to show dividers between flex lines or flex items. */
public static final int SHOW_DIVIDER_MIDDLE = 1 << 1;
/** Constant to show a divider at the end of the flex lines or flex items. */
public static final int SHOW_DIVIDER_END = 1 << 2;
/** The drawable to be drawn for the horizontal dividers. */
private Drawable mDividerDrawableHorizontal;
/** The drawable to be drawn for the vertical dividers. */
private Drawable mDividerDrawableVertical;
/**
* Indicates the divider mode for the {@link #mDividerDrawableHorizontal}. The value needs to
* be the combination of the value of {@link #SHOW_DIVIDER_NONE},
* {@link #SHOW_DIVIDER_BEGINNING}, {@link #SHOW_DIVIDER_MIDDLE} and {@link #SHOW_DIVIDER_END}
*/
private int mShowDividerHorizontal;
/**
* Indicates the divider mode for the {@link #mDividerDrawableVertical}. The value needs to
* be the combination of the value of {@link #SHOW_DIVIDER_NONE},
* {@link #SHOW_DIVIDER_BEGINNING}, {@link #SHOW_DIVIDER_MIDDLE} and {@link #SHOW_DIVIDER_END}
*/
private int mShowDividerVertical;
/** The height of the {@link #mDividerDrawableHorizontal}. */
private int mDividerHorizontalHeight;
/** The width of the {@link #mDividerDrawableVertical}. */
private int mDividerVerticalWidth;
/**
* Holds reordered indices, which {@link LayoutParams#order} parameters are taken into account
*/
private int[] mReorderedIndices;
/**
* Caches the {@link LayoutParams#order} attributes for children views.
* Key: the index of the view ({@link #mReorderedIndices} isn't taken into account)
* Value: the value for the order attribute
*/
private SparseIntArray mOrderCache;
private List<FlexLine> mFlexLines = new ArrayList<>();
/**
* Holds the 'frozen' state of children during measure. If a view is frozen it will no longer
* expand or shrink regardless of flexGrow/flexShrink. Items are indexed by the child's
* reordered index.
*/
private boolean[] mChildrenFrozen;
public FlexboxLayout(Context context) {
this(context, null);
}
public FlexboxLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlexboxLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.FlexboxLayout, defStyleAttr, 0);
mFlexDirection = a.getInt(R.styleable.FlexboxLayout_flexDirection, FLEX_DIRECTION_ROW);
mFlexWrap = a.getInt(R.styleable.FlexboxLayout_flexWrap, FLEX_WRAP_NOWRAP);
mJustifyContent = a
.getInt(R.styleable.FlexboxLayout_justifyContent, JUSTIFY_CONTENT_FLEX_START);
mAlignItems = a.getInt(R.styleable.FlexboxLayout_alignItems, ALIGN_ITEMS_STRETCH);
mAlignContent = a.getInt(R.styleable.FlexboxLayout_alignContent, ALIGN_CONTENT_STRETCH);
Drawable drawable = a.getDrawable(R.styleable.FlexboxLayout_dividerDrawable);
if (drawable != null) {
setDividerDrawableHorizontal(drawable);
setDividerDrawableVertical(drawable);
}
Drawable drawableHorizontal = a
.getDrawable(R.styleable.FlexboxLayout_dividerDrawableHorizontal);
if (drawableHorizontal != null) {
setDividerDrawableHorizontal(drawableHorizontal);
}
Drawable drawableVertical = a
.getDrawable(R.styleable.FlexboxLayout_dividerDrawableVertical);
if (drawableVertical != null) {
setDividerDrawableVertical(drawableVertical);
}
int dividerMode = a.getInt(R.styleable.FlexboxLayout_showDivider, SHOW_DIVIDER_NONE);
if (dividerMode != SHOW_DIVIDER_NONE) {
mShowDividerVertical = dividerMode;
mShowDividerHorizontal = dividerMode;
}
int dividerModeVertical = a
.getInt(R.styleable.FlexboxLayout_showDividerVertical, SHOW_DIVIDER_NONE);
if (dividerModeVertical != SHOW_DIVIDER_NONE) {
mShowDividerVertical = dividerModeVertical;
}
int dividerModeHorizontal = a
.getInt(R.styleable.FlexboxLayout_showDividerHorizontal, SHOW_DIVIDER_NONE);
if (dividerModeHorizontal != SHOW_DIVIDER_NONE) {
mShowDividerHorizontal = dividerModeHorizontal;
}
a.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (isOrderChangedFromLastMeasurement()) {
mReorderedIndices = createReorderedIndices();
}
if (mChildrenFrozen == null || mChildrenFrozen.length < getChildCount()) {
mChildrenFrozen = new boolean[getChildCount()];
}
// TODO: Only calculate the children views which are affected from the last measure.
switch (mFlexDirection) {
case FLEX_DIRECTION_ROW: // Intentional fall through
case FLEX_DIRECTION_ROW_REVERSE:
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
break;
case FLEX_DIRECTION_COLUMN: // Intentional fall through
case FLEX_DIRECTION_COLUMN_REVERSE:
measureVertical(widthMeasureSpec, heightMeasureSpec);
break;
default:
throw new IllegalStateException(
"Invalid value for the flex direction is set: " + mFlexDirection);
}
Arrays.fill(mChildrenFrozen, false);
}
/**
* Returns a View, which is reordered by taking {@link LayoutParams#order} parameters
* into account.
*
* @param index the index of the view
* @return the reordered view, which {@link LayoutParams@order} is taken into account.
* If the index is negative or out of bounds of the number of contained views,
* returns {@code null}.
*/
public View getReorderedChildAt(int index) {
if (index < 0 || index >= mReorderedIndices.length) {
return null;
}
return getChildAt(mReorderedIndices[index]);
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
// Create an array for the reordered indices before the View is added in the parent
// ViewGroup since otherwise reordered indices won't be in effect before the
// FlexboxLayout's onMeasure is called.
// Because requestLayout is requested in the super.addView method.
mReorderedIndices = createReorderedIndices(child, index, params);
super.addView(child, index, params);
}
/**
* Create an array, which indicates the reordered indices that {@link LayoutParams#order}
* attributes are taken into account. This method takes a View before that is added as the
* parent ViewGroup's children.
*
* @param viewBeforeAdded the View instance before added to the array of children
* Views of the parent ViewGroup
* @param indexForViewBeforeAdded the index for the View before added to the array of the
* parent ViewGroup
* @param paramsForViewBeforeAdded the layout parameters for the View before added to the array
* of the parent ViewGroup
* @return an array which have the reordered indices
*/
private int[] createReorderedIndices(View viewBeforeAdded, int indexForViewBeforeAdded,
ViewGroup.LayoutParams paramsForViewBeforeAdded) {
int childCount = getChildCount();
List<Order> orders = createOrders(childCount);
Order orderForViewToBeAdded = new Order();
if (viewBeforeAdded != null
&& paramsForViewBeforeAdded instanceof FlexboxLayout.LayoutParams) {
orderForViewToBeAdded.order = ((LayoutParams) paramsForViewBeforeAdded).order;
} else {
orderForViewToBeAdded.order = LayoutParams.ORDER_DEFAULT;
}
if (indexForViewBeforeAdded == -1 || indexForViewBeforeAdded == childCount) {
orderForViewToBeAdded.index = childCount;
} else if (indexForViewBeforeAdded < getChildCount()) {
orderForViewToBeAdded.index = indexForViewBeforeAdded;
for (int i = indexForViewBeforeAdded; i < childCount; i++) {
orders.get(i).index++;
}
} else {
// This path is not expected since OutOfBoundException will be thrown in the ViewGroup
// But setting the index for fail-safe
orderForViewToBeAdded.index = childCount;
}
orders.add(orderForViewToBeAdded);
return sortOrdersIntoReorderedIndices(childCount + 1, orders);
}
/**
* Create an array, which indicates the reordered indices that {@link LayoutParams#order}
* attributes are taken into account.
*
* @return @return an array which have the reordered indices
*/
private int[] createReorderedIndices() {
int childCount = getChildCount();
List<Order> orders = createOrders(childCount);
return sortOrdersIntoReorderedIndices(childCount, orders);
}
private int[] sortOrdersIntoReorderedIndices(int childCount, List<Order> orders) {
Collections.sort(orders);
if (mOrderCache == null) {
mOrderCache = new SparseIntArray(childCount);
}
mOrderCache.clear();
int[] reorderedIndices = new int[childCount];
int i = 0;
for (Order order : orders) {
reorderedIndices[i] = order.index;
mOrderCache.append(i, order.order);
i++;
}
return reorderedIndices;
}
@NonNull
private List<Order> createOrders(int childCount) {
List<Order> orders = new ArrayList<>(childCount);
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
LayoutParams params = (LayoutParams) child.getLayoutParams();
Order order = new Order();
order.order = params.order;
order.index = i;
orders.add(order);
}
return orders;
}
/**
* Returns if any of the children's {@link LayoutParams#order} attributes are changed
* from the last measurement.
*
* @return {@code true} if changed from the last measurement, {@code false} otherwise.
*/
private boolean isOrderChangedFromLastMeasurement() {
int childCount = getChildCount();
if (mOrderCache == null) {
mOrderCache = new SparseIntArray(childCount);
}
if (mOrderCache.size() != childCount) {
return true;
}
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
if (view == null) {
continue;
}
LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (lp.order != mOrderCache.get(i)) {
return true;
}
}
return false;
}
/**
* Sub method for {@link #onMeasure(int, int)}, when the main axis direction is horizontal
* (either left to right or right to left).
*
* @param widthMeasureSpec horizontal space requirements as imposed by the parent
* @param heightMeasureSpec vertical space requirements as imposed by the parent
* @see #onMeasure(int, int)
* @see #setFlexDirection(int)
* @see #setFlexWrap(int)
* @see #setAlignItems(int)
* @see #setAlignContent(int)
*/
private void measureHorizontal(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int childState = 0;
mFlexLines.clear();
// Determine how many flex lines are needed in this layout by measuring each child.
// (Expand or shrink the view depending on the flexGrow and flexShrink attributes in a later
// loop)
{
int childCount = getChildCount();
int paddingStart = ViewCompat.getPaddingStart(this);
int paddingEnd = ViewCompat.getPaddingEnd(this);
int largestHeightInRow = Integer.MIN_VALUE;
FlexLine flexLine = new FlexLine();
// The index of the view in a same flex line.
int indexInFlexLine = 0;
flexLine.mMainSize = paddingStart + paddingEnd;
for (int i = 0; i < childCount; i++) {
View child = getReorderedChildAt(i);
if (child == null) {
addFlexLineIfLastFlexItem(i, childCount, flexLine);
continue;
} else if (child.getVisibility() == View.GONE) {
flexLine.mItemCount++;
flexLine.mGoneItemCount++;
addFlexLineIfLastFlexItem(i, childCount, flexLine);
continue;
}
FlexboxLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.alignSelf == LayoutParams.ALIGN_SELF_STRETCH) {
flexLine.mIndicesAlignSelfStretch.add(i);
}
int childWidth = lp.width;
if (lp.flexBasisPercent != LayoutParams.FLEX_BASIS_PERCENT_DEFAULT
&& widthMode == MeasureSpec.EXACTLY) {
childWidth = Math.round(widthSize * lp.flexBasisPercent);
// Use the dimension from the layout_width attribute if the widthMode is not
// MeasureSpec.EXACTLY even if any fraction value is set to
// layout_flexBasisPercent.
// There are likely quite few use cases where assigning any fraction values
// with widthMode is not MeasureSpec.EXACTLY (e.g. FlexboxLayout's layout_width
// is set to wrap_content)
}
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeft() + getPaddingRight() + lp.leftMargin
+ lp.rightMargin, childWidth);
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTop() + getPaddingBottom() + lp.topMargin
+ lp.bottomMargin, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
// Check the size constraint after the first measurement for the child
// To prevent the child's width/height violate the size constraints imposed by the
// {@link LayoutParams#minWidth}, {@link LayoutParams#minHeight},
// {@link LayoutParams#maxWidth} and {@link LayoutParams#maxHeight} attributes.
// E.g. When the child's layout_width is wrap_content the measured width may be
// less than the min width after the first measurement.
checkSizeConstraints(child);
childState = ViewCompat
.combineMeasuredStates(childState, ViewCompat.getMeasuredState(child));
largestHeightInRow = Math.max(largestHeightInRow,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
if (isWrapRequired(widthMode, widthSize, flexLine.mMainSize,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin, lp,
i, indexInFlexLine)) {
if (flexLine.getItemCountNotGone() > 0) {
addFlexLine(flexLine);
}
flexLine = new FlexLine();
flexLine.mItemCount = 1;
flexLine.mMainSize = paddingStart + paddingEnd;
largestHeightInRow = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
indexInFlexLine = 0;
} else {
flexLine.mItemCount++;
indexInFlexLine++;
}
flexLine.mMainSize += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
flexLine.mTotalFlexGrow += lp.flexGrow;
flexLine.mTotalFlexShrink += lp.flexShrink;
// Temporarily set the cross axis length as the largest child in the row
// Expand along the cross axis depending on the mAlignContent property if needed
// later
flexLine.mCrossSize = Math.max(flexLine.mCrossSize, largestHeightInRow);
// Check if the beginning or middle divider is required for the flex item
if (hasDividerBeforeChildAtAlongMainAxis(i, indexInFlexLine)) {
flexLine.mMainSize += mDividerVerticalWidth;
flexLine.mDividerLengthInMainSize += mDividerVerticalWidth;
}
if (mFlexWrap != FLEX_WRAP_WRAP_REVERSE) {
flexLine.mMaxBaseline = Math
.max(flexLine.mMaxBaseline, child.getBaseline() + lp.topMargin);
} else {
// if the flex wrap property is FLEX_WRAP_WRAP_REVERSE, calculate the
// baseline as the distance from the cross end and the baseline
// since the cross size calculation is based on the distance from the cross end
flexLine.mMaxBaseline = Math
.max(flexLine.mMaxBaseline,
child.getMeasuredHeight() - child.getBaseline()
+ lp.bottomMargin);
}
addFlexLineIfLastFlexItem(i, childCount, flexLine);
}
}
determineMainSize(mFlexDirection, widthMeasureSpec, heightMeasureSpec);
// TODO: Consider the case any individual child's alignSelf is set to ALIGN_SELF_BASELINE
if (mAlignItems == ALIGN_ITEMS_BASELINE) {
int viewIndex = 0;
for (FlexLine flexLine : mFlexLines) {
// The largest height value that also take the baseline shift into account
int largestHeightInLine = Integer.MIN_VALUE;
for (int i = viewIndex; i < viewIndex + flexLine.mItemCount; i++) {
View child = getReorderedChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (mFlexWrap != FLEX_WRAP_WRAP_REVERSE) {
int marginTop = flexLine.mMaxBaseline - child.getBaseline();
marginTop = Math.max(marginTop, lp.topMargin);
largestHeightInLine = Math.max(largestHeightInLine,
child.getHeight() + marginTop + lp.bottomMargin);
} else {
int marginBottom = flexLine.mMaxBaseline - child.getMeasuredHeight() +
child.getBaseline();
marginBottom = Math.max(marginBottom, lp.bottomMargin);
largestHeightInLine = Math.max(largestHeightInLine,
child.getHeight() + lp.topMargin + marginBottom);
}
}
flexLine.mCrossSize = largestHeightInLine;
viewIndex += flexLine.mItemCount;
}
}
determineCrossSize(mFlexDirection, widthMeasureSpec, heightMeasureSpec,
getPaddingTop() + getPaddingBottom());
// Now cross size for each flex line is determined.
// Expand the views if alignItems (or alignSelf in each child view) is set to stretch
stretchViews(mFlexDirection, mAlignItems);
setMeasuredDimensionForFlex(mFlexDirection, widthMeasureSpec, heightMeasureSpec,
childState);
}
/**
* Sub method for {@link #onMeasure(int, int)} when the main axis direction is vertical
* (either from top to bottom or bottom to top).
*
* @param widthMeasureSpec horizontal space requirements as imposed by the parent
* @param heightMeasureSpec vertical space requirements as imposed by the parent
* @see #onMeasure(int, int)
* @see #setFlexDirection(int)
* @see #setFlexWrap(int)
* @see #setAlignItems(int)
* @see #setAlignContent(int)
*/
private void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int childState = 0;
mFlexLines.clear();
// Determine how many flex lines are needed in this layout by measuring each child.
// (Expand or shrink the view depending on the flexGrow and flexShrink attributes in a later
// loop)
int childCount = getChildCount();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
int largestWidthInColumn = Integer.MIN_VALUE;
FlexLine flexLine = new FlexLine();
flexLine.mMainSize = paddingTop + paddingBottom;
// The index of the view in a same flex line.
int indexInFlexLine = 0;
for (int i = 0; i < childCount; i++) {
View child = getReorderedChildAt(i);
if (child == null) {
addFlexLineIfLastFlexItem(i, childCount, flexLine);
continue;
} else if (child.getVisibility() == View.GONE) {
flexLine.mItemCount++;
flexLine.mGoneItemCount++;
addFlexLineIfLastFlexItem(i, childCount, flexLine);
continue;
}
FlexboxLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.alignSelf == LayoutParams.ALIGN_SELF_STRETCH) {
flexLine.mIndicesAlignSelfStretch.add(i);
}
int childHeight = lp.height;
if (lp.flexBasisPercent != LayoutParams.FLEX_BASIS_PERCENT_DEFAULT
&& heightMode == MeasureSpec.EXACTLY) {
childHeight = Math.round(heightSize * lp.flexBasisPercent);
// Use the dimension from the layout_height attribute if the heightMode is not
// MeasureSpec.EXACTLY even if any fraction value is set to layout_flexBasisPercent.
// There are likely quite few use cases where assigning any fraction values
// with heightMode is not MeasureSpec.EXACTLY (e.g. FlexboxLayout's layout_height
// is set to wrap_content)
}
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeft() + getPaddingRight() + lp.leftMargin
+ lp.rightMargin, lp.width);
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTop() + getPaddingBottom() + lp.topMargin
+ lp.bottomMargin, childHeight);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
// Check the size constraint after the first measurement for the child
// To prevent the child's width/height violate the size constraints imposed by the
// {@link LayoutParams#minWidth}, {@link LayoutParams#minHeight},
// {@link LayoutParams#maxWidth} and {@link LayoutParams#maxHeight} attributes.
// E.g. When the child's layout_height is wrap_content the measured height may be
// less than the min height after the first measurement.
checkSizeConstraints(child);
childState = ViewCompat
.combineMeasuredStates(childState, ViewCompat.getMeasuredState(child));
largestWidthInColumn = Math.max(largestWidthInColumn,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
if (isWrapRequired(heightMode, heightSize, flexLine.mMainSize,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin, lp,
i, indexInFlexLine)) {
if (flexLine.getItemCountNotGone() > 0) {
addFlexLine(flexLine);
}
flexLine = new FlexLine();
flexLine.mItemCount = 1;
flexLine.mMainSize = paddingTop + paddingBottom;
largestWidthInColumn = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
indexInFlexLine = 0;
} else {
flexLine.mItemCount++;
indexInFlexLine++;
}
flexLine.mMainSize += child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
flexLine.mTotalFlexGrow += lp.flexGrow;
flexLine.mTotalFlexShrink += lp.flexShrink;
// Temporarily set the cross axis length as the largest child width in the column
// Expand along the cross axis depending on the mAlignContent property if needed
// later
flexLine.mCrossSize = Math.max(flexLine.mCrossSize, largestWidthInColumn);
if (hasDividerBeforeChildAtAlongMainAxis(i, indexInFlexLine)) {
flexLine.mMainSize += mDividerHorizontalHeight;
}
addFlexLineIfLastFlexItem(i, childCount, flexLine);
}
determineMainSize(mFlexDirection, widthMeasureSpec, heightMeasureSpec);
determineCrossSize(mFlexDirection, widthMeasureSpec, heightMeasureSpec,
getPaddingLeft() + getPaddingRight());
// Now cross size for each flex line is determined.
// Expand the views if alignItems (or alignSelf in each child view) is set to stretch
stretchViews(mFlexDirection, mAlignItems);
setMeasuredDimensionForFlex(mFlexDirection, widthMeasureSpec, heightMeasureSpec,
childState);
}
/**
* Checks if the view's width/height don't violate the minimum/maximum size constraints imposed
* by the {@link LayoutParams#minWidth}, {@link LayoutParams#minHeight},
* {@link LayoutParams#maxWidth} and {@link LayoutParams#maxHeight} attributes.
*
* @param view the view to be checked
*/
private void checkSizeConstraints(View view) {
boolean needsMeasure = false;
LayoutParams lp = (LayoutParams) view.getLayoutParams();
int childWidth = view.getMeasuredWidth();
int childHeight = view.getMeasuredHeight();
if (view.getMeasuredWidth() < lp.minWidth) {
needsMeasure = true;
childWidth = lp.minWidth;
} else if (view.getMeasuredWidth() > lp.maxWidth) {
needsMeasure = true;
childWidth = lp.maxWidth;
}
if (childHeight < lp.minHeight) {
needsMeasure = true;
childHeight = lp.minHeight;
} else if (childHeight > lp.maxHeight) {
needsMeasure = true;
childHeight = lp.maxHeight;
}
if (needsMeasure) {
view.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
}
}
private void addFlexLineIfLastFlexItem(int childIndex, int childCount, FlexLine flexLine) {
if (childIndex == childCount - 1 && flexLine.getItemCountNotGone() != 0) {
// Add the flex line if this item is the last item
addFlexLine(flexLine);
}
}
private void addFlexLine(FlexLine flexLine) {
// The size of the end divider isn't added until the flexLine is added to the flex container
// take the divider width (or height) into account when adding the flex line.
if (isMainAxisDirectionHorizontal(mFlexDirection)) {
if ((mShowDividerVertical & SHOW_DIVIDER_END) > 0) {
flexLine.mMainSize += mDividerVerticalWidth;
flexLine.mDividerLengthInMainSize += mDividerVerticalWidth;
}
} else {
if ((mShowDividerHorizontal & SHOW_DIVIDER_END) > 0) {
flexLine.mMainSize += mDividerHorizontalHeight;
flexLine.mDividerLengthInMainSize += mDividerHorizontalHeight;
}
}
mFlexLines.add(flexLine);
}
/**
* Determine the main size by expanding (shrinking if negative remaining free space is given)
* an individual child in each flex line if any children's flexGrow (or flexShrink if remaining
* space is negative) properties are set to non-zero.
*
* @param flexDirection the value of the flex direction
* @param widthMeasureSpec horizontal space requirements as imposed by the parent
* @param heightMeasureSpec vertical space requirements as imposed by the parent
* @see #setFlexDirection(int)
* @see #getFlexDirection()
*/
private void determineMainSize(@FlexDirection int flexDirection, int widthMeasureSpec,
int heightMeasureSpec) {
int mainSize;
int paddingAlongMainAxis;
switch (flexDirection) {
case FLEX_DIRECTION_ROW: // Intentional fall through
case FLEX_DIRECTION_ROW_REVERSE:
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
mainSize = widthSize;
} else {
mainSize = getLargestMainSize();
}
paddingAlongMainAxis = getPaddingLeft() + getPaddingRight();
break;
case FLEX_DIRECTION_COLUMN: // Intentional fall through
case FLEX_DIRECTION_COLUMN_REVERSE:
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode == MeasureSpec.EXACTLY) {
mainSize = heightSize;
} else {
mainSize = getLargestMainSize();
}
paddingAlongMainAxis = getPaddingTop() + getPaddingBottom();
break;
default:
throw new IllegalArgumentException("Invalid flex direction: " + flexDirection);
}
int childIndex = 0;
for (FlexLine flexLine : mFlexLines) {
if (flexLine.mMainSize < mainSize) {
childIndex = expandFlexItems(widthMeasureSpec, heightMeasureSpec, flexLine,
flexDirection, mainSize, paddingAlongMainAxis, childIndex, false);
} else {
childIndex = shrinkFlexItems(widthMeasureSpec, heightMeasureSpec, flexLine,
flexDirection, mainSize, paddingAlongMainAxis, childIndex, false);
}
}
}
/**
* Expand the flex items along the main axis based on the individual flexGrow attribute.
*
* @param widthMeasureSpec the horizontal space requirements as imposed by the parent
* @param heightMeasureSpec the vertical space requirements as imposed by the parent
* @param flexLine the flex line to which flex items belong
* @param flexDirection the flexDirection value for this FlexboxLayout
* @param maxMainSize the maximum main size. Expanded main size will be this size
* @param paddingAlongMainAxis the padding value along the main axis
* @param startIndex the start index of the children views to be expanded. This index
* needs to
* be an absolute index in the flex container (FlexboxLayout),
* not the relative index in the flex line.
* @param calledRecursively true if this method is called recursively, false otherwise
* @return the next index, the next flex line's first flex item starts from the returned index
* @see #getFlexDirection()
* @see #setFlexDirection(int)
* @see LayoutParams#flexGrow
*/
private int expandFlexItems(int widthMeasureSpec, int heightMeasureSpec, FlexLine flexLine,
@FlexDirection int flexDirection, int maxMainSize, int paddingAlongMainAxis,
int startIndex, boolean calledRecursively) {
int childIndex = startIndex;
if (flexLine.mTotalFlexGrow <= 0 || maxMainSize < flexLine.mMainSize) {
childIndex += flexLine.mItemCount;
return childIndex;
}
int sizeBeforeExpand = flexLine.mMainSize;
boolean needsReexpand = false;
float unitSpace = (maxMainSize - flexLine.mMainSize) / flexLine.mTotalFlexGrow;
flexLine.mMainSize = paddingAlongMainAxis + flexLine.mDividerLengthInMainSize;
// Setting the cross size of the flex line as the temporal value since the cross size of
// each flex item may be changed from the initial calculation
// (in the measureHorizontal/measureVertical method) even this method is part of the main
// size determination.
// E.g. If a TextView's layout_width is set to 0dp, layout_height is set to wrap_content,
// and layout_flexGrow is set to 1, the TextView is trying to expand to the vertical
// direction to enclose its content (in the measureHorizontal method), but
// the width will be expanded in this method. In that case, the height needs to be measured
// again with the expanded width.
if (!calledRecursively) {
flexLine.mCrossSize = Integer.MIN_VALUE;
}
int largestCrossSize = 0;
float accumulatedRoundError = 0;
for (int i = 0; i < flexLine.mItemCount; i++) {
View child = getReorderedChildAt(childIndex);
if (child == null) {
continue;
} else if (child.getVisibility() == View.GONE) {
childIndex++;
continue;
}
LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (isMainAxisDirectionHorizontal(flexDirection)) {
// The direction of the main axis is horizontal
if (!mChildrenFrozen[childIndex]) {
float rawCalculatedWidth = child.getMeasuredWidth() + unitSpace * lp.flexGrow;
if (i == flexLine.mItemCount - 1) {
rawCalculatedWidth += accumulatedRoundError;
accumulatedRoundError = 0;
}
int newWidth = Math.round(rawCalculatedWidth);
if (newWidth > lp.maxWidth) {
// This means the child can't expand beyond the value of the maxWidth attribute.
// To adjust the flex line length to the size of maxMainSize, remaining
// positive free space needs to be re-distributed to other flex items
// (children views). In that case, invoke this method again with the same
// startIndex.
needsReexpand = true;
newWidth = lp.maxWidth;
mChildrenFrozen[childIndex] = true;
flexLine.mTotalFlexGrow -= lp.flexGrow;
} else {
accumulatedRoundError += (rawCalculatedWidth - newWidth);
if (accumulatedRoundError > 1.0) {
newWidth += 1;
accumulatedRoundError -= 1.0;
} else if (accumulatedRoundError < -1.0) {
newWidth -= 1;
accumulatedRoundError += 1.0;
}
}
int childHeightMeasureSpec = getChildHeightMeasureSpec(heightMeasureSpec, lp);
child.measure(MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
childHeightMeasureSpec);
largestCrossSize = Math.max(largestCrossSize, child.getMeasuredHeight()
+ lp.topMargin + lp.bottomMargin);
}
flexLine.mMainSize += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
} else {
// The direction of the main axis is vertical
if (!mChildrenFrozen[childIndex]) {
float rawCalculatedHeight = child.getMeasuredHeight() + unitSpace * lp.flexGrow;
if (i == flexLine.mItemCount - 1) {
rawCalculatedHeight += accumulatedRoundError;
accumulatedRoundError = 0;
}
int newHeight = Math.round(rawCalculatedHeight);
if (newHeight > lp.maxHeight) {
// This means the child can't expand beyond the value of the maxHeight
// attribute.
// To adjust the flex line length to the size of maxMainSize, remaining
// positive free space needs to be re-distributed to other flex items
// (children views). In that case, invoke this method again with the same
// startIndex.
needsReexpand = true;
newHeight = lp.maxHeight;
mChildrenFrozen[childIndex] = true;
flexLine.mTotalFlexGrow -= lp.flexGrow;
} else {
accumulatedRoundError += (rawCalculatedHeight - newHeight);
if (accumulatedRoundError > 1.0) {
newHeight += 1;
accumulatedRoundError -= 1.0;
} else if (accumulatedRoundError < -1.0) {
newHeight -= 1;
accumulatedRoundError += 1.0;
}
}
int childWidthMeasureSpec = getChildWidthMeasureSpec(widthMeasureSpec, lp);
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
largestCrossSize = Math.max(largestCrossSize, child.getMeasuredWidth()
+ lp.leftMargin + lp.rightMargin);
}
flexLine.mMainSize += child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
}
flexLine.mCrossSize = Math.max(flexLine.mCrossSize, largestCrossSize);
childIndex++;
}
if (needsReexpand && sizeBeforeExpand != flexLine.mMainSize) {
// Re-invoke the method with the same startIndex to distribute the positive free space
// that wasn't fully distributed (because of maximum length constraint)
expandFlexItems(widthMeasureSpec, heightMeasureSpec, flexLine, flexDirection,
maxMainSize, paddingAlongMainAxis, startIndex, true);
}
return childIndex;
}
/**
* Shrink the flex items along the main axis based on the individual flexShrink attribute.
*
* @param widthMeasureSpec the horizontal space requirements as imposed by the parent
* @param heightMeasureSpec the vertical space requirements as imposed by the parent
* @param flexLine the flex line to which flex items belong
* @param flexDirection the flexDirection value for this FlexboxLayout
* @param maxMainSize the maximum main size. Shrank main size will be this size
* @param paddingAlongMainAxis the padding value along the main axis
* @param startIndex the start index of the children views to be shrank. This index
* needs to
* be an absolute index in the flex container (FlexboxLayout),
* not the relative index in the flex line.
* @param calledRecursively true if this method is called recursively, false otherwise
* @return the next index, the next flex line's first flex item starts from the returned index
* @see #getFlexDirection()
* @see #setFlexDirection(int)
* @see LayoutParams#flexShrink
*/
private int shrinkFlexItems(int widthMeasureSpec, int heightMeasureSpec, FlexLine flexLine,
@FlexDirection int flexDirection, int maxMainSize, int paddingAlongMainAxis,
int startIndex, boolean calledRecursively) {
int childIndex = startIndex;
int sizeBeforeShrink = flexLine.mMainSize;
if (flexLine.mTotalFlexShrink <= 0 || maxMainSize > flexLine.mMainSize) {
childIndex += flexLine.mItemCount;
return childIndex;
}
boolean needsReshrink = false;
float unitShrink = (flexLine.mMainSize - maxMainSize) / flexLine.mTotalFlexShrink;
float accumulatedRoundError = 0;
flexLine.mMainSize = paddingAlongMainAxis + flexLine.mDividerLengthInMainSize;
// Setting the cross size of the flex line as the temporal value since the cross size of
// each flex item may be changed from the initial calculation
// (in the measureHorizontal/measureVertical method) even this method is part of the main
// size determination.
// E.g. If a TextView's layout_width is set to 0dp, layout_height is set to wrap_content,
// and layout_flexGrow is set to 1, the TextView is trying to expand to the vertical
// direction to enclose its content (in the measureHorizontal method), but
// the width will be expanded in this method. In that case, the height needs to be measured
// again with the expanded width.
int largestCrossSize = 0;
if (!calledRecursively) {
flexLine.mCrossSize = Integer.MIN_VALUE;
}
for (int i = 0; i < flexLine.mItemCount; i++) {
View child = getReorderedChildAt(childIndex);
if (child == null) {
continue;
} else if (child.getVisibility() == View.GONE) {
childIndex++;
continue;
}
LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (isMainAxisDirectionHorizontal(flexDirection)) {
// The direction of main axis is horizontal
if (!mChildrenFrozen[childIndex]) {
float rawCalculatedWidth = child.getMeasuredWidth()
- unitShrink * lp.flexShrink;
if (i == flexLine.mItemCount - 1) {
rawCalculatedWidth += accumulatedRoundError;
accumulatedRoundError = 0;
}
int newWidth = Math.round(rawCalculatedWidth);
if (newWidth < lp.minWidth) {
// This means the child doesn't have enough space to distribute the negative
// free space. To adjust the flex line length down to the maxMainSize, remaining
// negative free space needs to be re-distributed to other flex items
// (children views). In that case, invoke this method again with the same
// startIndex.
needsReshrink = true;
newWidth = lp.minWidth;
mChildrenFrozen[childIndex] = true;
flexLine.mTotalFlexShrink -= lp.flexShrink;
} else {
accumulatedRoundError += (rawCalculatedWidth - newWidth);
if (accumulatedRoundError > 1.0) {
newWidth += 1;
accumulatedRoundError -= 1;
} else if (accumulatedRoundError < -1.0) {
newWidth -= 1;
accumulatedRoundError += 1;
}
}
int childHeightMeasureSpec = getChildHeightMeasureSpec(heightMeasureSpec, lp);
child.measure(MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
childHeightMeasureSpec);
largestCrossSize = Math.max(largestCrossSize, child.getMeasuredHeight()
+ lp.topMargin + lp.bottomMargin);
}
flexLine.mMainSize += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
} else {
// The direction of main axis is vertical
if (!mChildrenFrozen[childIndex]) {
float rawCalculatedHeight = child.getMeasuredHeight()
- unitShrink * lp.flexShrink;
if (i == flexLine.mItemCount - 1) {
rawCalculatedHeight += accumulatedRoundError;
accumulatedRoundError = 0;
}
int newHeight = Math.round(rawCalculatedHeight);
if (newHeight < lp.minHeight) {
// Need to invoke this method again like the case flex direction is vertical
needsReshrink = true;
newHeight = lp.minHeight;
mChildrenFrozen[childIndex] = true;
flexLine.mTotalFlexShrink -= lp.flexShrink;
} else {
accumulatedRoundError += (rawCalculatedHeight - newHeight);
if (accumulatedRoundError > 1.0) {
newHeight += 1;
accumulatedRoundError -= 1;
} else if (accumulatedRoundError < -1.0) {
newHeight -= 1;
accumulatedRoundError += 1;
}
}
int childWidthMeasureSpec = getChildWidthMeasureSpec(widthMeasureSpec, lp);
child.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
largestCrossSize = Math.max(largestCrossSize, child.getMeasuredWidth()
+ lp.leftMargin + lp.rightMargin);
}
flexLine.mMainSize += child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
}
flexLine.mCrossSize = Math.max(flexLine.mCrossSize, largestCrossSize);
childIndex++;
}
if (needsReshrink && sizeBeforeShrink != flexLine.mMainSize) {
// Re-invoke the method with the same startIndex to distribute the negative free space
// that wasn't fully distributed (because some views length were not enough)
shrinkFlexItems(widthMeasureSpec, heightMeasureSpec, flexLine, flexDirection,
maxMainSize, paddingAlongMainAxis, startIndex, true);
}
return childIndex;
}
private int getChildWidthMeasureSpec(int widthMeasureSpec, LayoutParams lp) {
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeft() + getPaddingRight() + lp.leftMargin
+ lp.rightMargin, lp.width);
int childWidth = MeasureSpec.getSize(childWidthMeasureSpec);
if (childWidth > lp.maxWidth) {
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.maxWidth,
MeasureSpec.getMode(childWidthMeasureSpec));
} else if (childWidth < lp.minWidth) {
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.minWidth,
MeasureSpec.getMode(childWidthMeasureSpec));
}
return childWidthMeasureSpec;
}
private int getChildHeightMeasureSpec(int heightMeasureSpec, LayoutParams lp) {
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
getPaddingTop() + getPaddingBottom() + lp.topMargin
+ lp.bottomMargin, lp.height);
int childHeight = MeasureSpec.getSize(childHeightMeasureSpec);
if (childHeight > lp.maxHeight) {
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.maxHeight,
MeasureSpec.getMode(childHeightMeasureSpec));
} else if (childHeight < lp.minHeight) {
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.minHeight,
MeasureSpec.getMode(childHeightMeasureSpec));
}
return childHeightMeasureSpec;
}
/**
* Determines the cross size (Calculate the length along the cross axis).
* Expand the cross size only if the height mode is MeasureSpec.EXACTLY, otherwise
* use the sum of cross sizes of all flex lines.
*
* @param flexDirection the flex direction attribute
* @param widthMeasureSpec horizontal space requirements as imposed by the parent
* @param heightMeasureSpec vertical space requirements as imposed by the parent
* @param paddingAlongCrossAxis the padding value for the FlexboxLayout along the cross axis
* @see #getFlexDirection()
* @see #setFlexDirection(int)
* @see #getAlignContent()
* @see #setAlignContent(int)
*/
private void determineCrossSize(int flexDirection, int widthMeasureSpec,
int heightMeasureSpec, int paddingAlongCrossAxis) {
// The MeasureSpec mode along the cross axis
int mode;
// The MeasureSpec size along the cross axis
int size;
switch (flexDirection) {
case FLEX_DIRECTION_ROW: // Intentional fall through
case FLEX_DIRECTION_ROW_REVERSE:
mode = MeasureSpec.getMode(heightMeasureSpec);
size = MeasureSpec.getSize(heightMeasureSpec);
break;
case FLEX_DIRECTION_COLUMN: // Intentional fall through
case FLEX_DIRECTION_COLUMN_REVERSE:
mode = MeasureSpec.getMode(widthMeasureSpec);
size = MeasureSpec.getSize(widthMeasureSpec);
break;
default:
throw new IllegalArgumentException("Invalid flex direction: " + flexDirection);
}
if (mode == MeasureSpec.EXACTLY) {
int totalCrossSize = getSumOfCrossSize() + paddingAlongCrossAxis;
if (mFlexLines.size() == 1) {
mFlexLines.get(0).mCrossSize = size - paddingAlongCrossAxis;
// alignContent property is valid only if the Flexbox has at least two lines
} else if (mFlexLines.size() >= 2 && totalCrossSize < size) {
switch (mAlignContent) {
case ALIGN_CONTENT_STRETCH: {
float freeSpaceUnit = (size - totalCrossSize) / (float) mFlexLines.size();
float accumulatedError = 0;
for (int i = 0, flexLinesSize = mFlexLines.size(); i < flexLinesSize; i++) {
FlexLine flexLine = mFlexLines.get(i);
float newCrossSizeAsFloat = flexLine.mCrossSize + freeSpaceUnit;
if (i == mFlexLines.size() - 1) {
newCrossSizeAsFloat += accumulatedError;
accumulatedError = 0;
}
int newCrossSize = Math.round(newCrossSizeAsFloat);
accumulatedError += (newCrossSizeAsFloat - newCrossSize);
if (accumulatedError > 1) {
newCrossSize += 1;
accumulatedError -= 1;
} else if (accumulatedError < -1) {
newCrossSize -= 1;
accumulatedError += 1;
}
flexLine.mCrossSize = newCrossSize;
}
break;
}
case ALIGN_CONTENT_SPACE_AROUND: {
// The value of free space along the cross axis which needs to be put on top
// and below the bottom of each flex line.
int spaceTopAndBottom = size - totalCrossSize;
// The number of spaces along the cross axis
int numberOfSpaces = mFlexLines.size() * 2;
spaceTopAndBottom = spaceTopAndBottom / numberOfSpaces;
List<FlexLine> newFlexLines = new ArrayList<>();
FlexLine dummySpaceFlexLine = new FlexLine();
dummySpaceFlexLine.mCrossSize = spaceTopAndBottom;
for (FlexLine flexLine : mFlexLines) {
newFlexLines.add(dummySpaceFlexLine);
newFlexLines.add(flexLine);
newFlexLines.add(dummySpaceFlexLine);
}
mFlexLines = newFlexLines;
break;
}
case ALIGN_CONTENT_SPACE_BETWEEN: {
// The value of free space along the cross axis between each flex line.
float spaceBetweenFlexLine = size - totalCrossSize;
int numberOfSpaces = mFlexLines.size() - 1;
spaceBetweenFlexLine = spaceBetweenFlexLine / (float) numberOfSpaces;
float accumulatedError = 0;
List<FlexLine> newFlexLines = new ArrayList<>();
for (int i = 0, flexLineSize = mFlexLines.size(); i < flexLineSize; i++) {
FlexLine flexLine = mFlexLines.get(i);
newFlexLines.add(flexLine);
if (i != mFlexLines.size() - 1) {
FlexLine dummySpaceFlexLine = new FlexLine();
if (i == mFlexLines.size() - 2) {
// The last dummy space block in the flex container.
// Adjust the cross size by the accumulated error.
dummySpaceFlexLine.mCrossSize = Math
.round(spaceBetweenFlexLine + accumulatedError);
accumulatedError = 0;
} else {
dummySpaceFlexLine.mCrossSize = Math
.round(spaceBetweenFlexLine);
}
accumulatedError += (spaceBetweenFlexLine
- dummySpaceFlexLine.mCrossSize);
if (accumulatedError > 1) {
dummySpaceFlexLine.mCrossSize += 1;
accumulatedError -= 1;
} else if (accumulatedError < -1) {
dummySpaceFlexLine.mCrossSize -= 1;
accumulatedError += 1;
}
newFlexLines.add(dummySpaceFlexLine);
}
}
mFlexLines = newFlexLines;
break;
}
case ALIGN_CONTENT_CENTER: {
int spaceAboveAndBottom = size - totalCrossSize;
spaceAboveAndBottom = spaceAboveAndBottom / 2;
List<FlexLine> newFlexLines = new ArrayList<>();
FlexLine dummySpaceFlexLine = new FlexLine();
dummySpaceFlexLine.mCrossSize = spaceAboveAndBottom;
for (int i = 0, flexLineSize = mFlexLines.size(); i < flexLineSize; i++) {
if (i == 0) {
newFlexLines.add(dummySpaceFlexLine);
}
FlexLine flexLine = mFlexLines.get(i);
newFlexLines.add(flexLine);
if (i == mFlexLines.size() - 1) {
newFlexLines.add(dummySpaceFlexLine);
}
}
mFlexLines = newFlexLines;
break;
}
case ALIGN_CONTENT_FLEX_END: {
int spaceTop = size - totalCrossSize;
FlexLine dummySpaceFlexLine = new FlexLine();
dummySpaceFlexLine.mCrossSize = spaceTop;
mFlexLines.add(0, dummySpaceFlexLine);
break;
}
}
}
}
}
/**
* Expand the view if the {@link #mAlignItems} attribute is set to {@link #ALIGN_ITEMS_STRETCH}
* or {@link LayoutParams#ALIGN_SELF_STRETCH} is set to an individual child view.
*
* @param flexDirection the flex direction attribute
* @param alignItems the align items attribute
* @see #getFlexDirection()
* @see #setFlexDirection(int)
* @see #getAlignItems()
* @see #setAlignItems(int)
* @see LayoutParams#alignSelf
*/
private void stretchViews(int flexDirection, int alignItems) {
if (alignItems == ALIGN_ITEMS_STRETCH) {
int viewIndex = 0;
for (FlexLine flexLine : mFlexLines) {
for (int i = 0; i < flexLine.mItemCount; i++, viewIndex++) {
View view = getReorderedChildAt(viewIndex);
LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (lp.alignSelf != LayoutParams.ALIGN_SELF_AUTO &&
lp.alignSelf != LayoutParams.ALIGN_SELF_STRETCH) {
continue;
}
switch (flexDirection) {
case FLEX_DIRECTION_ROW: // Intentional fall through
case FLEX_DIRECTION_ROW_REVERSE:
stretchViewVertically(view, flexLine.mCrossSize);
break;
case FLEX_DIRECTION_COLUMN:
case FLEX_DIRECTION_COLUMN_REVERSE:
stretchViewHorizontally(view, flexLine.mCrossSize);
break;
default:
throw new IllegalArgumentException(
"Invalid flex direction: " + flexDirection);
}
}
}
} else {
for (FlexLine flexLine : mFlexLines) {
for (Integer index : flexLine.mIndicesAlignSelfStretch) {
View view = getReorderedChildAt(index);
switch (flexDirection) {
case FLEX_DIRECTION_ROW: // Intentional fall through
case FLEX_DIRECTION_ROW_REVERSE:
stretchViewVertically(view, flexLine.mCrossSize);
break;
case FLEX_DIRECTION_COLUMN:
case FLEX_DIRECTION_COLUMN_REVERSE:
stretchViewHorizontally(view, flexLine.mCrossSize);
break;
default:
throw new IllegalArgumentException(
"Invalid flex direction: " + flexDirection);
}
}
}
}
}
/**
* Expand the view vertically to the size of the crossSize (considering the view margins)
*
* @param view the View to be stretched
* @param crossSize the cross size
*/
private void stretchViewVertically(View view, int crossSize) {
LayoutParams lp = (LayoutParams) view.getLayoutParams();
int newHeight = crossSize - lp.topMargin - lp.bottomMargin;
newHeight = Math.max(newHeight, 0);
view.measure(MeasureSpec
.makeMeasureSpec(view.getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY));
}
/**
* Expand the view horizontally to the size of the crossSize (considering the view margins)
*
* @param view the View to be stretched
* @param crossSize the cross size
*/
private void stretchViewHorizontally(View view, int crossSize) {
LayoutParams lp = (LayoutParams) view.getLayoutParams();
int newWidth = crossSize - lp.leftMargin - lp.rightMargin;
newWidth = Math.max(newWidth, 0);
view.measure(MeasureSpec
.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(view.getMeasuredHeight(), MeasureSpec.EXACTLY));
}
/**
* Set this FlexboxLayouts' width and height depending on the calculated size of main axis and
* cross axis.
*
* @param flexDirection the value of the flex direction
* @param widthMeasureSpec horizontal space requirements as imposed by the parent
* @param heightMeasureSpec vertical space requirements as imposed by the parent
* @param childState the child state of the View
* @see #getFlexDirection()
* @see #setFlexDirection(int)
*/
private void setMeasuredDimensionForFlex(@FlexDirection int flexDirection, int widthMeasureSpec,
int heightMeasureSpec, int childState) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int calculatedMaxHeight;
int calculatedMaxWidth;
switch (flexDirection) {
case FLEX_DIRECTION_ROW: // Intentional fall through
case FLEX_DIRECTION_ROW_REVERSE:
calculatedMaxHeight = getSumOfCrossSize() + getPaddingTop()
+ getPaddingBottom();
calculatedMaxWidth = getLargestMainSize();
break;
case FLEX_DIRECTION_COLUMN: // Intentional fall through
case FLEX_DIRECTION_COLUMN_REVERSE:
calculatedMaxHeight = getLargestMainSize();
calculatedMaxWidth = getSumOfCrossSize() + getPaddingLeft() + getPaddingRight();
break;
default:
throw new IllegalArgumentException("Invalid flex direction: " + flexDirection);
}
int widthSizeAndState;
switch (widthMode) {
case MeasureSpec.EXACTLY:
if (widthSize < calculatedMaxWidth) {
childState = ViewCompat
.combineMeasuredStates(childState, ViewCompat.MEASURED_STATE_TOO_SMALL);
}
widthSizeAndState = ViewCompat.resolveSizeAndState(widthSize, widthMeasureSpec,
childState);
break;
case MeasureSpec.AT_MOST: {
if (widthSize < calculatedMaxWidth) {
childState = ViewCompat
.combineMeasuredStates(childState, ViewCompat.MEASURED_STATE_TOO_SMALL);
} else {
widthSize = calculatedMaxWidth;
}
widthSizeAndState = ViewCompat.resolveSizeAndState(widthSize, widthMeasureSpec,
childState);
break;
}
case MeasureSpec.UNSPECIFIED: {
widthSizeAndState = ViewCompat
.resolveSizeAndState(calculatedMaxWidth, widthMeasureSpec, childState);
break;
}
default:
throw new IllegalStateException("Unknown width mode is set: " + widthMode);
}
int heightSizeAndState;
switch (heightMode) {
case MeasureSpec.EXACTLY:
if (heightSize < calculatedMaxHeight) {
childState = ViewCompat.combineMeasuredStates(childState,
ViewCompat.MEASURED_STATE_TOO_SMALL
>> ViewCompat.MEASURED_HEIGHT_STATE_SHIFT);
}
heightSizeAndState = ViewCompat.resolveSizeAndState(heightSize, heightMeasureSpec,
childState);
break;
case MeasureSpec.AT_MOST: {
if (heightSize < calculatedMaxHeight) {
childState = ViewCompat.combineMeasuredStates(childState,
ViewCompat.MEASURED_STATE_TOO_SMALL
>> ViewCompat.MEASURED_HEIGHT_STATE_SHIFT);
} else {
heightSize = calculatedMaxHeight;
}
heightSizeAndState = ViewCompat.resolveSizeAndState(heightSize, heightMeasureSpec,
childState);
break;
}
case MeasureSpec.UNSPECIFIED: {
heightSizeAndState = ViewCompat.resolveSizeAndState(calculatedMaxHeight,
heightMeasureSpec, childState);
break;
}
default:
throw new IllegalStateException("Unknown height mode is set: " + heightMode);
}
setMeasuredDimension(widthSizeAndState, heightSizeAndState);
}
/**
* Determine if a wrap is required (add a new flex line).
*
* @param mode the width or height mode along the main axis direction
* @param maxSize the max size along the main axis direction
* @param currentLength the accumulated current length
* @param childLength the length of a child view which is to be collected to the flex line
* @param lp the LayoutParams for the view being determined whether a new flex line
* is needed
* @return {@code true} if a wrap is required, {@code false} otherwise
* @see #getFlexWrap()
* @see #setFlexWrap(int)
*/
private boolean isWrapRequired(int mode, int maxSize, int currentLength, int childLength,
LayoutParams lp, int childAbsoluteIndex, int childRelativeIndexInFlexLine) {
if (mFlexWrap == FLEX_WRAP_NOWRAP) {
return false;
}
if (lp.wrapBefore) {
return true;
}
if (mode == MeasureSpec.UNSPECIFIED) {
return false;
}
if (isMainAxisDirectionHorizontal(mFlexDirection)) {
if (hasDividerBeforeChildAtAlongMainAxis(childAbsoluteIndex,
childRelativeIndexInFlexLine)) {
childLength += mDividerVerticalWidth;
}
if ((mShowDividerVertical & SHOW_DIVIDER_END) > 0) {
childLength += mDividerVerticalWidth;
}
} else {
if (hasDividerBeforeChildAtAlongMainAxis(childAbsoluteIndex,
childRelativeIndexInFlexLine)) {
childLength += mDividerHorizontalHeight;
}
if ((mShowDividerHorizontal & SHOW_DIVIDER_END) > 0) {
childLength += mDividerHorizontalHeight;
}
}
return maxSize < currentLength + childLength;
}
/**
* Retrieve the largest main size of all flex lines.
*
* @return the largest main size
*/
private int getLargestMainSize() {
int largestSize = Integer.MIN_VALUE;
for (FlexLine flexLine : mFlexLines) {
largestSize = Math.max(largestSize, flexLine.mMainSize);
}
return largestSize;
}
/**
* Retrieve the sum of the cross sizes of all flex lines including divider lengths.
*
* @return the sum of the cross sizes
*/
private int getSumOfCrossSize() {
int sum = 0;
for (int i = 0, size = mFlexLines.size(); i < size; i++) {
FlexLine flexLine = mFlexLines.get(i);
// Judge if the beginning or middle dividers are required
if (hasDividerBeforeFlexLine(i)) {
if (isMainAxisDirectionHorizontal(mFlexDirection)) {
sum += mDividerHorizontalHeight;
} else {
sum += mDividerVerticalWidth;
}
}
// Judge if the end divider is required
if (hasEndDividerAfterFlexLine(i)) {
if (isMainAxisDirectionHorizontal(mFlexDirection)) {
sum += mDividerHorizontalHeight;
} else {
sum += mDividerVerticalWidth;
}
}
sum += flexLine.mCrossSize;
}
return sum;
}
private boolean isMainAxisDirectionHorizontal(@FlexDirection int flexDirection) {
return flexDirection == FLEX_DIRECTION_ROW
|| flexDirection == FLEX_DIRECTION_ROW_REVERSE;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
int layoutDirection = ViewCompat.getLayoutDirection(this);
boolean isRtl;
switch (mFlexDirection) {
case FLEX_DIRECTION_ROW:
isRtl = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
layoutHorizontal(isRtl, left, top, right, bottom);
break;
case FLEX_DIRECTION_ROW_REVERSE:
isRtl = layoutDirection != ViewCompat.LAYOUT_DIRECTION_RTL;
layoutHorizontal(isRtl, left, top, right, bottom);
break;
case FLEX_DIRECTION_COLUMN:
isRtl = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
if (mFlexWrap == FLEX_WRAP_WRAP_REVERSE) {
isRtl = !isRtl;
}
layoutVertical(isRtl, false, left, top, right, bottom);
break;
case FLEX_DIRECTION_COLUMN_REVERSE:
isRtl = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
if (mFlexWrap == FLEX_WRAP_WRAP_REVERSE) {
isRtl = !isRtl;
}
layoutVertical(isRtl, true, left, top, right, bottom);
break;
default:
throw new IllegalStateException("Invalid flex direction is set: " + mFlexDirection);
}
}
/**
* Sub method for {@link #onLayout(boolean, int, int, int, int)} when the
* {@link #mFlexDirection} is either {@link #FLEX_DIRECTION_ROW} or
* {@link #FLEX_DIRECTION_ROW_REVERSE}.
*
* @param isRtl {@code true} if the horizontal layout direction is right to left, {@code
* false} otherwise.
* @param left the left position of this View
* @param top the top position of this View
* @param right the right position of this View
* @param bottom the bottom position of this View
* @see #getFlexWrap()
* @see #setFlexWrap(int)
* @see #getJustifyContent()
* @see #setJustifyContent(int)
* @see #getAlignItems()
* @see #setAlignItems(int)
* @see LayoutParams#alignSelf
*/
private void layoutHorizontal(boolean isRtl, int left, int top, int right, int bottom) {
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
// Use float to reduce the round error that may happen in when justifyContent ==
// SPACE_BETWEEN or SPACE_AROUND
float childLeft;
int currentViewIndex = 0;
int height = bottom - top;
int width = right - left;
// childBottom is used if the mFlexWrap is FLEX_WRAP_WRAP_REVERSE otherwise
// childTop is used to align the vertical position of the children views.
int childBottom = height - getPaddingBottom();
int childTop = getPaddingTop();
// Used only for RTL layout
// Use float to reduce the round error that may happen in when justifyContent ==
// SPACE_BETWEEN or SPACE_AROUND
float childRight;
for (int i = 0, size = mFlexLines.size(); i < size; i++) {
FlexLine flexLine = mFlexLines.get(i);
if (hasDividerBeforeFlexLine(i)) {
childBottom -= mDividerHorizontalHeight;
childTop += mDividerHorizontalHeight;
}
float spaceBetweenItem = 0f;
switch (mJustifyContent) {
case JUSTIFY_CONTENT_FLEX_START:
childLeft = paddingLeft;
childRight = width - paddingRight;
break;
case JUSTIFY_CONTENT_FLEX_END:
childLeft = width - flexLine.mMainSize + paddingRight;
childRight = flexLine.mMainSize - paddingLeft;
break;
case JUSTIFY_CONTENT_CENTER:
childLeft = paddingLeft + (width - flexLine.mMainSize) / 2f;
childRight = width - paddingRight - (width - flexLine.mMainSize) / 2f;
break;
case JUSTIFY_CONTENT_SPACE_AROUND:
int visibleCount = flexLine.getItemCountNotGone();
if (visibleCount != 0) {
spaceBetweenItem = (width - flexLine.mMainSize)
/ (float) visibleCount;
}
childLeft = paddingLeft + spaceBetweenItem / 2f;
childRight = width - paddingRight - spaceBetweenItem / 2f;
break;
case JUSTIFY_CONTENT_SPACE_BETWEEN:
childLeft = paddingLeft;
int visibleItem = flexLine.getItemCountNotGone();
float denominator = visibleItem != 1 ? visibleItem - 1 : 1f;
spaceBetweenItem = (width - flexLine.mMainSize) / denominator;
childRight = width - paddingRight;
break;
default:
throw new IllegalStateException(
"Invalid justifyContent is set: " + mJustifyContent);
}
spaceBetweenItem = Math.max(spaceBetweenItem, 0);
for (int j = 0; j < flexLine.mItemCount; j++) {
View child = getReorderedChildAt(currentViewIndex);
if (child == null) {
continue;
} else if (child.getVisibility() == View.GONE) {
currentViewIndex++;
continue;
}
LayoutParams lp = ((LayoutParams) child.getLayoutParams());
childLeft += lp.leftMargin;
childRight -= lp.rightMargin;
if (hasDividerBeforeChildAtAlongMainAxis(currentViewIndex, j)) {
childLeft += mDividerVerticalWidth;
childRight -= mDividerVerticalWidth;
}
if (mFlexWrap == FLEX_WRAP_WRAP_REVERSE) {
if (isRtl) {
layoutSingleChildHorizontal(child, flexLine, mFlexWrap, mAlignItems,
Math.round(childRight) - child.getMeasuredWidth(),
childBottom - child.getMeasuredHeight(), Math.round(childRight),
childBottom);
} else {
layoutSingleChildHorizontal(child, flexLine, mFlexWrap, mAlignItems,
Math.round(childLeft), childBottom - child.getMeasuredHeight(),
Math.round(childLeft) + child.getMeasuredWidth(),
childBottom);
}
} else {
if (isRtl) {
layoutSingleChildHorizontal(child, flexLine, mFlexWrap, mAlignItems,
Math.round(childRight) - child.getMeasuredWidth(), childTop,
Math.round(childRight), childTop + child.getMeasuredHeight());
} else {
layoutSingleChildHorizontal(child, flexLine, mFlexWrap, mAlignItems,
Math.round(childLeft), childTop,
Math.round(childLeft) + child.getMeasuredWidth(),
childTop + child.getMeasuredHeight());
}
}
childLeft += child.getMeasuredWidth() + spaceBetweenItem + lp.rightMargin;
childRight -= child.getMeasuredWidth() + spaceBetweenItem + lp.leftMargin;
currentViewIndex++;
flexLine.mLeft = Math.min(flexLine.mLeft, child.getLeft() - lp.leftMargin);
flexLine.mTop = Math.min(flexLine.mTop, child.getTop() - lp.topMargin);
flexLine.mRight = Math.max(flexLine.mRight, child.getRight() + lp.rightMargin);
flexLine.mBottom = Math.max(flexLine.mBottom, child.getBottom() + lp.bottomMargin);
}
childTop += flexLine.mCrossSize;
childBottom -= flexLine.mCrossSize;
}
}
/**
* Place a single View when the layout direction is horizontal ({@link #mFlexDirection} is
* either {@link #FLEX_DIRECTION_ROW} or {@link #FLEX_DIRECTION_ROW_REVERSE}).
*
* @param view the View to be placed
* @param flexLine the {@link FlexLine} where the View belongs to
* @param flexWrap the flex wrap attribute of this FlexboxLayout
* @param alignItems the align items attribute of this FlexboxLayout
* @param left the left position of the View, which the View's margin is already taken
* into account
* @param top the top position of the flex line where the View belongs to. The actual
* View's top position is shifted depending on the flexWrap and alignItems
* attributes
* @param right the right position of the View, which the View's margin is already taken
* into account
* @param bottom the bottom position of the flex line where the View belongs to. The actual
* View's bottom position is shifted depending on the flexWrap and alignItems
* attributes
* @see #getAlignItems()
* @see #setAlignItems(int)
* @see LayoutParams#alignSelf
*/
private void layoutSingleChildHorizontal(View view, FlexLine flexLine, @FlexWrap int flexWrap,
int alignItems, int left, int top, int right, int bottom) {
LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (lp.alignSelf != LayoutParams.ALIGN_SELF_AUTO) {
// Expecting the values for alignItems and alignSelf match except for ALIGN_SELF_AUTO.
// Assigning the alignSelf value as alignItems should work.
alignItems = lp.alignSelf;
}
int crossSize = flexLine.mCrossSize;
switch (alignItems) {
case ALIGN_ITEMS_FLEX_START: // Intentional fall through
case ALIGN_ITEMS_STRETCH:
if (flexWrap != FLEX_WRAP_WRAP_REVERSE) {
view.layout(left, top + lp.topMargin, right, bottom + lp.topMargin);
} else {
view.layout(left, top - lp.bottomMargin, right, bottom - lp.bottomMargin);
}
break;
case ALIGN_ITEMS_BASELINE:
if (flexWrap != FLEX_WRAP_WRAP_REVERSE) {
int marginTop = flexLine.mMaxBaseline - view.getBaseline();
marginTop = Math.max(marginTop, lp.topMargin);
view.layout(left, top + marginTop, right, bottom + marginTop);
} else {
int marginBottom = flexLine.mMaxBaseline - view.getMeasuredHeight() + view
.getBaseline();
marginBottom = Math.max(marginBottom, lp.bottomMargin);
view.layout(left, top - marginBottom, right, bottom - marginBottom);
}
break;
case ALIGN_ITEMS_FLEX_END:
if (flexWrap != FLEX_WRAP_WRAP_REVERSE) {
view.layout(left,
top + crossSize - view.getMeasuredHeight() - lp.bottomMargin,
right, top + crossSize - lp.bottomMargin);
} else {
// If the flexWrap == FLEX_WRAP_WRAP_REVERSE, the direction of the
// flexEnd is flipped (from top to bottom).
view.layout(left, top - crossSize + view.getMeasuredHeight() + lp.topMargin,
right, bottom - crossSize + view.getMeasuredHeight() + lp.topMargin);
}
break;
case ALIGN_ITEMS_CENTER:
int topFromCrossAxis = (crossSize - view.getMeasuredHeight()
+ lp.topMargin - lp.bottomMargin) / 2;
if (flexWrap != FLEX_WRAP_WRAP_REVERSE) {
view.layout(left, top + topFromCrossAxis,
right, top + topFromCrossAxis + view.getMeasuredHeight());
} else {
view.layout(left, top - topFromCrossAxis,
right, top - topFromCrossAxis + view.getMeasuredHeight());
}
break;
}
}
/**
* Sub method for {@link #onLayout(boolean, int, int, int, int)} when the
* {@link #mFlexDirection} is either {@link #FLEX_DIRECTION_COLUMN} or
* {@link #FLEX_DIRECTION_COLUMN_REVERSE}.
*
* @param isRtl {@code true} if the horizontal layout direction is right to left,
* {@code false}
* otherwise
* @param fromBottomToTop {@code true} if the layout direction is bottom to top, {@code false}
* otherwise
* @param left the left position of this View
* @param top the top position of this View
* @param right the right position of this View
* @param bottom the bottom position of this View
* @see #getFlexWrap()
* @see #setFlexWrap(int)
* @see #getJustifyContent()
* @see #setJustifyContent(int)
* @see #getAlignItems()
* @see #setAlignItems(int)
* @see LayoutParams#alignSelf
*/
private void layoutVertical(boolean isRtl, boolean fromBottomToTop, int left, int top,
int right, int bottom) {
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
int paddingRight = getPaddingRight();
int childLeft = getPaddingLeft();
int currentViewIndex = 0;
int width = right - left;
int height = bottom - top;
// childRight is used if the mFlexWrap is FLEX_WRAP_WRAP_REVERSE otherwise
// childLeft is used to align the horizontal position of the children views.
int childRight = width - paddingRight;
// Use float to reduce the round error that may happen in when justifyContent ==
// SPACE_BETWEEN or SPACE_AROUND
float childTop;
// Used only for if the direction is from bottom to top
float childBottom;
for (int i = 0, size = mFlexLines.size(); i < size; i++) {
FlexLine flexLine = mFlexLines.get(i);
if (hasDividerBeforeFlexLine(i)) {
childLeft += mDividerVerticalWidth;
childRight -= mDividerVerticalWidth;
}
float spaceBetweenItem = 0f;
switch (mJustifyContent) {
case JUSTIFY_CONTENT_FLEX_START:
childTop = paddingTop;
childBottom = height - paddingBottom;
break;
case JUSTIFY_CONTENT_FLEX_END:
childTop = height - flexLine.mMainSize + paddingBottom;
childBottom = flexLine.mMainSize - paddingTop;
break;
case JUSTIFY_CONTENT_CENTER:
childTop = paddingTop + (height - flexLine.mMainSize) / 2f;
childBottom = height - paddingBottom - (height - flexLine.mMainSize) / 2f;
break;
case JUSTIFY_CONTENT_SPACE_AROUND:
int visibleCount = flexLine.getItemCountNotGone();
if (visibleCount != 0) {
spaceBetweenItem = (height - flexLine.mMainSize)
/ (float) visibleCount;
}
childTop = paddingTop + spaceBetweenItem / 2f;
childBottom = height - paddingBottom - spaceBetweenItem / 2f;
break;
case JUSTIFY_CONTENT_SPACE_BETWEEN:
childTop = paddingTop;
int visibleItem = flexLine.getItemCountNotGone();
float denominator = visibleItem != 1 ? visibleItem - 1 : 1f;
spaceBetweenItem = (height - flexLine.mMainSize) / denominator;
childBottom = height - paddingBottom;
break;
default:
throw new IllegalStateException(
"Invalid justifyContent is set: " + mJustifyContent);
}
spaceBetweenItem = Math.max(spaceBetweenItem, 0);
for (int j = 0; j < flexLine.mItemCount; j++) {
View child = getReorderedChildAt(currentViewIndex);
if (child == null) {
continue;
} else if (child.getVisibility() == View.GONE) {
currentViewIndex++;
continue;
}
LayoutParams lp = ((LayoutParams) child.getLayoutParams());
childTop += lp.topMargin;
childBottom -= lp.bottomMargin;
if (hasDividerBeforeChildAtAlongMainAxis(currentViewIndex, j)) {
childTop += mDividerHorizontalHeight;
childBottom -= mDividerHorizontalHeight;
}
if (isRtl) {
if (fromBottomToTop) {
layoutSingleChildVertical(child, flexLine, true, mAlignItems,
childRight - child.getMeasuredWidth(),
Math.round(childBottom) - child.getMeasuredHeight(), childRight,
Math.round(childBottom));
} else {
layoutSingleChildVertical(child, flexLine, true, mAlignItems,
childRight - child.getMeasuredWidth(), Math.round(childTop),
childRight, Math.round(childTop) + child.getMeasuredHeight());
}
} else {
if (fromBottomToTop) {
layoutSingleChildVertical(child, flexLine, false, mAlignItems,
childLeft, Math.round(childBottom) - child.getMeasuredHeight(),
childLeft + child.getMeasuredWidth(), Math.round(childBottom));
} else {
layoutSingleChildVertical(child, flexLine, false, mAlignItems,
childLeft, Math.round(childTop),
childLeft + child.getMeasuredWidth(),
Math.round(childTop) + child.getMeasuredHeight());
}
}
childTop += child.getMeasuredHeight() + spaceBetweenItem + lp.bottomMargin;
childBottom -= child.getMeasuredHeight() + spaceBetweenItem + lp.topMargin;
currentViewIndex++;
flexLine.mLeft = Math.min(flexLine.mLeft, child.getLeft() - lp.leftMargin);
flexLine.mTop = Math.min(flexLine.mTop, child.getTop() - lp.topMargin);
flexLine.mRight = Math.max(flexLine.mRight, child.getRight() + lp.rightMargin);
flexLine.mBottom = Math.max(flexLine.mBottom, child.getBottom() + lp.bottomMargin);
}
childLeft += flexLine.mCrossSize;
childRight -= flexLine.mCrossSize;
}
}
/**
* Place a single View when the layout direction is vertical ({@link #mFlexDirection} is
* either {@link #FLEX_DIRECTION_COLUMN} or {@link #FLEX_DIRECTION_COLUMN_REVERSE}).
*
* @param view the View to be placed
* @param flexLine the {@link FlexLine} where the View belongs to
* @param isRtl {@code true} if the layout direction is right to left, {@code false}
* otherwise
* @param alignItems the align items attribute of this FlexboxLayout
* @param left the left position of the flex line where the View belongs to. The actual
* View's left position is shifted depending on the isRtl and alignItems
* attributes
* @param top the top position of the View, which the View's margin is already taken
* into account
* @param right the right position of the flex line where the View belongs to. The actual
* View's right position is shifted depending on the isRtl and alignItems
* attributes
* @param bottom the bottom position of the View, which the View's margin is already taken
* into account
* @see #getAlignItems()
* @see #setAlignItems(int)
* @see LayoutParams#alignSelf
*/
private void layoutSingleChildVertical(View view, FlexLine flexLine, boolean isRtl,
int alignItems, int left, int top, int right, int bottom) {
LayoutParams lp = (LayoutParams) view.getLayoutParams();
if (lp.alignSelf != LayoutParams.ALIGN_SELF_AUTO) {
// Expecting the values for alignItems and alignSelf match except for ALIGN_SELF_AUTO.
// Assigning the alignSelf value as alignItems should work.
alignItems = lp.alignSelf;
}
int crossSize = flexLine.mCrossSize;
switch (alignItems) {
case ALIGN_ITEMS_FLEX_START: // Intentional fall through
case ALIGN_ITEMS_STRETCH: // Intentional fall through
case ALIGN_ITEMS_BASELINE:
if (!isRtl) {
view.layout(left + lp.leftMargin, top, right + lp.leftMargin, bottom);
} else {
view.layout(left - lp.rightMargin, top, right - lp.rightMargin, bottom);
}
break;
case ALIGN_ITEMS_FLEX_END:
if (!isRtl) {
view.layout(left + crossSize - view.getMeasuredWidth() - lp.rightMargin,
top, right + crossSize - view.getMeasuredWidth() - lp.rightMargin,
bottom);
} else {
// If the flexWrap == FLEX_WRAP_WRAP_REVERSE, the direction of the
// flexEnd is flipped (from left to right).
view.layout(left - crossSize + view.getMeasuredWidth() + lp.leftMargin, top,
right - crossSize + view.getMeasuredWidth() + lp.leftMargin,
bottom);
}
break;
case ALIGN_ITEMS_CENTER:
int leftFromCrossAxis = (crossSize - view.getMeasuredWidth()
+ MarginLayoutParamsCompat.getMarginStart(lp)
- MarginLayoutParamsCompat.getMarginEnd(lp)) / 2;
if (!isRtl) {
view.layout(left + leftFromCrossAxis, top, right + leftFromCrossAxis, bottom);
} else {
view.layout(left - leftFromCrossAxis, top, right - leftFromCrossAxis, bottom);
}
break;
}
}
@Override
protected void onDraw(Canvas canvas) {
if (mDividerDrawableVertical == null && mDividerDrawableHorizontal == null) {
return;
}
if (mShowDividerHorizontal == SHOW_DIVIDER_NONE
&& mShowDividerVertical == SHOW_DIVIDER_NONE) {
return;
}
int layoutDirection = ViewCompat.getLayoutDirection(this);
boolean isRtl;
boolean fromBottomToTop = false;
switch (mFlexDirection) {
case FLEX_DIRECTION_ROW:
isRtl = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
if (mFlexWrap == FLEX_WRAP_WRAP_REVERSE) {
fromBottomToTop = true;
}
drawDividersHorizontal(canvas, isRtl, fromBottomToTop);
break;
case FLEX_DIRECTION_ROW_REVERSE:
isRtl = layoutDirection != ViewCompat.LAYOUT_DIRECTION_RTL;
if (mFlexWrap == FLEX_WRAP_WRAP_REVERSE) {
fromBottomToTop = true;
}
drawDividersHorizontal(canvas, isRtl, fromBottomToTop);
break;
case FLEX_DIRECTION_COLUMN:
isRtl = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
if (mFlexWrap == FLEX_WRAP_WRAP_REVERSE) {
isRtl = !isRtl;
}
fromBottomToTop = false;
drawDividersVertical(canvas, isRtl, fromBottomToTop);
break;
case FLEX_DIRECTION_COLUMN_REVERSE:
isRtl = layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
if (mFlexWrap == FLEX_WRAP_WRAP_REVERSE) {
isRtl = !isRtl;
}
fromBottomToTop = true;
drawDividersVertical(canvas, isRtl, fromBottomToTop);
break;
}
}
/**
* Sub method for {@link #onDraw(Canvas)} when the main axis direction is horizontal
* ({@link #mFlexDirection} is either of {@link #FLEX_DIRECTION_ROW} or
* {@link #FLEX_DIRECTION_ROW_REVERSE}.
*
* @param canvas the canvas on which the background will be drawn
* @param isRtl {@code true} when the horizontal layout direction is right to left,
* {@code false} otherwise
* @param fromBottomToTop {@code true} when the vertical layout direction is bottom to top,
* {@code false} otherwise
*/
private void drawDividersHorizontal(Canvas canvas, boolean isRtl, boolean fromBottomToTop) {
int currentViewIndex = 0;
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int horizontalDividerLength = Math.max(0, getWidth() - paddingRight - paddingLeft);
for (int i = 0, size = mFlexLines.size(); i < size; i++) {
FlexLine flexLine = mFlexLines.get(i);
for (int j = 0; j < flexLine.mItemCount; j++) {
View view = getReorderedChildAt(currentViewIndex);
if (view == null || view.getVisibility() == View.GONE) {
continue;
}
LayoutParams lp = (LayoutParams) view.getLayoutParams();
// Judge if the beginning or middle divider is needed
if (hasDividerBeforeChildAtAlongMainAxis(currentViewIndex, j)) {
int dividerLeft;
if (isRtl) {
dividerLeft = view.getRight() + lp.rightMargin;
} else {
dividerLeft = view.getLeft() - lp.leftMargin - mDividerVerticalWidth;
}
drawVerticalDivider(canvas, dividerLeft, flexLine.mTop, flexLine.mCrossSize);
}
// Judge if the end divider is needed
if (j == flexLine.mItemCount - 1) {
if ((mShowDividerVertical & SHOW_DIVIDER_END) > 0) {
int dividerLeft;
if (isRtl) {
dividerLeft = view.getLeft() - lp.leftMargin - mDividerVerticalWidth;
} else {
dividerLeft = view.getRight() + lp.rightMargin;
}
drawVerticalDivider(canvas, dividerLeft, flexLine.mTop,
flexLine.mCrossSize);
}
}
currentViewIndex++;
}
// Judge if the beginning or middle dividers are needed before the flex line
if (hasDividerBeforeFlexLine(i)) {
int horizontalDividerTop;
if (fromBottomToTop) {
horizontalDividerTop = flexLine.mBottom;
} else {
horizontalDividerTop = flexLine.mTop - mDividerHorizontalHeight;
}
drawHorizontalDivider(canvas, paddingLeft, horizontalDividerTop,
horizontalDividerLength);
}
// Judge if the end divider is needed before the flex line
if (hasEndDividerAfterFlexLine(i)) {
if ((mShowDividerHorizontal & SHOW_DIVIDER_END) > 0) {
int horizontalDividerTop;
if (fromBottomToTop) {
horizontalDividerTop = flexLine.mTop - mDividerHorizontalHeight;
} else {
horizontalDividerTop = flexLine.mBottom;
}
drawHorizontalDivider(canvas, paddingLeft, horizontalDividerTop,
horizontalDividerLength);
}
}
}
}
/**
* Sub method for {@link #onDraw(Canvas)} when the main axis direction is vertical
* ({@link #mFlexDirection} is either of {@link #FLEX_DIRECTION_COLUMN} or
* {@link #FLEX_DIRECTION_COLUMN_REVERSE}.
*
* @param canvas the canvas on which the background will be drawn
* @param isRtl {@code true} when the horizontal layout direction is right to left,
* {@code false} otherwise
* @param fromBottomToTop {@code true} when the vertical layout direction is bottom to top,
* {@code false} otherwise
*/
private void drawDividersVertical(Canvas canvas, boolean isRtl, boolean fromBottomToTop) {
int currentViewIndex = 0;
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
int verticalDividerLength = Math.max(0, getHeight() - paddingBottom - paddingTop);
for (int i = 0, size = mFlexLines.size(); i < size; i++) {
FlexLine flexLine = mFlexLines.get(i);
// Draw horizontal dividers if needed
for (int j = 0; j < flexLine.mItemCount; j++) {
View view = getReorderedChildAt(currentViewIndex);
if (view == null || view.getVisibility() == View.GONE) {
continue;
}
LayoutParams lp = (LayoutParams) view.getLayoutParams();
// Judge if the beginning or middle divider is needed
if (hasDividerBeforeChildAtAlongMainAxis(currentViewIndex, j)) {
int dividerTop;
if (fromBottomToTop) {
dividerTop = view.getBottom() + lp.bottomMargin;
} else {
dividerTop = view.getTop() - lp.topMargin - mDividerHorizontalHeight;
}
drawHorizontalDivider(canvas, flexLine.mLeft, dividerTop, flexLine.mCrossSize);
}
// Judge if the end divider is needed
if (j == flexLine.mItemCount - 1) {
if ((mShowDividerHorizontal & SHOW_DIVIDER_END) > 0) {
int dividerTop;
if (fromBottomToTop) {
dividerTop = view.getTop() - lp.topMargin - mDividerHorizontalHeight;
} else {
dividerTop = view.getBottom() + lp.bottomMargin;
}
drawHorizontalDivider(canvas, flexLine.mLeft, dividerTop,
flexLine.mCrossSize);
}
}
currentViewIndex++;
}
// Judge if the beginning or middle dividers are needed before the flex line
if (hasDividerBeforeFlexLine(i)) {
int verticalDividerLeft;
if (isRtl) {
verticalDividerLeft = flexLine.mRight;
} else {
verticalDividerLeft = flexLine.mLeft - mDividerVerticalWidth;
}
drawVerticalDivider(canvas, verticalDividerLeft, paddingTop,
verticalDividerLength);
}
if (hasEndDividerAfterFlexLine(i)) {
if ((mShowDividerVertical & SHOW_DIVIDER_END) > 0) {
int verticalDividerLeft;
if (isRtl) {
verticalDividerLeft = flexLine.mLeft - mDividerVerticalWidth;
} else {
verticalDividerLeft = flexLine.mRight;
}
drawVerticalDivider(canvas, verticalDividerLeft, paddingTop,
verticalDividerLength);
}
}
}
}
private void drawVerticalDivider(Canvas canvas, int left, int top, int length) {
if (mDividerDrawableVertical == null) {
return;
}
mDividerDrawableVertical.setBounds(left, top, left + mDividerVerticalWidth, top + length);
mDividerDrawableVertical.draw(canvas);
}
private void drawHorizontalDivider(Canvas canvas, int left, int top, int length) {
if (mDividerDrawableHorizontal == null) {
return;
}
mDividerDrawableHorizontal
.setBounds(left, top, left + length, top + mDividerHorizontalHeight);
mDividerDrawableHorizontal.draw(canvas);
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof FlexboxLayout.LayoutParams;
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new FlexboxLayout.LayoutParams(getContext(), attrs);
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p);
}
@FlexDirection
public int getFlexDirection() {
return mFlexDirection;
}
public void setFlexDirection(@FlexDirection int flexDirection) {
if (mFlexDirection != flexDirection) {
mFlexDirection = flexDirection;
requestLayout();
}
}
@FlexWrap
public int getFlexWrap() {
return mFlexWrap;
}
public void setFlexWrap(@FlexWrap int flexWrap) {
if (mFlexWrap != flexWrap) {
mFlexWrap = flexWrap;
requestLayout();
}
}
@JustifyContent
public int getJustifyContent() {
return mJustifyContent;
}
public void setJustifyContent(@JustifyContent int justifyContent) {
if (mJustifyContent != justifyContent) {
mJustifyContent = justifyContent;
requestLayout();
}
}
@AlignItems
public int getAlignItems() {
return mAlignItems;
}
public void setAlignItems(@AlignItems int alignItems) {
if (mAlignItems != alignItems) {
mAlignItems = alignItems;
requestLayout();
}
}
@AlignContent
public int getAlignContent() {
return mAlignContent;
}
public void setAlignContent(@AlignContent int alignContent) {
if (mAlignContent != alignContent) {
mAlignContent = alignContent;
requestLayout();
}
}
/**
* @return the flex lines composing this flex container. This method returns a copy of the
* original list excluding a dummy flex line (flex line that doesn't have any flex items in it
* but used for the alignment along the cross axis).
* Thus any changes of the returned list are not reflected to the original list.
*/
public List<FlexLine> getFlexLines() {
List<FlexLine> result = new ArrayList<>(mFlexLines.size());
for (FlexLine flexLine : mFlexLines) {
if (flexLine.getItemCountNotGone() == 0) {
continue;
}
result.add(flexLine);
}
return result;
}
/**
* @return the horizontal divider drawable that will divide each item.
* @see #setDividerDrawable(Drawable)
* @see #setDividerDrawableHorizontal(Drawable)
*/
public Drawable getDividerDrawableHorizontal() {
return mDividerDrawableHorizontal;
}
/**
* @return the vertical divider drawable that will divide each item.
* @see #setDividerDrawable(Drawable)
* @see #setDividerDrawableVertical(Drawable)
*/
public Drawable getDividerDrawableVertical() {
return mDividerDrawableVertical;
}
/**
* Set a drawable to be used as a divider between items. The drawable is used for both
* horizontal and vertical dividers.
*
* @param divider Drawable that will divide each item for both horizontally and vertically.
* @see #setShowDivider(int)
*/
public void setDividerDrawable(Drawable divider) {
setDividerDrawableHorizontal(divider);
setDividerDrawableVertical(divider);
}
/**
* Set a drawable to be used as a horizontal divider between items.
*
* @param divider Drawable that will divide each item.
* @see #setDividerDrawable(Drawable)
* @see #setShowDivider(int)
* @see #setShowDividerHorizontal(int)
*/
public void setDividerDrawableHorizontal(Drawable divider) {
if (divider == mDividerDrawableHorizontal) {
return;
}
mDividerDrawableHorizontal = divider;
if (divider != null) {
mDividerHorizontalHeight = divider.getIntrinsicHeight();
} else {
mDividerHorizontalHeight = 0;
}
setWillNotDrawFlag();
requestLayout();
}
/**
* Set a drawable to be used as a vertical divider between items.
*
* @param divider Drawable that will divide each item.
* @see #setDividerDrawable(Drawable)
* @see #setShowDivider(int)
* @see #setShowDividerVertical(int)
*/
public void setDividerDrawableVertical(Drawable divider) {
if (divider == mDividerDrawableVertical) {
return;
}
mDividerDrawableVertical = divider;
if (divider != null) {
mDividerVerticalWidth = divider.getIntrinsicWidth();
} else {
mDividerVerticalWidth = 0;
}
setWillNotDrawFlag();
requestLayout();
}
@FlexboxLayout.DividerMode
public int getShowDividerVertical() {
return mShowDividerVertical;
}
@FlexboxLayout.DividerMode
public int getShowDividerHorizontal() {
return mShowDividerHorizontal;
}
/**
* Set how dividers should be shown between items in this layout. This method sets the
* divider mode for both horizontally and vertically.
*
* @param dividerMode One or more of {@link #SHOW_DIVIDER_BEGINNING},
* {@link #SHOW_DIVIDER_MIDDLE}, or {@link #SHOW_DIVIDER_END},
* or {@link #SHOW_DIVIDER_NONE} to show no dividers.
* @see #setShowDividerVertical(int)
* @see #setShowDividerHorizontal(int)
*/
public void setShowDivider(@DividerMode int dividerMode) {
setShowDividerVertical(dividerMode);
setShowDividerHorizontal(dividerMode);
}
/**
* Set how vertical dividers should be shown between items in this layout
*
* @param dividerMode One or more of {@link #SHOW_DIVIDER_BEGINNING},
* {@link #SHOW_DIVIDER_MIDDLE}, or {@link #SHOW_DIVIDER_END},
* or {@link #SHOW_DIVIDER_NONE} to show no dividers.
* @see #setShowDivider(int)
*/
public void setShowDividerVertical(@DividerMode int dividerMode) {
if (dividerMode != mShowDividerVertical) {
mShowDividerVertical = dividerMode;
requestLayout();
}
}
/**
* Set how horizontal dividers should be shown between items in this layout.
*
* @param dividerMode One or more of {@link #SHOW_DIVIDER_BEGINNING},
* {@link #SHOW_DIVIDER_MIDDLE}, or {@link #SHOW_DIVIDER_END},
* or {@link #SHOW_DIVIDER_NONE} to show no dividers.
* @see #setShowDivider(int)
*/
public void setShowDividerHorizontal(@DividerMode int dividerMode) {
if (dividerMode != mShowDividerHorizontal) {
mShowDividerHorizontal = dividerMode;
requestLayout();
}
}
private void setWillNotDrawFlag() {
if (mDividerDrawableHorizontal == null && mDividerDrawableVertical == null) {
setWillNotDraw(true);
} else {
setWillNotDraw(false);
}
}
/**
* Check if a divider is needed before the view whose indices are passed as arguments.
*
* @param childAbsoluteIndex the absolute index of the view to be judged
* @param childRelativeIndexInFlexLine the relative index in the flex line where the view
* belongs
* @return {@code true} if a divider is needed, {@code false} otherwise
*/
private boolean hasDividerBeforeChildAtAlongMainAxis(int childAbsoluteIndex,
int childRelativeIndexInFlexLine) {
if (allViewsAreGoneBefore(childAbsoluteIndex, childRelativeIndexInFlexLine)) {
if (isMainAxisDirectionHorizontal(mFlexDirection)) {
return (mShowDividerVertical & SHOW_DIVIDER_BEGINNING) != 0;
} else {
return (mShowDividerHorizontal & SHOW_DIVIDER_BEGINNING) != 0;
}
} else {
if (isMainAxisDirectionHorizontal(mFlexDirection)) {
return (mShowDividerVertical & SHOW_DIVIDER_MIDDLE) != 0;
} else {
return (mShowDividerHorizontal & SHOW_DIVIDER_MIDDLE) != 0;
}
}
}
private boolean allViewsAreGoneBefore(int childAbsoluteIndex,
int childRelativeIndexInFlexLine) {
for (int i = 1; i <= childRelativeIndexInFlexLine; i++) {
View view = getReorderedChildAt(childAbsoluteIndex - i);
if (view != null && view.getVisibility() != View.GONE) {
return false;
}
}
return true;
}
/**
* Check if a divider is needed before the flex line whose index is passed as an argument.
*
* @param flexLineIndex the index of the flex line to be checked
* @return {@code true} if a divider is needed, {@code false} otherwise
*/
private boolean hasDividerBeforeFlexLine(int flexLineIndex) {
if (flexLineIndex < 0 || flexLineIndex >= mFlexLines.size()) {
return false;
}
if (allFlexLinesAreDummyBefore(flexLineIndex)) {
if (isMainAxisDirectionHorizontal(mFlexDirection)) {
return (mShowDividerHorizontal & SHOW_DIVIDER_BEGINNING) != 0;
} else {
return (mShowDividerVertical & SHOW_DIVIDER_BEGINNING) != 0;
}
} else {
if (isMainAxisDirectionHorizontal(mFlexDirection)) {
return (mShowDividerHorizontal & SHOW_DIVIDER_MIDDLE) != 0;
} else {
return (mShowDividerVertical & SHOW_DIVIDER_MIDDLE) != 0;
}
}
}
private boolean allFlexLinesAreDummyBefore(int flexLineIndex) {
for (int i = 0; i < flexLineIndex; i++) {
if (mFlexLines.get(i).getItemCountNotGone() > 0) {
return false;
}
}
return true;
}
/**
* Check if a end divider is needed after the flex line whose index is passed as an argument.
*
* @param flexLineIndex the index of the flex line to be checked
* @return {@code true} if a divider is needed, {@code false} otherwise
*/
private boolean hasEndDividerAfterFlexLine(int flexLineIndex) {
if (flexLineIndex < 0 || flexLineIndex >= mFlexLines.size()) {
return false;
}
for (int i = flexLineIndex + 1; i < mFlexLines.size(); i++) {
if (mFlexLines.get(i).getItemCountNotGone() > 0) {
return false;
}
}
if (isMainAxisDirectionHorizontal(mFlexDirection)) {
return (mShowDividerHorizontal & SHOW_DIVIDER_END) != 0;
} else {
return (mShowDividerVertical & SHOW_DIVIDER_END) != 0;
}
}
/**
* Per child parameters for children views of the {@link FlexboxLayout}.
*/
public static class LayoutParams extends MarginLayoutParams {
private static final int ORDER_DEFAULT = 1;
private static final float FLEX_GROW_DEFAULT = 0f;
private static final float FLEX_SHRINK_DEFAULT = 1f;
public static final float FLEX_BASIS_PERCENT_DEFAULT = -1f;
public static final int ALIGN_SELF_AUTO = -1;
public static final int ALIGN_SELF_FLEX_START = ALIGN_ITEMS_FLEX_START;
public static final int ALIGN_SELF_FLEX_END = ALIGN_ITEMS_FLEX_END;
public static final int ALIGN_SELF_CENTER = ALIGN_ITEMS_CENTER;
public static final int ALIGN_SELF_BASELINE = ALIGN_ITEMS_BASELINE;
public static final int ALIGN_SELF_STRETCH = ALIGN_ITEMS_STRETCH;
private static final int MAX_SIZE = Integer.MAX_VALUE & ViewCompat.MEASURED_SIZE_MASK;
/**
* This attribute can change the ordering of the children views are laid out.
* By default, children are displayed and laid out in the same order as they appear in the
* layout XML. If not specified, {@link #ORDER_DEFAULT} is set as a default value.
*/
public int order = ORDER_DEFAULT;
/**
* This attribute determines how much this child will grow if positive free space is
* distributed relative to the rest of other flex items included in the same flex line.
* If not specified, {@link #FLEX_GROW_DEFAULT} is set as a default value.
*/
public float flexGrow = FLEX_GROW_DEFAULT;
/**
* This attributes determines how much this child will shrink is negative free space is
* distributed relative to the rest of other flex items included in the same flex line.
* If not specified, {@link #FLEX_SHRINK_DEFAULT} is set as a default value.
*/
public float flexShrink = FLEX_SHRINK_DEFAULT;
/**
* This attributes determines the alignment along the cross axis (perpendicular to the
* main axis). The alignment in the same direction can be determined by the
* {@link #mAlignItems} in the parent, but if this is set to other than
* {@link #ALIGN_SELF_AUTO}, the cross axis alignment is overridden for this child.
* The value needs to be one of the values in ({@link #ALIGN_SELF_AUTO},
* {@link #ALIGN_SELF_STRETCH}, {@link #ALIGN_SELF_FLEX_START}, {@link
* #ALIGN_SELF_FLEX_END}, {@link #ALIGN_SELF_CENTER}, or {@link #ALIGN_SELF_BASELINE}).
* If not specified, {@link #ALIGN_SELF_AUTO} is set as a default value.
*/
public int alignSelf = ALIGN_SELF_AUTO;
/**
* The initial flex item length in a fraction format relative to its parent.
* The initial main size of this child View is trying to be expanded as the specified
* fraction against the parent main size.
* If this value is set, the length specified from layout_width
* (or layout_height) is overridden by the calculated value from this attribute.
* This attribute is only effective when the parent's MeasureSpec mode is
* MeasureSpec.EXACTLY. The default value is {@link #FLEX_BASIS_PERCENT_DEFAULT}, which
* means not set.
*/
public float flexBasisPercent = FLEX_BASIS_PERCENT_DEFAULT;
/**
* This attribute determines the minimum width the child can shrink to.
*/
public int minWidth;
/**
* This attribute determines the minimum height the child can shrink to.
*/
public int minHeight;
/**
* This attribute determines the maximum width the child can expand to.
*/
public int maxWidth = MAX_SIZE;
/**
* This attribute determines the maximum height the child can expand to.
*/
public int maxHeight = MAX_SIZE;
/**
* This attribute forces a flex line wrapping. i.e. if this is set to {@code true} for a
* flex item, the item will become the first item of the new flex line. (A wrapping happens
* regardless of the flex items being processed in the the previous flex line)
* This attribute is ignored if the flex_wrap attribute is set as nowrap.
* The equivalent attribute isn't defined in the original CSS Flexible Box Module
* specification, but having this attribute is useful for Android developers to flatten
* the layouts when building a grid like layout or for a situation where developers want
* to put a new flex line to make a semantic difference from the previous one, etc.
*/
public boolean wrapBefore;
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context
.obtainStyledAttributes(attrs, R.styleable.FlexboxLayout_Layout);
order = a.getInt(R.styleable.FlexboxLayout_Layout_layout_order, ORDER_DEFAULT);
flexGrow = a
.getFloat(R.styleable.FlexboxLayout_Layout_layout_flexGrow, FLEX_GROW_DEFAULT);
flexShrink = a.getFloat(R.styleable.FlexboxLayout_Layout_layout_flexShrink,
FLEX_SHRINK_DEFAULT);
alignSelf = a
.getInt(R.styleable.FlexboxLayout_Layout_layout_alignSelf, ALIGN_SELF_AUTO);
flexBasisPercent = a
.getFraction(R.styleable.FlexboxLayout_Layout_layout_flexBasisPercent, 1, 1,
FLEX_BASIS_PERCENT_DEFAULT);
minWidth = a.getDimensionPixelSize(R.styleable.FlexboxLayout_Layout_layout_minWidth, 0);
minHeight = a
.getDimensionPixelSize(R.styleable.FlexboxLayout_Layout_layout_minHeight, 0);
maxWidth = a.getDimensionPixelSize(R.styleable.FlexboxLayout_Layout_layout_maxWidth,
MAX_SIZE);
maxHeight = a.getDimensionPixelSize(R.styleable.FlexboxLayout_Layout_layout_maxHeight,
MAX_SIZE);
wrapBefore = a.getBoolean(R.styleable.FlexboxLayout_Layout_layout_wrapBefore, false);
a.recycle();
}
public LayoutParams(LayoutParams source) {
super(source);
order = source.order;
flexGrow = source.flexGrow;
flexShrink = source.flexShrink;
alignSelf = source.alignSelf;
flexBasisPercent = source.flexBasisPercent;
minWidth = source.minWidth;
minHeight = source.minHeight;
maxWidth = source.maxWidth;
maxHeight = source.maxHeight;
wrapBefore = source.wrapBefore;
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(int width, int height) {
super(new ViewGroup.LayoutParams(width, height));
}
}
/**
* A class that is used for calculating the view order which view's indices and order
* properties from Flexbox are taken into account.
*/
private static class Order implements Comparable<Order> {
/** {@link View}'s index */
int index;
/** order property in the Flexbox */
int order;
@Override
public int compareTo(@NonNull Order another) {
if (order != another.order) {
return order - another.order;
}
return index - another.index;
}
@Override
public String toString() {
return "Order{" +
"order=" + order +
", index=" + index +
'}';
}
}
}