I created a WrapLayout few months back that can have multiple features. I don’t remember If I have copied it from somewhere or I asked ChatGPT or mix of them with my adjustments. But now I have issue with last row, that is not visible if I have a small or smaller views in the last row. The code of my class is given below.
package com.aunalvi.visa;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
public final class WrapLayout extends ViewGroup {
public static final int SPACING_AUTO = -65536;
public static final int SPACING_ALIGN = -65537;
private static final int SPACING_UNDEFINED = -65538;
private static final int UNSPECIFIED_GRAVITY = -1;
private static final int ROW_VERTICAL_GRAVITY_AUTO = -65536;
private static final boolean DEFAULT_FLOW = true;
private static final int DEFAULT_CHILD_SPACING = 0;
private static final float DEFAULT_ROW_SPACING = 0;
private static final boolean DEFAULT_RTL = false;
private static final int DEFAULT_MAX_ROWS = Integer.MAX_VALUE;
private boolean mFlow;
private int mChildSpacing;
private int mMinChildSpacing;
private int mChildSpacingForLastRow;
private float mRowSpacing;
private float mAdjustedRowSpacing = DEFAULT_ROW_SPACING;
private boolean mRtl = DEFAULT_RTL;
private int mMaxRows;
private int mGravity;
private int mRowVerticalGravity;
private int mExactMeasuredHeight;
private final List<Float> mHorizontalSpacingForRow = new ArrayList<>();
private final List<Integer> mHeightForRow = new ArrayList<>();
private final List<Integer> mWidthForRow = new ArrayList<>();
private final List<Integer> mChildNumForRow = new ArrayList<>();
public WrapLayout(Context context) {
this(context, null);
}
public WrapLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WrapLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init (final Context context, final AttributeSet attrs) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.WrapLayout, 0, 0);
mFlow = a.getBoolean(R.styleable.WrapLayout_flow, DEFAULT_FLOW);
mChildSpacing = getDimensionOrInt(a, R.styleable.WrapLayout_childSpacing, (int) dpToPx(DEFAULT_CHILD_SPACING));
mMinChildSpacing = getDimensionOrInt(a, R.styleable.WrapLayout_minChildSpacing, (int) dpToPx(DEFAULT_CHILD_SPACING));
mChildSpacingForLastRow = getDimensionOrInt(a, R.styleable.WrapLayout_childSpacingForLastRow, SPACING_UNDEFINED);
mRowSpacing = getDimensionOrInt(a, R.styleable.WrapLayout_rowSpacing, (int) dpToPx(DEFAULT_ROW_SPACING));
mMaxRows = a.getInt(R.styleable.WrapLayout_maxRows, DEFAULT_MAX_ROWS);
mGravity = a.getInt(R.styleable.WrapLayout_android_gravity, UNSPECIFIED_GRAVITY);
mRowVerticalGravity = a.getInt(R.styleable.WrapLayout_rowVerticalGravity, ROW_VERTICAL_GRAVITY_AUTO);
a.recycle();
}
private int getDimensionOrInt(TypedArray a, int index, int defValue) {
TypedValue tv = new TypedValue();
a.getValue(index, tv);
if (tv.type == TypedValue.TYPE_DIMENSION) {
return a.getDimensionPixelSize(index, defValue);
} else {
return a.getInt(index, defValue);
}
}
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
mHorizontalSpacingForRow.clear();
mHeightForRow.clear();
mWidthForRow.clear();
mChildNumForRow.clear();
int measuredHeight = 0, measuredWidth = 0, childCount = getChildCount();
int rowWidth = 0, maxChildHeightInRow = 0, childNumInRow = 0;
final int rowSize = widthSize - getPaddingLeft() - getPaddingRight();
int rowTotalChildWidth = 0;
final boolean allowFlow = widthMode != MeasureSpec.UNSPECIFIED && mFlow;
final int childSpacing = mChildSpacing == SPACING_AUTO && widthMode == MeasureSpec.UNSPECIFIED
? 0 : mChildSpacing;
final float tmpSpacing = childSpacing == SPACING_AUTO ? mMinChildSpacing : childSpacing;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
LayoutParams childParams = child.getLayoutParams();
int horizontalMargin = 0, verticalMargin = 0;
if (childParams instanceof MarginLayoutParams) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, measuredHeight);
MarginLayoutParams marginParams = (MarginLayoutParams) childParams;
horizontalMargin = marginParams.leftMargin + marginParams.rightMargin;
verticalMargin = marginParams.topMargin + marginParams.bottomMargin;
} else {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
int childWidth = child.getMeasuredWidth() + horizontalMargin;
int childHeight = child.getMeasuredHeight() + verticalMargin;
if (allowFlow && rowWidth + childWidth > rowSize) { // Need flow to next row
// Save parameters for current row
mHorizontalSpacingForRow.add(getSpacingForRow(childSpacing, rowSize, rowTotalChildWidth, childNumInRow));
mChildNumForRow.add(childNumInRow);
mHeightForRow.add(maxChildHeightInRow);
mWidthForRow.add(rowWidth - (int) tmpSpacing);
if (mHorizontalSpacingForRow.size() <= mMaxRows) {
measuredHeight += maxChildHeightInRow;
}
measuredWidth = Math.max(measuredWidth, rowWidth);
// Place the child view to next row
childNumInRow = 1;
rowWidth = childWidth + (int) tmpSpacing;
rowTotalChildWidth = childWidth;
maxChildHeightInRow = childHeight;
} else {
childNumInRow++;
rowWidth += (int) (childWidth + tmpSpacing);
rowTotalChildWidth += childWidth;
maxChildHeightInRow = Math.max(maxChildHeightInRow, childHeight);
}
}
// Measure remaining child views in the last row
if (mChildSpacingForLastRow == SPACING_ALIGN) {
// For SPACING_ALIGN, use the same spacing from the row above if there is more than one
// row.
if (!mHorizontalSpacingForRow.isEmpty()) mHorizontalSpacingForRow.add(mHorizontalSpacingForRow.get(mHorizontalSpacingForRow.size() - 1));
else mHorizontalSpacingForRow.add(getSpacingForRow(childSpacing, rowSize, rowTotalChildWidth, childNumInRow));
} else if (mChildSpacingForLastRow != SPACING_UNDEFINED) {
// For SPACING_AUTO and specific DP values, apply them to the spacing strategy.
mHorizontalSpacingForRow.add(getSpacingForRow(mChildSpacingForLastRow, rowSize, rowTotalChildWidth, childNumInRow));
} else {
// For SPACING_UNDEFINED, apply childSpacing to the spacing strategy for the last row.
mHorizontalSpacingForRow.add(getSpacingForRow(childSpacing, rowSize, rowTotalChildWidth, childNumInRow));
}
mChildNumForRow.add(childNumInRow);
mHeightForRow.add(maxChildHeightInRow);
mWidthForRow.add(rowWidth - (int) tmpSpacing);
if (mHorizontalSpacingForRow.size() <= mMaxRows) measuredHeight += maxChildHeightInRow;
measuredWidth = Math.max(measuredWidth, rowWidth);
if (childSpacing == SPACING_AUTO) measuredWidth = widthSize;
else if (widthMode == MeasureSpec.UNSPECIFIED) measuredWidth = measuredWidth + getPaddingLeft() + getPaddingRight();
else measuredWidth = Math.min(measuredWidth + getPaddingLeft() + getPaddingRight(), widthSize);
measuredHeight += getPaddingTop() + getPaddingBottom();
int rowNum = Math.min(mHorizontalSpacingForRow.size(), mMaxRows);
float rowSpacing = mRowSpacing == SPACING_AUTO && heightMode == MeasureSpec.UNSPECIFIED ? 0 : mRowSpacing;
if (rowSpacing == SPACING_AUTO) {
if (rowNum > 1) {
mAdjustedRowSpacing = (heightSize - measuredHeight) / (rowNum - 1.0f);
} else {
mAdjustedRowSpacing = 0;
}
measuredHeight = heightSize;
} else {
mAdjustedRowSpacing = rowSpacing;
if (rowNum > 1) {
measuredHeight = heightMode == MeasureSpec.UNSPECIFIED
? ((int) (measuredHeight + mAdjustedRowSpacing * (rowNum - 1)))
: (Math.min((int) (measuredHeight + mAdjustedRowSpacing * (rowNum - 1)),
heightSize));
}
}
mExactMeasuredHeight = measuredHeight;
measuredWidth = widthMode == MeasureSpec.EXACTLY ? widthSize : measuredWidth;
measuredHeight = heightMode == MeasureSpec.EXACTLY ? heightSize : measuredHeight;
setMeasuredDimension(measuredWidth, measuredHeight);
}
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int paddingLeft = getPaddingLeft(), paddingRight = getPaddingRight(),
paddingTop = getPaddingTop(), paddingBottom = getPaddingBottom();
setLayoutDirection(super.getLayoutDirection());
int x = mRtl ? (getWidth() - paddingRight) : paddingLeft;
int y = paddingTop;
int verticalGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
int horizontalGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
if (verticalGravity==Gravity.CENTER_VERTICAL) y += (b - t - paddingTop - paddingBottom - mExactMeasuredHeight) / 2;
else if (verticalGravity==Gravity.BOTTOM) y += b - t - paddingTop - paddingBottom - mExactMeasuredHeight;
int horizontalPadding = paddingLeft + paddingRight, layoutWidth = r - l;
x += getHorizontalGravityOffsetForRow(horizontalGravity, layoutWidth, horizontalPadding, 0);
int verticalRowGravity = mRowVerticalGravity & Gravity.VERTICAL_GRAVITY_MASK;
int rowCount = mChildNumForRow.size(), childIdx = 0;
for (int row = 0; row < Math.min(rowCount, mMaxRows); row++) {
int childNum = mChildNumForRow.get(row);
int rowHeight = mHeightForRow.get(row);
float spacing = mHorizontalSpacingForRow.get(row);
for (int i = 0; i < childNum && childIdx < getChildCount(); ) {
View child = getChildAt(childIdx++);
if (child.getVisibility() == GONE) continue;
i++;
LayoutParams childParams = child.getLayoutParams();
int marginLeft = 0, marginTop = 0, marginBottom = 0, marginRight = 0;
if (childParams instanceof MarginLayoutParams) {
MarginLayoutParams marginParams = (MarginLayoutParams) childParams;
marginLeft = marginParams.leftMargin;
marginRight = marginParams.rightMargin;
marginTop = marginParams.topMargin;
marginBottom = marginParams.bottomMargin;
}
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
int tt = y + marginTop;
if (verticalRowGravity == Gravity.BOTTOM) {
tt = y + rowHeight - marginBottom - childHeight;
} else if (verticalRowGravity == Gravity.CENTER_VERTICAL) {
tt = y + marginTop + (rowHeight - marginTop - marginBottom - childHeight) / 2;
}
int bb = tt + childHeight;
if (mRtl) {
int l1 = x - marginRight - childWidth;
int r1 = x - marginRight;
child.layout(l1, tt, r1, bb);
x -= (int) (childWidth + spacing + marginLeft + marginRight);
} else {
int l2 = x + marginLeft;
int r2 = x + marginLeft + childWidth;
child.layout(l2, tt, r2, bb);
x += (int) (childWidth + spacing + marginLeft + marginRight);
}
}
x = mRtl ? (getWidth() - paddingRight) : paddingLeft;
x += getHorizontalGravityOffsetForRow(
horizontalGravity, layoutWidth, horizontalPadding, row + 1);
y += (int) (rowHeight + mAdjustedRowSpacing);
}
for (int i = childIdx; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) child.layout(0, 0, 0, 0);
}
}
@SuppressLint("RtlHardcoded")
private int getHorizontalGravityOffsetForRow(int horizontalGravity, int parentWidth, int horizontalPadding, int row) {
if (mChildSpacing == SPACING_AUTO || row >= mWidthForRow.size() || row >= mChildNumForRow.size() || mChildNumForRow.get(row) <= 0) return 0;
if ((horizontalGravity == Gravity.END && mRtl)) return -(parentWidth - horizontalPadding - mWidthForRow.get(row));
if (horizontalGravity==Gravity.CENTER_HORIZONTAL && !mRtl) return (parentWidth - horizontalPadding - mWidthForRow.get(row)) / 2;
if ((horizontalGravity == Gravity.RIGHT && !mRtl) || horizontalGravity == Gravity.END) return parentWidth - horizontalPadding - mWidthForRow.get(row);
if (horizontalGravity==Gravity.CENTER_HORIZONTAL) return -(parentWidth - horizontalPadding - mWidthForRow.get(row)) / 2;
if (horizontalGravity == Gravity.LEFT && mRtl) return -(parentWidth - horizontalPadding - mWidthForRow.get(row));
return 0;
}
@Override protected LayoutParams generateLayoutParams(LayoutParams p) {
return new MarginLayoutParams(p);
}
@Override public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
public boolean isFlow() {
return mFlow;
}
public void setFlow(boolean flow) {
mFlow = flow;
requestLayout();
}
public int getChildSpacing() {
return mChildSpacing;
}
public void setChildSpacing(int childSpacing) {
mChildSpacing = childSpacing;
requestLayout();
}
public int getChildSpacingForLastRow() {
return mChildSpacingForLastRow;
}
public void setChildSpacingForLastRow(int childSpacingForLastRow) {
mChildSpacingForLastRow = childSpacingForLastRow;
requestLayout();
}
public float getRowSpacing() {
return mRowSpacing;
}
public void setRowSpacing(float rowSpacing) {
mRowSpacing = rowSpacing;
requestLayout();
}
public int getMaxRows() {
return mMaxRows;
}
public void setMaxRows(int maxRows) {
mMaxRows = maxRows;
requestLayout();
}
public void setGravity(int gravity) {
if (mGravity != gravity) {
mGravity = gravity;
requestLayout();
}
}
public void setRowVerticalGravity(int rowVerticalGravity) {
if (mRowVerticalGravity != rowVerticalGravity) {
mRowVerticalGravity = rowVerticalGravity;
requestLayout();
}
}
@Override public void setLayoutDirection(int layoutDirection) {
super.setLayoutDirection(layoutDirection);
if (layoutDirection == LAYOUT_DIRECTION_INHERIT) {
if (getParent()==null) setRtl(getResources().getConfiguration().getLayoutDirection() == LAYOUT_DIRECTION_RTL);
else setRtl(getParent().getLayoutDirection() == LAYOUT_DIRECTION_RTL);
}
if (layoutDirection == LAYOUT_DIRECTION_LOCALE) setRtl(getResources().getConfiguration().getLayoutDirection() == LAYOUT_DIRECTION_RTL);
else setRtl(layoutDirection == LAYOUT_DIRECTION_RTL);
}
private void setRtl(boolean rtl) {
mRtl = rtl;
requestLayout();
}
public int getMinChildSpacing() {
return mMinChildSpacing;
}
public void setMinChildSpacing(int minChildSpacing) {
this.mMinChildSpacing = minChildSpacing;
requestLayout();
}
public int getRowsCount() {
return mChildNumForRow.size();
}
private float getSpacingForRow(int spacingAttribute, int rowSize, int usedSize, int childNum) {
if (spacingAttribute != SPACING_AUTO) return spacingAttribute;
if (childNum > 1) return (rowSize - usedSize) / (childNum - 1.0f);
return 0;
}
private float dpToPx(float dp) {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
}
Tell me the fix what is the issue in onMeasuse!
I tried to change the things in the last row code by trying to add last child height to measuredHeight but that is not working either.