package org.telegram.ui.Components; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.util.Pair; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import org.telegram.messenger.AndroidUtilities; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ArticleViewer; import org.telegram.ui.Cells.TextSelectionHelper; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import static android.view.Gravity.AXIS_PULL_AFTER; import static android.view.Gravity.AXIS_PULL_BEFORE; import static android.view.Gravity.AXIS_SPECIFIED; import static android.view.Gravity.AXIS_X_SHIFT; import static android.view.Gravity.AXIS_Y_SHIFT; import static android.view.Gravity.HORIZONTAL_GRAVITY_MASK; import static android.view.Gravity.RELATIVE_LAYOUT_DIRECTION; import static android.view.Gravity.VERTICAL_GRAVITY_MASK; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.makeMeasureSpec; import static java.lang.Math.max; import static java.lang.Math.min; public class TableLayout extends View { public static final int HORIZONTAL = LinearLayout.HORIZONTAL; public static final int VERTICAL = LinearLayout.VERTICAL; public static final int UNDEFINED = Integer.MIN_VALUE; public static final int ALIGN_BOUNDS = 0; public static final int ALIGN_MARGINS = 1; static final int MAX_SIZE = 100000; static final int UNINITIALIZED_HASH = 0; private TextSelectionHelper.ArticleTextSelectionHelper textSelectionHelper; private int colCount; private static final int DEFAULT_ORIENTATION = HORIZONTAL; private static final int DEFAULT_COUNT = UNDEFINED; private static final boolean DEFAULT_USE_DEFAULT_MARGINS = false; private static final boolean DEFAULT_ORDER_PRESERVED = true; private static final int DEFAULT_ALIGNMENT_MODE = ALIGN_MARGINS; private final Axis mHorizontalAxis = new Axis(true); private final Axis mVerticalAxis = new Axis(false); private int mOrientation = DEFAULT_ORIENTATION; private boolean mUseDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS; private int mAlignmentMode = DEFAULT_ALIGNMENT_MODE; private int mDefaultGap; private int mLastLayoutParamsHashCode = UNINITIALIZED_HASH; private int itemPaddingTop = AndroidUtilities.dp(7); private int itemPaddingLeft = AndroidUtilities.dp(8); private boolean drawLines; private boolean isStriped; private boolean isRtl; private ArrayList cellsToFixHeight = new ArrayList<>(); private ArrayList rowSpans = new ArrayList<>(); private Path linePath = new Path(); private Path backgroundPath = new Path(); private RectF rect = new RectF(); private float[] radii = new float[8]; public class Child { private LayoutParams layoutParams; public ArticleViewer.DrawingText textLayout; private TLRPC.TL_pageTableCell cell; private int index; public int textWidth; public int textHeight; public int textX; public int textY; public int textLeft; public int rowspan; private int measuredWidth; private int measuredHeight; private int fixedHeight; public int x; public int y; private int selectionIndex = -1; public Child(int i) { index = i; } public LayoutParams getLayoutParams() { return layoutParams; } public int getMeasuredWidth() { return measuredWidth; } public int getMeasuredHeight() { return measuredHeight; } public void measure(int width, int height, boolean first) { measuredWidth = width; measuredHeight = height; if (first) { fixedHeight = measuredHeight; } if (cell != null) { if (cell.valign_middle) { textY = (measuredHeight - textHeight) / 2; } else if (cell.valign_bottom) { textY = measuredHeight - textHeight - itemPaddingTop; } else { textY = itemPaddingTop; } if (textLayout != null) { int lineCount = textLayout.getLineCount(); if (!first && (lineCount > 1 || lineCount > 0 && (cell.align_center || cell.align_right))) { setTextLayout(delegate.createTextLayout(cell, measuredWidth - itemPaddingLeft * 2)); fixedHeight = textHeight + itemPaddingTop * 2; } if (textLeft != 0) { textX = -textLeft; if (cell.align_right) { textX += (measuredWidth - textWidth - itemPaddingLeft); } else if (cell.align_center) { textX += Math.round((measuredWidth - textWidth) / 2); } else { textX += itemPaddingLeft; } } else { textX = itemPaddingLeft; } } } } public void setTextLayout(ArticleViewer.DrawingText layout) { textLayout = layout; if (layout != null) { textWidth = 0; textLeft = 0; for (int a = 0, N = layout.getLineCount(); a < N; a++) { float lineLeft = layout.getLineLeft(a); textLeft = a == 0 ? (int) Math.ceil(lineLeft) : Math.min(textLeft, (int) Math.ceil(lineLeft)); textWidth = (int) Math.ceil(Math.max(layout.getLineWidth(a), textWidth)); } textHeight = layout.getHeight(); } else { textLeft = 0; textWidth = 0; textHeight = 0; } } public void layout(int left, int top, int right, int bottom) { x = left; y = top; } public int getTextX() { return x + textX; } public int getTextY() { return y + textY; } public void setFixedHeight(int value) { measuredHeight = fixedHeight; if (cell.valign_middle) { textY = (measuredHeight - textHeight) / 2; } else if (cell.valign_bottom) { textY = measuredHeight - textHeight - itemPaddingTop; } } public void draw(Canvas canvas, View view) { if (cell == null) { return; } boolean isLastX = x + measuredWidth == TableLayout.this.getMeasuredWidth(); boolean isLastY = y + measuredHeight == TableLayout.this.getMeasuredHeight(); int rad = AndroidUtilities.dp(3); if (cell.header || isStriped && layoutParams.rowSpec.span.min % 2 == 0) { boolean hasCorners = false; if (x == 0 && y == 0) { radii[0] = radii[1] = rad; hasCorners = true; } else { radii[0] = radii[1] = 0; } if (isLastX && y == 0) { radii[2] = radii[3] = rad; hasCorners = true; } else { radii[2] = radii[3] = 0; } if (isLastX && isLastY) { radii[4] = radii[5] = rad; hasCorners = true; } else { radii[4] = radii[5] = 0; } if (x == 0 && isLastY) { radii[6] = radii[7] = rad; hasCorners = true; } else { radii[6] = radii[7] = 0; } if (hasCorners) { rect.set(x, y, x + measuredWidth, y + measuredHeight); backgroundPath.reset(); backgroundPath.addRoundRect(rect, radii, Path.Direction.CW); if (cell.header) { canvas.drawPath(backgroundPath, delegate.getHeaderPaint()); } else { canvas.drawPath(backgroundPath, delegate.getStripPaint()); } } else { if (cell.header) { canvas.drawRect(x, y, x + measuredWidth, y + measuredHeight, delegate.getHeaderPaint()); } else { canvas.drawRect(x, y, x + measuredWidth, y + measuredHeight, delegate.getStripPaint()); } } } if (textLayout != null) { canvas.save(); canvas.translate(getTextX(), getTextY()); if (selectionIndex >= 0) { textSelectionHelper.draw(canvas, (TextSelectionHelper.ArticleSelectableView) getParent().getParent(), selectionIndex); } textLayout.draw(canvas, view); canvas.restore(); } if (drawLines) { Paint linePaint = delegate.getLinePaint(); Paint halfLinePaint = delegate.getLinePaint(); float strokeWidth = linePaint.getStrokeWidth() / 2.0f; float halfStrokeWidth = halfLinePaint.getStrokeWidth() / 2.0f; float start; float end; if (x == 0) { start = y; end = y + measuredHeight; if (y == 0) { start += rad; } if (end == TableLayout.this.getMeasuredHeight()) { end -= rad; } canvas.drawLine(x + strokeWidth, start, x + strokeWidth, end, linePaint); } else { canvas.drawLine(x - halfStrokeWidth, y, x - halfStrokeWidth, y + measuredHeight, halfLinePaint); } if (y == 0) { start = x; end = x + measuredWidth; if (x == 0) { start += rad; } if (end == TableLayout.this.getMeasuredWidth()) { end -= rad; } canvas.drawLine(start, y + strokeWidth, end, y + strokeWidth, linePaint); } else { canvas.drawLine(x, y - halfStrokeWidth, x + measuredWidth, y - halfStrokeWidth, halfLinePaint); } if (isLastX && y == 0) { start = y + rad; } else { start = y - strokeWidth; } if (isLastX && isLastY) { end = y + measuredHeight - rad; } else { end = y + measuredHeight - strokeWidth; } canvas.drawLine(x + measuredWidth - strokeWidth, start, x + measuredWidth - strokeWidth, end, linePaint); if (x == 0 && isLastY) { start = x + rad; } else { start = x - strokeWidth; } if (isLastX && isLastY) { end = x + measuredWidth - rad; } else { end = x + measuredWidth - strokeWidth; } canvas.drawLine(start, y + measuredHeight - strokeWidth, end, y + measuredHeight - strokeWidth, linePaint); if (x == 0 && y == 0) { rect.set(x + strokeWidth, y + strokeWidth, x + strokeWidth + rad * 2, y + strokeWidth + rad * 2); canvas.drawArc(rect, -180, 90, false, linePaint); } if (isLastX && y == 0) { rect.set(x + measuredWidth - strokeWidth - rad * 2, y + strokeWidth, x + measuredWidth - strokeWidth, y + strokeWidth + rad * 2); canvas.drawArc(rect, 0, -90, false, linePaint); } if (x == 0 && isLastY) { rect.set(x + strokeWidth, y + measuredHeight - strokeWidth - rad * 2, x + strokeWidth + rad * 2, y + measuredHeight - strokeWidth); canvas.drawArc(rect, 180, -90, false, linePaint); } if (isLastX && isLastY) { rect.set(x + measuredWidth - strokeWidth - rad * 2, y + measuredHeight - strokeWidth - rad * 2, x + measuredWidth - strokeWidth, y + measuredHeight - strokeWidth); canvas.drawArc(rect, 0, 90, false, linePaint); } } } public void setSelectionIndex(int selectionIndex) { this.selectionIndex = selectionIndex; } public int getRow() { return rowspan + 10; } } public interface TableLayoutDelegate { ArticleViewer.DrawingText createTextLayout(TLRPC.TL_pageTableCell cell, int maxWidth); Paint getLinePaint(); Paint getHalfLinePaint(); Paint getHeaderPaint(); Paint getStripPaint(); void onLayoutChild(ArticleViewer.DrawingText text, int x, int y); } private TableLayoutDelegate delegate; private ArrayList childrens = new ArrayList<>(); public void addChild(int x, int y, int colspan, int rowspan) { Child child = new Child(childrens.size()); LayoutParams layoutParams = new LayoutParams(); layoutParams.rowSpec = new Spec(false, new Interval(y, y + rowspan), FILL, 0.0f); layoutParams.columnSpec = new Spec(false, new Interval(x, x + colspan), FILL, 0.0f); child.layoutParams = layoutParams; child.rowspan = y; childrens.add(child); invalidateStructure(); } public void addChild(TLRPC.TL_pageTableCell cell, int x, int y, int colspan) { if (colspan == 0) { colspan = 1; } Child child = new Child(childrens.size()); child.cell = cell; LayoutParams layoutParams = new LayoutParams(); layoutParams.rowSpec = new Spec(false, new Interval(y, y + (cell.rowspan != 0 ? cell.rowspan : 1)), FILL, 0.0f); layoutParams.columnSpec = new Spec(false, new Interval(x, x + colspan), FILL, 1.0f); child.layoutParams = layoutParams; child.rowspan = y; childrens.add(child); if (cell.rowspan > 1) { rowSpans.add(new Point(y, y + cell.rowspan)); } invalidateStructure(); } public void setDrawLines(boolean value) { drawLines = value; } public void setStriped(boolean value) { isStriped = value; } public void setRtl(boolean value) { isRtl = value; } public void removeAllChildrens() { childrens.clear(); rowSpans.clear(); invalidateStructure(); } public int getChildCount() { return childrens.size(); } public Child getChildAt(int index) { if (index < 0 || index >= childrens.size()) { return null; } return childrens.get(index); } public TableLayout(Context context, TableLayoutDelegate tableLayoutDelegate, TextSelectionHelper.ArticleTextSelectionHelper textSelectionHelper) { super(context); this.textSelectionHelper = textSelectionHelper; setRowCount(DEFAULT_COUNT); setColumnCount(DEFAULT_COUNT); setOrientation(DEFAULT_ORIENTATION); setUseDefaultMargins(DEFAULT_USE_DEFAULT_MARGINS); setAlignmentMode(DEFAULT_ALIGNMENT_MODE); setRowOrderPreserved(DEFAULT_ORDER_PRESERVED); setColumnOrderPreserved(DEFAULT_ORDER_PRESERVED); delegate = tableLayoutDelegate; } public int getOrientation() { return mOrientation; } public void setOrientation(int orientation) { if (this.mOrientation != orientation) { this.mOrientation = orientation; invalidateStructure(); requestLayout(); } } public int getRowCount() { return mVerticalAxis.getCount(); } public void setRowCount(int rowCount) { mVerticalAxis.setCount(rowCount); invalidateStructure(); requestLayout(); } public int getColumnCount() { return mHorizontalAxis.getCount(); } public void setColumnCount(int columnCount) { mHorizontalAxis.setCount(columnCount); invalidateStructure(); requestLayout(); } public boolean getUseDefaultMargins() { return mUseDefaultMargins; } public void setUseDefaultMargins(boolean useDefaultMargins) { this.mUseDefaultMargins = useDefaultMargins; requestLayout(); } public int getAlignmentMode() { return mAlignmentMode; } public void setAlignmentMode(int alignmentMode) { this.mAlignmentMode = alignmentMode; requestLayout(); } public boolean isRowOrderPreserved() { return mVerticalAxis.isOrderPreserved(); } public void setRowOrderPreserved(boolean rowOrderPreserved) { mVerticalAxis.setOrderPreserved(rowOrderPreserved); invalidateStructure(); requestLayout(); } public boolean isColumnOrderPreserved() { return mHorizontalAxis.isOrderPreserved(); } public void setColumnOrderPreserved(boolean columnOrderPreserved) { mHorizontalAxis.setOrderPreserved(columnOrderPreserved); invalidateStructure(); requestLayout(); } static int max2(int[] a, int valueIfEmpty) { int result = valueIfEmpty; for (int i = 0, N = a.length; i < N; i++) { result = max(result, a[i]); } return result; } @SuppressWarnings("unchecked") static T[] append(T[] a, T[] b) { T[] result = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length + b.length); System.arraycopy(a, 0, result, 0, a.length); System.arraycopy(b, 0, result, a.length, b.length); return result; } static Alignment getAlignment(int gravity, boolean horizontal) { int mask = horizontal ? HORIZONTAL_GRAVITY_MASK : VERTICAL_GRAVITY_MASK; int shift = horizontal ? AXIS_X_SHIFT : AXIS_Y_SHIFT; int flags = (gravity & mask) >> shift; switch (flags) { case (AXIS_SPECIFIED | AXIS_PULL_BEFORE): return horizontal ? LEFT : TOP; case (AXIS_SPECIFIED | AXIS_PULL_AFTER): return horizontal ? RIGHT : BOTTOM; case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | AXIS_PULL_AFTER): return FILL; case AXIS_SPECIFIED: return CENTER; case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | RELATIVE_LAYOUT_DIRECTION): return START; case (AXIS_SPECIFIED | AXIS_PULL_AFTER | RELATIVE_LAYOUT_DIRECTION): return END; default: return UNDEFINED_ALIGNMENT; } } private int getDefaultMargin(Child c, boolean horizontal, boolean leading) { return mDefaultGap / 2; } private int getDefaultMargin(Child c, boolean isAtEdge, boolean horizontal, boolean leading) { return getDefaultMargin(c, horizontal, leading); } private int getDefaultMargin(Child c, LayoutParams p, boolean horizontal, boolean leading) { if (!mUseDefaultMargins) { return 0; } Spec spec = horizontal ? p.columnSpec : p.rowSpec; Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis; Interval span = spec.span; boolean leading1 = (horizontal && isRtl) != leading; boolean isAtEdge = leading1 ? (span.min == 0) : (span.max == axis.getCount()); return getDefaultMargin(c, isAtEdge, horizontal, leading); } int getMargin1(Child view, boolean horizontal, boolean leading) { LayoutParams lp = view.getLayoutParams(); int margin = horizontal ? (leading ? lp.leftMargin : lp.rightMargin) : (leading ? lp.topMargin : lp.bottomMargin); return margin == UNDEFINED ? getDefaultMargin(view, lp, horizontal, leading) : margin; } private int getMargin(Child view, boolean horizontal, boolean leading) { if (mAlignmentMode == ALIGN_MARGINS) { return getMargin1(view, horizontal, leading); } else { Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis; int[] margins = leading ? axis.getLeadingMargins() : axis.getTrailingMargins(); LayoutParams lp = view.getLayoutParams(); Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; int index = leading ? spec.span.min : spec.span.max; return margins[index]; } } private int getTotalMargin(Child child, boolean horizontal) { return getMargin(child, horizontal, true) + getMargin(child, horizontal, false); } private static boolean fits(int[] a, int value, int start, int end) { if (end > a.length) { return false; } for (int i = start; i < end; i++) { if (a[i] > value) { return false; } } return true; } private static void procrusteanFill(int[] a, int start, int end, int value) { int length = a.length; Arrays.fill(a, min(start, length), min(end, length), value); } private static void setCellGroup(LayoutParams lp, int row, int rowSpan, int col, int colSpan) { lp.setRowSpecSpan(new Interval(row, row + rowSpan)); lp.setColumnSpecSpan(new Interval(col, col + colSpan)); } private static int clip(Interval minorRange, boolean minorWasDefined, int count) { int size = minorRange.size(); if (count == 0) { return size; } int min = minorWasDefined ? min(minorRange.min, count) : 0; return min(size, count - min); } private void validateLayoutParams() { final boolean horizontal = (mOrientation == HORIZONTAL); final Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis; final int count = (axis.definedCount != UNDEFINED) ? axis.definedCount : 0; int major = 0; int minor = 0; int[] maxSizes = new int[count]; for (int i = 0, N = getChildCount(); i < N; i++) { LayoutParams lp = getChildAt(i).getLayoutParams(); final Spec majorSpec = horizontal ? lp.rowSpec : lp.columnSpec; final Interval majorRange = majorSpec.span; final boolean majorWasDefined = majorSpec.startDefined; final int majorSpan = majorRange.size(); if (majorWasDefined) { major = majorRange.min; } final Spec minorSpec = horizontal ? lp.columnSpec : lp.rowSpec; final Interval minorRange = minorSpec.span; final boolean minorWasDefined = minorSpec.startDefined; final int minorSpan = clip(minorRange, minorWasDefined, count); if (minorWasDefined) { minor = minorRange.min; } if (count != 0) { if (!majorWasDefined || !minorWasDefined) { while (!fits(maxSizes, major, minor, minor + minorSpan)) { if (minorWasDefined) { major++; } else { if (minor + minorSpan <= count) { minor++; } else { minor = 0; major++; } } } } procrusteanFill(maxSizes, minor, minor + minorSpan, major + majorSpan); } if (horizontal) { setCellGroup(lp, major, majorSpan, minor, minorSpan); } else { setCellGroup(lp, minor, minorSpan, major, majorSpan); } minor = minor + minorSpan; } } private void invalidateStructure() { mLastLayoutParamsHashCode = UNINITIALIZED_HASH; mHorizontalAxis.invalidateStructure(); mVerticalAxis.invalidateStructure(); invalidateValues(); } private void invalidateValues() { if (mHorizontalAxis != null && mVerticalAxis != null) { mHorizontalAxis.invalidateValues(); mVerticalAxis.invalidateValues(); } } private static void handleInvalidParams(String msg) { throw new IllegalArgumentException(msg + ". "); } private void checkLayoutParams(LayoutParams lp, boolean horizontal) { String groupName = horizontal ? "column" : "row"; Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; Interval span = spec.span; if (span.min != UNDEFINED && span.min < 0) { handleInvalidParams(groupName + " indices must be positive"); } Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis; int count = axis.definedCount; if (count != UNDEFINED) { if (span.max > count) { handleInvalidParams(groupName + " indices (start + span) mustn't exceed the " + groupName + " count"); } if (span.size() > count) { handleInvalidParams(groupName + " span mustn't exceed the " + groupName + " count"); } } } @Override protected void onDraw(Canvas canvas) { for (int i = 0, N = getChildCount(); i < N; i++) { Child c = getChildAt(i); c.draw(canvas, this); } } private int computeLayoutParamsHashCode() { int result = 1; for (int i = 0, N = getChildCount(); i < N; i++) { Child c = getChildAt(i); LayoutParams lp = c.getLayoutParams(); result = 31 * result + lp.hashCode(); } return result; } private void consistencyCheck() { if (mLastLayoutParamsHashCode == UNINITIALIZED_HASH) { validateLayoutParams(); mLastLayoutParamsHashCode = computeLayoutParamsHashCode(); } else if (mLastLayoutParamsHashCode != computeLayoutParamsHashCode()) { invalidateStructure(); consistencyCheck(); } } private void measureChildWithMargins2(Child child, int parentWidthSpec, int parentHeightSpec, int childWidth, int childHeight, boolean first) { child.measure(getTotalMargin(child, true) + childWidth, getTotalMargin(child, false) + childHeight, first); } private void measureChildrenWithMargins(int widthSpec, int heightSpec, boolean firstPass) { int N = getChildCount(); //float maxWidth = 0; for (int i = 0; i < N; i++) { Child c = getChildAt(i); LayoutParams lp = c.getLayoutParams(); if (firstPass) { int width = MeasureSpec.getSize(widthSpec); int maxCellWidth; if (colCount == 2) { maxCellWidth = (int) (width / 2.0f) - itemPaddingLeft * 4; } else { maxCellWidth = (int) (width / 1.5f); } c.setTextLayout(delegate.createTextLayout(c.cell, maxCellWidth)); if (c.textLayout != null) { lp.width = c.textWidth + itemPaddingLeft * 2; lp.height = c.textHeight + itemPaddingTop * 2; } else { lp.width = 0; lp.height = 0; } measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height, true); //maxWidth = Math.max(maxWidth, c.textWidth); } else { boolean horizontal = (mOrientation == HORIZONTAL); Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; if (spec.getAbsoluteAlignment(horizontal) == FILL) { Interval span = spec.span; Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis; int[] locations = axis.getLocations(); int cellSize = locations[span.max] - locations[span.min]; int viewSize = cellSize - getTotalMargin(c, horizontal); if (horizontal) { measureChildWithMargins2(c, widthSpec, heightSpec, viewSize, lp.height, false); } else { measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, viewSize, false); } } } } /*if (firstPass) { for (int i = 0; i < N; i++) { Child c = getChildAt(i); LayoutParams lp = c.getLayoutParams(); lp.columnSpec.weight = c.textWidth / maxWidth; } }*/ } static int adjust(int measureSpec, int delta) { return makeMeasureSpec(MeasureSpec.getSize(measureSpec + delta), MeasureSpec.getMode(measureSpec)); } @Override protected void onMeasure(int widthSpec, int heightSpec) { consistencyCheck(); invalidateValues(); colCount = 0; for (int a = 0, N = getChildCount(); a < N; a++) { Child child = getChildAt(a); colCount = Math.max(colCount, child.layoutParams.columnSpec.span.max); } measureChildrenWithMargins(widthSpec, heightSpec, true); int widthSansPadding; int heightSansPadding; if (mOrientation == HORIZONTAL) { widthSansPadding = mHorizontalAxis.getMeasure(widthSpec); measureChildrenWithMargins(widthSpec, heightSpec, false); heightSansPadding = mVerticalAxis.getMeasure(heightSpec); } else { heightSansPadding = mVerticalAxis.getMeasure(heightSpec); measureChildrenWithMargins(widthSpec, heightSpec, false); widthSansPadding = mHorizontalAxis.getMeasure(widthSpec); } int measuredWidth = max(widthSansPadding, MeasureSpec.getSize(widthSpec)); int measuredHeight = max(heightSansPadding, getSuggestedMinimumHeight()); setMeasuredDimension(measuredWidth, measuredHeight); mHorizontalAxis.layout(measuredWidth); mVerticalAxis.layout(measuredHeight); int[] hLocations = mHorizontalAxis.getLocations(); int[] vLocations = mVerticalAxis.getLocations(); int fixedHeight = measuredHeight; cellsToFixHeight.clear(); measuredWidth = hLocations[hLocations.length - 1]; for (int i = 0, N = getChildCount(); i < N; i++) { Child c = getChildAt(i); LayoutParams lp = c.getLayoutParams(); Spec columnSpec = lp.columnSpec; Spec rowSpec = lp.rowSpec; Interval colSpan = columnSpec.span; Interval rowSpan = rowSpec.span; int x1 = hLocations[colSpan.min]; int y1 = vLocations[rowSpan.min]; int x2 = hLocations[colSpan.max]; int y2 = vLocations[rowSpan.max]; int cellWidth = x2 - x1; int cellHeight = y2 - y1; int pWidth = getMeasurement(c, true); int pHeight = getMeasurement(c, false); Alignment hAlign = columnSpec.getAbsoluteAlignment(true); Alignment vAlign = rowSpec.getAbsoluteAlignment(false); Bounds boundsX = mHorizontalAxis.getGroupBounds().getValue(i); Bounds boundsY = mVerticalAxis.getGroupBounds().getValue(i); int gravityOffsetX = hAlign.getGravityOffset(c, cellWidth - boundsX.size(true)); int gravityOffsetY = vAlign.getGravityOffset(c, cellHeight - boundsY.size(true)); int leftMargin = getMargin(c, true, true); int topMargin = getMargin(c, false, true); int rightMargin = getMargin(c, true, false); int bottomMargin = getMargin(c, false, false); int sumMarginsX = leftMargin + rightMargin; int sumMarginsY = topMargin + bottomMargin; int alignmentOffsetX = boundsX.getOffset(this, c, hAlign, pWidth + sumMarginsX, true); int alignmentOffsetY = boundsY.getOffset(this, c, vAlign, pHeight + sumMarginsY, false); int width = hAlign.getSizeInCell(c, pWidth, cellWidth - sumMarginsX); int height = vAlign.getSizeInCell(c, pHeight, cellHeight - sumMarginsY); int dx = x1 + gravityOffsetX + alignmentOffsetX; int cx = !isRtl ? leftMargin + dx : measuredWidth - width - rightMargin - dx; int cy = y1 + gravityOffsetY + alignmentOffsetY + topMargin; if (c.cell != null) { if (width != c.getMeasuredWidth() || height != c.getMeasuredHeight()) { c.measure(width, height, false); } if (c.fixedHeight != 0 && c.fixedHeight != height && c.layoutParams.rowSpec.span.max - c.layoutParams.rowSpec.span.min <= 1) { boolean found = false; for (int a = 0, size = rowSpans.size(); a < size; a++) { Point p = rowSpans.get(a); if (p.x <= c.layoutParams.rowSpec.span.min && p.y > c.layoutParams.rowSpec.span.min) { found = true; break; } } if (!found) { cellsToFixHeight.add(c); } } } c.layout(cx, cy, cx + width, cy + height); } for (int a = 0, N = cellsToFixHeight.size(); a < N; a++) { Child child = cellsToFixHeight.get(a); boolean skip = false; int heightDiff = child.measuredHeight - child.fixedHeight; for (int i = child.index + 1, size = childrens.size(); i < size; i++) { Child next = childrens.get(i); if (child.layoutParams.rowSpec.span.min == next.layoutParams.rowSpec.span.min) { if (child.fixedHeight < next.fixedHeight) { skip = true; break; } else { int diff = next.measuredHeight - next.fixedHeight; if (diff > 0) { heightDiff = Math.min(heightDiff, diff); } } } else { break; } } if (!skip) { for (int i = child.index - 1; i >= 0; i--) { Child next = childrens.get(i); if (child.layoutParams.rowSpec.span.min == next.layoutParams.rowSpec.span.min) { if (child.fixedHeight < next.fixedHeight) { skip = true; break; } else { int diff = next.measuredHeight - next.fixedHeight; if (diff > 0) { heightDiff = Math.min(heightDiff, diff); } } } else { break; } } } if (skip) { continue; } child.setFixedHeight(child.fixedHeight); fixedHeight -= heightDiff; for (int i = 0, size = childrens.size(); i < size; i++) { Child next = childrens.get(i); if (child == next) { continue; } if (child.layoutParams.rowSpec.span.min == next.layoutParams.rowSpec.span.min) { if (next.fixedHeight != next.measuredHeight) { cellsToFixHeight.remove(next); if (next.index < child.index) { a--; } N--; } next.measuredHeight -= heightDiff; next.measure(next.measuredWidth, next.measuredHeight, true); } else if (child.layoutParams.rowSpec.span.min < next.layoutParams.rowSpec.span.min) { next.y -= heightDiff; } } } for (int i = 0, N = getChildCount(); i < N; i++) { Child c = getChildAt(i); delegate.onLayoutChild(c.textLayout, c.getTextX(), c.getTextY()); } setMeasuredDimension(measuredWidth, fixedHeight); } private int getMeasurement(Child c, boolean horizontal) { return horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight(); } final int getMeasurementIncludingMargin(Child c, boolean horizontal) { return getMeasurement(c, horizontal) + getTotalMargin(c, horizontal); } @Override public void requestLayout() { super.requestLayout(); invalidateValues(); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { consistencyCheck(); } final class Axis { private static final int NEW = 0; private static final int PENDING = 1; private static final int COMPLETE = 2; public final boolean horizontal; public int definedCount = UNDEFINED; private int maxIndex = UNDEFINED; PackedMap groupBounds; public boolean groupBoundsValid = false; PackedMap forwardLinks; public boolean forwardLinksValid = false; PackedMap backwardLinks; public boolean backwardLinksValid = false; public int[] leadingMargins; public boolean leadingMarginsValid = false; public int[] trailingMargins; public boolean trailingMarginsValid = false; public Arc[] arcs; public boolean arcsValid = false; public int[] locations; public boolean locationsValid = false; public boolean hasWeights; public boolean hasWeightsValid = false; public int[] deltas; boolean orderPreserved = DEFAULT_ORDER_PRESERVED; private MutableInt parentMin = new MutableInt(0); private MutableInt parentMax = new MutableInt(-MAX_SIZE); private Axis(boolean horizontal) { this.horizontal = horizontal; } private int calculateMaxIndex() { int result = -1; for (int i = 0, N = getChildCount(); i < N; i++) { Child c = getChildAt(i); LayoutParams params = c.getLayoutParams(); Spec spec = horizontal ? params.columnSpec : params.rowSpec; Interval span = spec.span; result = max(result, span.min); result = max(result, span.max); result = max(result, span.size()); } return result == -1 ? UNDEFINED : result; } private int getMaxIndex() { if (maxIndex == UNDEFINED) { maxIndex = max(0, calculateMaxIndex()); } return maxIndex; } public int getCount() { return max(definedCount, getMaxIndex()); } public void setCount(int count) { if (count != UNDEFINED && count < getMaxIndex()) { handleInvalidParams((horizontal ? "column" : "row") + "Count must be greater than or equal to the maximum of all grid indices (and spans) defined in the LayoutParams of each child"); } this.definedCount = count; } public boolean isOrderPreserved() { return orderPreserved; } public void setOrderPreserved(boolean orderPreserved) { this.orderPreserved = orderPreserved; invalidateStructure(); } private PackedMap createGroupBounds() { Assoc assoc = Assoc.of(Spec.class, Bounds.class); for (int i = 0, N = getChildCount(); i < N; i++) { Child c = getChildAt(i); LayoutParams lp = c.getLayoutParams(); Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; Bounds bounds = spec.getAbsoluteAlignment(horizontal).getBounds(); assoc.put(spec, bounds); } return assoc.pack(); } private void computeGroupBounds() { Bounds[] values = groupBounds.values; for (int i = 0; i < values.length; i++) { values[i].reset(); } for (int i = 0, N = getChildCount(); i < N; i++) { Child c = getChildAt(i); LayoutParams lp = c.getLayoutParams(); Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; int size = getMeasurementIncludingMargin(c, horizontal) + (spec.weight == 0 ? 0 : deltas[i]); groupBounds.getValue(i).include(TableLayout.this, c, spec, this, size); } } public PackedMap getGroupBounds() { if (groupBounds == null) { groupBounds = createGroupBounds(); } if (!groupBoundsValid) { computeGroupBounds(); groupBoundsValid = true; } return groupBounds; } private PackedMap createLinks(boolean min) { Assoc result = Assoc.of(Interval.class, MutableInt.class); Spec[] keys = getGroupBounds().keys; for (int i = 0, N = keys.length; i < N; i++) { Interval span = min ? keys[i].span : keys[i].span.inverse(); result.put(span, new MutableInt()); } return result.pack(); } private void computeLinks(PackedMap links, boolean min) { MutableInt[] spans = links.values; for (int i = 0; i < spans.length; i++) { spans[i].reset(); } Bounds[] bounds = getGroupBounds().values; for (int i = 0; i < bounds.length; i++) { int size = bounds[i].size(min); MutableInt valueHolder = links.getValue(i); valueHolder.value = max(valueHolder.value, min ? size : -size); } } private PackedMap getForwardLinks() { if (forwardLinks == null) { forwardLinks = createLinks(true); } if (!forwardLinksValid) { computeLinks(forwardLinks, true); forwardLinksValid = true; } return forwardLinks; } private PackedMap getBackwardLinks() { if (backwardLinks == null) { backwardLinks = createLinks(false); } if (!backwardLinksValid) { computeLinks(backwardLinks, false); backwardLinksValid = true; } return backwardLinks; } private void include(List arcs, Interval key, MutableInt size, boolean ignoreIfAlreadyPresent) { if (key.size() == 0) { return; } if (ignoreIfAlreadyPresent) { for (Arc arc : arcs) { Interval span = arc.span; if (span.equals(key)) { return; } } } arcs.add(new Arc(key, size)); } private void include(List arcs, Interval key, MutableInt size) { include(arcs, key, size, true); } Arc[][] groupArcsByFirstVertex(Arc[] arcs) { int N = getCount() + 1; Arc[][] result = new Arc[N][]; int[] sizes = new int[N]; for (Arc arc : arcs) { sizes[arc.span.min]++; } for (int i = 0; i < sizes.length; i++) { result[i] = new Arc[sizes[i]]; } Arrays.fill(sizes, 0); for (Arc arc : arcs) { int i = arc.span.min; result[i][sizes[i]++] = arc; } return result; } private Arc[] topologicalSort(final Arc[] arcs) { return new Object() { Arc[] result = new Arc[arcs.length]; int cursor = result.length - 1; Arc[][] arcsByVertex = groupArcsByFirstVertex(arcs); int[] visited = new int[getCount() + 1]; void walk(int loc) { switch (visited[loc]) { case NEW: { visited[loc] = PENDING; for (Arc arc : arcsByVertex[loc]) { walk(arc.span.max); result[cursor--] = arc; } visited[loc] = COMPLETE; break; } case PENDING: { break; } case COMPLETE: { break; } } } Arc[] sort() { for (int loc = 0, N = arcsByVertex.length; loc < N; loc++) { walk(loc); } return result; } }.sort(); } private Arc[] topologicalSort(List arcs) { return topologicalSort(arcs.toArray(new Arc[0])); } private void addComponentSizes(List result, PackedMap links) { for (int i = 0; i < links.keys.length; i++) { Interval key = links.keys[i]; include(result, key, links.values[i], false); } } private Arc[] createArcs() { List mins = new ArrayList<>(); List maxs = new ArrayList<>(); addComponentSizes(mins, getForwardLinks()); addComponentSizes(maxs, getBackwardLinks()); if (orderPreserved) { for (int i = 0; i < getCount(); i++) { include(mins, new Interval(i, i + 1), new MutableInt(0)); } } int N = getCount(); include(mins, new Interval(0, N), parentMin, false); include(maxs, new Interval(N, 0), parentMax, false); Arc[] sMins = topologicalSort(mins); Arc[] sMaxs = topologicalSort(maxs); return append(sMins, sMaxs); } private void computeArcs() { getForwardLinks(); getBackwardLinks(); } public Arc[] getArcs() { if (arcs == null) { arcs = createArcs(); } if (!arcsValid) { computeArcs(); arcsValid = true; } return arcs; } private boolean relax(int[] locations, Arc entry) { if (!entry.valid) { return false; } Interval span = entry.span; int u = span.min; int v = span.max; int value = entry.value.value; int candidate = locations[u] + value; if (candidate > locations[v]) { locations[v] = candidate; return true; } return false; } private void init(int[] locations) { Arrays.fill(locations, 0); } private boolean solve(Arc[] arcs, int[] locations) { return solve(arcs, locations, true); } private boolean solve(Arc[] arcs, int[] locations, boolean modifyOnError) { int N = getCount() + 1; for (int p = 0; p < arcs.length; p++) { init(locations); for (int i = 0; i < N; i++) { boolean changed = false; for (int j = 0, length = arcs.length; j < length; j++) { changed |= relax(locations, arcs[j]); } if (!changed) { return true; } } if (!modifyOnError) { return false; } boolean[] culprits = new boolean[arcs.length]; for (int i = 0; i < N; i++) { for (int j = 0, length = arcs.length; j < length; j++) { culprits[j] |= relax(locations, arcs[j]); } } for (int i = 0; i < arcs.length; i++) { if (culprits[i]) { Arc arc = arcs[i]; if (arc.span.min < arc.span.max) { continue; } arc.valid = false; break; } } } return true; } private void computeMargins(boolean leading) { int[] margins = leading ? leadingMargins : trailingMargins; for (int i = 0, N = getChildCount(); i < N; i++) { Child c = getChildAt(i); LayoutParams lp = c.getLayoutParams(); Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; Interval span = spec.span; int index = leading ? span.min : span.max; margins[index] = max(margins[index], getMargin1(c, horizontal, leading)); } } public int[] getLeadingMargins() { if (leadingMargins == null) { leadingMargins = new int[getCount() + 1]; } if (!leadingMarginsValid) { computeMargins(true); leadingMarginsValid = true; } return leadingMargins; } public int[] getTrailingMargins() { if (trailingMargins == null) { trailingMargins = new int[getCount() + 1]; } if (!trailingMarginsValid) { computeMargins(false); trailingMarginsValid = true; } return trailingMargins; } private boolean solve(int[] a) { return solve(getArcs(), a); } private boolean computeHasWeights() { for (int i = 0, N = getChildCount(); i < N; i++) { final Child child = getChildAt(i); LayoutParams lp = child.getLayoutParams(); Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; if (spec.weight != 0) { return true; } } return false; } private boolean hasWeights() { if (!hasWeightsValid) { hasWeights = computeHasWeights(); hasWeightsValid = true; } return hasWeights; } public int[] getDeltas() { if (deltas == null) { deltas = new int[getChildCount()]; } return deltas; } private void shareOutDelta(int totalDelta, float totalWeight) { Arrays.fill(deltas, 0); for (int i = 0, N = getChildCount(); i < N; i++) { final Child c = getChildAt(i); LayoutParams lp = c.getLayoutParams(); Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; float weight = spec.weight; if (weight != 0) { int delta = Math.round((weight * totalDelta / totalWeight)); deltas[i] = delta; totalDelta -= delta; totalWeight -= weight; } } } private void solveAndDistributeSpace(int[] a) { Arrays.fill(getDeltas(), 0); solve(a); int deltaMax = parentMin.value * getChildCount() + 1; if (deltaMax < 2) { return; } int deltaMin = 0; float totalWeight = calculateTotalWeight(); int validDelta = -1; boolean validSolution = true; while (deltaMin < deltaMax) { final int delta = (int) (((long) deltaMin + deltaMax) / 2); invalidateValues(); shareOutDelta(delta, totalWeight); validSolution = solve(getArcs(), a, false); if (validSolution) { validDelta = delta; deltaMin = delta + 1; } else { deltaMax = delta; } } if (validDelta > 0 && !validSolution) { invalidateValues(); shareOutDelta(validDelta, totalWeight); solve(a); } } private float calculateTotalWeight() { float totalWeight = 0f; for (int i = 0, N = getChildCount(); i < N; i++) { Child c = getChildAt(i); LayoutParams lp = c.getLayoutParams(); Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; totalWeight += spec.weight; } return totalWeight; } private void computeLocations(int[] a) { if (!hasWeights()) { solve(a); } else { solveAndDistributeSpace(a); } if (!orderPreserved) { int a0 = a[0]; for (int i = 0, N = a.length; i < N; i++) { a[i] = a[i] - a0; } } } public int[] getLocations() { if (locations == null) { int N = getCount() + 1; locations = new int[N]; } if (!locationsValid) { computeLocations(locations); locationsValid = true; } return locations; } private int size(int[] locations) { return locations[getCount()]; } private void setParentConstraints(int min, int max) { parentMin.value = min; parentMax.value = -max; locationsValid = false; } private int getMeasure(int min, int max) { setParentConstraints(min, max); return size(getLocations()); } public int getMeasure(int measureSpec) { int mode = MeasureSpec.getMode(measureSpec); int size = MeasureSpec.getSize(measureSpec); switch (mode) { case MeasureSpec.UNSPECIFIED: { return getMeasure(0, MAX_SIZE); } case EXACTLY: { return getMeasure(size, size); } case MeasureSpec.AT_MOST: { return getMeasure(0, size); } default: { return 0; } } } public void layout(int size) { setParentConstraints(size, size); getLocations(); } public void invalidateStructure() { maxIndex = UNDEFINED; groupBounds = null; forwardLinks = null; backwardLinks = null; leadingMargins = null; trailingMargins = null; arcs = null; locations = null; deltas = null; hasWeightsValid = false; invalidateValues(); } public void invalidateValues() { groupBoundsValid = false; forwardLinksValid = false; backwardLinksValid = false; leadingMarginsValid = false; trailingMarginsValid = false; arcsValid = false; locationsValid = false; } } public static class LayoutParams extends ViewGroup.MarginLayoutParams { private static final int DEFAULT_WIDTH = WRAP_CONTENT; private static final int DEFAULT_HEIGHT = WRAP_CONTENT; private static final int DEFAULT_MARGIN = UNDEFINED; private static final Interval DEFAULT_SPAN = new Interval(UNDEFINED, UNDEFINED + 1); private static final int DEFAULT_SPAN_SIZE = DEFAULT_SPAN.size(); public Spec rowSpec = Spec.UNDEFINED; public Spec columnSpec = Spec.UNDEFINED; private LayoutParams(int width, int height, int left, int top, int right, int bottom, Spec rowSpec, Spec columnSpec) { super(width, height); setMargins(left, top, right, bottom); this.rowSpec = rowSpec; this.columnSpec = columnSpec; } public LayoutParams(Spec rowSpec, Spec columnSpec) { this(DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, rowSpec, columnSpec); } public LayoutParams() { this(Spec.UNDEFINED, Spec.UNDEFINED); } public LayoutParams(ViewGroup.LayoutParams params) { super(params); } public LayoutParams(ViewGroup.MarginLayoutParams params) { super(params); } public LayoutParams(LayoutParams source) { super(source); this.rowSpec = source.rowSpec; this.columnSpec = source.columnSpec; } public void setGravity(int gravity) { rowSpec = rowSpec.copyWriteAlignment(getAlignment(gravity, false)); columnSpec = columnSpec.copyWriteAlignment(getAlignment(gravity, true)); } final void setRowSpecSpan(Interval span) { rowSpec = rowSpec.copyWriteSpan(span); } final void setColumnSpecSpan(Interval span) { columnSpec = columnSpec.copyWriteSpan(span); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; LayoutParams that = (LayoutParams) o; if (!columnSpec.equals(that.columnSpec)) return false; if (!rowSpec.equals(that.rowSpec)) return false; return true; } @Override public int hashCode() { int result = rowSpec.hashCode(); result = 31 * result + columnSpec.hashCode(); return result; } } final static class Arc { public final Interval span; public final MutableInt value; public boolean valid = true; public Arc(Interval span, MutableInt value) { this.span = span; this.value = value; } } final static class MutableInt { public int value; public MutableInt() { reset(); } public MutableInt(int value) { this.value = value; } public void reset() { value = Integer.MIN_VALUE; } } final static class Assoc extends ArrayList> { private final Class keyType; private final Class valueType; private Assoc(Class keyType, Class valueType) { this.keyType = keyType; this.valueType = valueType; } public static Assoc of(Class keyType, Class valueType) { return new Assoc<>(keyType, valueType); } public void put(K key, V value) { add(Pair.create(key, value)); } @SuppressWarnings(value = "unchecked") public PackedMap pack() { int N = size(); K[] keys = (K[]) Array.newInstance(keyType, N); V[] values = (V[]) Array.newInstance(valueType, N); for (int i = 0; i < N; i++) { keys[i] = get(i).first; values[i] = get(i).second; } return new PackedMap<>(keys, values); } } @SuppressWarnings(value = "unchecked") final static class PackedMap { public final int[] index; public final K[] keys; public final V[] values; private PackedMap(K[] keys, V[] values) { this.index = createIndex(keys); this.keys = compact(keys, index); this.values = compact(values, index); } public V getValue(int i) { return values[index[i]]; } private static int[] createIndex(K[] keys) { int size = keys.length; int[] result = new int[size]; Map keyToIndex = new HashMap<>(); for (int i = 0; i < size; i++) { K key = keys[i]; Integer index = keyToIndex.get(key); if (index == null) { index = keyToIndex.size(); keyToIndex.put(key, index); } result[i] = index; } return result; } private static K[] compact(K[] a, int[] index) { int size = a.length; Class componentType = a.getClass().getComponentType(); K[] result = (K[]) Array.newInstance(componentType, max2(index, -1) + 1); for (int i = 0; i < size; i++) { result[index[i]] = a[i]; } return result; } } static class Bounds { public int before; public int after; public int flexibility; private Bounds() { reset(); } protected void reset() { before = Integer.MIN_VALUE; after = Integer.MIN_VALUE; flexibility = CAN_STRETCH; } protected void include(int before, int after) { this.before = max(this.before, before); this.after = max(this.after, after); } protected int size(boolean min) { if (!min) { if (canStretch(flexibility)) { return MAX_SIZE; } } return before + after; } protected int getOffset(TableLayout gl, Child c, Alignment a, int size, boolean horizontal) { return before - a.getAlignmentValue(c, size); } protected final void include(TableLayout gl, Child c, Spec spec, Axis axis, int size) { this.flexibility &= spec.getFlexibility(); boolean horizontal = axis.horizontal; Alignment alignment = spec.getAbsoluteAlignment(axis.horizontal); int before = alignment.getAlignmentValue(c, size); include(before, size - before); } } final static class Interval { public final int min; public final int max; public Interval(int min, int max) { this.min = min; this.max = max; } int size() { return max - min; } Interval inverse() { return new Interval(max, min); } @Override public boolean equals(Object that) { if (this == that) { return true; } if (that == null || getClass() != that.getClass()) { return false; } Interval interval = (Interval) that; if (max != interval.max) { return false; } if (min != interval.min) { return false; } return true; } @Override public int hashCode() { int result = min; result = 31 * result + max; return result; } } public static class Spec { static final Spec UNDEFINED = spec(TableLayout.UNDEFINED); static final float DEFAULT_WEIGHT = 0; final boolean startDefined; final Interval span; final Alignment alignment; float weight; private Spec(boolean startDefined, Interval span, Alignment alignment, float weight) { this.startDefined = startDefined; this.span = span; this.alignment = alignment; this.weight = weight; } private Spec(boolean startDefined, int start, int size, Alignment alignment, float weight) { this(startDefined, new Interval(start, start + size), alignment, weight); } private Alignment getAbsoluteAlignment(boolean horizontal) { if (alignment != UNDEFINED_ALIGNMENT) { return alignment; } if (weight == 0f) { return horizontal ? START : BASELINE; } return FILL; } final Spec copyWriteSpan(Interval span) { return new Spec(startDefined, span, alignment, weight); } final Spec copyWriteAlignment(Alignment alignment) { return new Spec(startDefined, span, alignment, weight); } final int getFlexibility() { return (alignment == UNDEFINED_ALIGNMENT && weight == 0) ? INFLEXIBLE : CAN_STRETCH; } @Override public boolean equals(Object that) { if (this == that) { return true; } if (that == null || getClass() != that.getClass()) { return false; } Spec spec = (Spec) that; if (!alignment.equals(spec.alignment)) { return false; } if (!span.equals(spec.span)) { return false; } return true; } @Override public int hashCode() { int result = span.hashCode(); result = 31 * result + alignment.hashCode(); return result; } } public static Spec spec(int start, int size, Alignment alignment, float weight) { return new Spec(start != UNDEFINED, start, size, alignment, weight); } public static Spec spec(int start, Alignment alignment, float weight) { return spec(start, 1, alignment, weight); } public static Spec spec(int start, int size, float weight) { return spec(start, size, UNDEFINED_ALIGNMENT, weight); } public static Spec spec(int start, float weight) { return spec(start, 1, weight); } public static Spec spec(int start, int size, Alignment alignment) { return spec(start, size, alignment, Spec.DEFAULT_WEIGHT); } public static Spec spec(int start, Alignment alignment) { return spec(start, 1, alignment); } public static Spec spec(int start, int size) { return spec(start, size, UNDEFINED_ALIGNMENT); } public static Spec spec(int start) { return spec(start, 1); } public static abstract class Alignment { Alignment() { } abstract int getGravityOffset(Child view, int cellDelta); abstract int getAlignmentValue(Child view, int viewSize); int getSizeInCell(Child view, int viewSize, int cellSize) { return viewSize; } Bounds getBounds() { return new Bounds(); } } static final Alignment UNDEFINED_ALIGNMENT = new Alignment() { @Override int getGravityOffset(Child view, int cellDelta) { return UNDEFINED; } @Override public int getAlignmentValue(Child view, int viewSize) { return UNDEFINED; } }; private static final Alignment LEADING = new Alignment() { @Override int getGravityOffset(Child view, int cellDelta) { return 0; } @Override public int getAlignmentValue(Child view, int viewSize) { return 0; } }; private static final Alignment TRAILING = new Alignment() { @Override int getGravityOffset(Child view, int cellDelta) { return cellDelta; } @Override public int getAlignmentValue(Child view, int viewSize) { return viewSize; } }; public static final Alignment TOP = LEADING; public static final Alignment BOTTOM = TRAILING; public static final Alignment START = LEADING; public static final Alignment END = TRAILING; private static Alignment createSwitchingAlignment(final Alignment ltr) { return new Alignment() { @Override int getGravityOffset(Child view, int cellDelta) { return ltr.getGravityOffset(view, cellDelta); } @Override public int getAlignmentValue(Child view, int viewSize) { return ltr.getAlignmentValue(view, viewSize); } }; } public static final Alignment LEFT = createSwitchingAlignment(START); public static final Alignment RIGHT = createSwitchingAlignment(END); public static final Alignment CENTER = new Alignment() { @Override int getGravityOffset(Child view, int cellDelta) { return cellDelta >> 1; } @Override public int getAlignmentValue(Child view, int viewSize) { return viewSize >> 1; } }; public static final Alignment BASELINE = new Alignment() { @Override int getGravityOffset(Child view, int cellDelta) { return 0; } @Override public int getAlignmentValue(Child view, int viewSize) { return UNDEFINED; } @Override public Bounds getBounds() { return new Bounds() { private int size; @Override protected void reset() { super.reset(); size = Integer.MIN_VALUE; } @Override protected void include(int before, int after) { super.include(before, after); size = max(size, before + after); } @Override protected int size(boolean min) { return max(super.size(min), size); } @Override protected int getOffset(TableLayout gl, Child c, Alignment a, int size, boolean hrz) { return max(0, super.getOffset(gl, c, a, size, hrz)); } }; } }; public static final Alignment FILL = new Alignment() { @Override int getGravityOffset(Child view, int cellDelta) { return 0; } @Override public int getAlignmentValue(Child view, int viewSize) { return UNDEFINED; } @Override public int getSizeInCell(Child view, int viewSize, int cellSize) { return cellSize; } }; static boolean canStretch(int flexibility) { return (flexibility & CAN_STRETCH) != 0; } private static final int INFLEXIBLE = 0; private static final int CAN_STRETCH = 2; }