mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
Add alignSelf nad flexWrapBefore in the flexbox examples Some test fail with quite close calculations. Use eps. Fix flex grow making last items with flexGrow 0 to shrink due to rounding, happy tslint
1417 lines
63 KiB
TypeScript
1417 lines
63 KiB
TypeScript
import {
|
|
FlexDirection,
|
|
FlexWrap,
|
|
JustifyContent,
|
|
AlignItems,
|
|
AlignContent,
|
|
AlignSelf,
|
|
FlexboxLayoutBase,
|
|
FlexBasisPercent
|
|
} from "./flexbox-layout-common";
|
|
|
|
export * from "./flexbox-layout-common";
|
|
|
|
import {CommonLayoutParams, nativeLayoutParamsProperty} from "ui/styling/style";
|
|
import {LayoutBase} from "ui/layouts/layout-base";
|
|
import {View} from "ui/core/view";
|
|
import * as utils from "utils/utils";
|
|
import * as enums from "ui/enums";
|
|
|
|
import EXACTLY = utils.layout.EXACTLY;
|
|
import AT_MOST = utils.layout.AT_MOST;
|
|
import UNSPECIFIED = utils.layout.UNSPECIFIED;
|
|
|
|
import MEASURED_SIZE_MASK = utils.layout.MEASURED_SIZE_MASK;
|
|
import MEASURED_STATE_TOO_SMALL = utils.layout.MEASURED_STATE_TOO_SMALL;
|
|
|
|
const MATCH_PARENT = -1;
|
|
const WRAP_CONTENT = -2;
|
|
|
|
const View_sUseZeroUnspecifiedMeasureSpec = true; // NOTE: android version < M
|
|
|
|
// Long ints may not be safe in JavaScript
|
|
const MAX_SIZE = 0x00FFFFFF & MEASURED_SIZE_MASK;
|
|
|
|
import makeMeasureSpec = utils.layout.makeMeasureSpec;
|
|
import getMeasureSpecMode = utils.layout.getMeasureSpecMode;
|
|
import getMeasureSpecSize = utils.layout.getMeasureSpecSize;
|
|
|
|
class FlexLine {
|
|
|
|
_left: number = Number.MAX_VALUE;
|
|
_top: number = Number.MAX_VALUE;
|
|
_right: number = Number.MAX_VALUE;
|
|
_bottom: number = Number.MAX_VALUE;
|
|
|
|
_mainSize: number = 0;
|
|
_dividerLengthInMainSize = 0;
|
|
_crossSize: number = 0;
|
|
_itemCount: number = 0;
|
|
_totalFlexGrow: number = 0;
|
|
_totalFlexShrink: number = 0;
|
|
_maxBaseline: number = 0;
|
|
|
|
_indicesAlignSelfStretch: number[] = [];
|
|
|
|
get left(): number { return this._left; }
|
|
get top(): number { return this._top; }
|
|
get right(): number { return this._right; }
|
|
get bottom(): number { return this._bottom; }
|
|
|
|
get mainSize(): number { return this._mainSize; }
|
|
get crossSize(): number { return this._crossSize; }
|
|
get itemCount(): number { return this._itemCount; }
|
|
get totalFlexGrow(): number { return this._totalFlexGrow; }
|
|
get totalFlexShrink(): number { return this._totalFlexShrink; }
|
|
}
|
|
|
|
class Order {
|
|
index: number;
|
|
order: number;
|
|
|
|
public compareTo(another: Order): number {
|
|
if (this.order !== another.order) {
|
|
return this.order - another.order;
|
|
}
|
|
return this.index - another.index;
|
|
}
|
|
}
|
|
|
|
export class FlexboxLayout extends FlexboxLayoutBase {
|
|
|
|
// Omit divider
|
|
|
|
private _reorderedIndices: number[];
|
|
|
|
private _orderCache: number[];
|
|
private _flexLines: FlexLine[] = [];
|
|
private _childrenFrozen: boolean[];
|
|
|
|
protected invalidate() {
|
|
this.requestLayout();
|
|
}
|
|
|
|
protected setNativeFlexDirection(flexDirection: FlexDirection) {
|
|
// lint happy no-op
|
|
}
|
|
protected setNativeFlexWrap(flexWrap: FlexWrap) {
|
|
// lint happy no-op
|
|
}
|
|
protected setNativeJustifyContent(justifyContent: JustifyContent) {
|
|
// lint happy no-op
|
|
}
|
|
protected setNativeAlignItems(alignItems: AlignItems) {
|
|
// lint happy no-op
|
|
}
|
|
protected setNativeAlignContent(alignContent: AlignContent) {
|
|
// lint happy no-op
|
|
}
|
|
|
|
public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void {
|
|
LayoutBase.adjustChildrenLayoutParams(this, widthMeasureSpec, heightMeasureSpec);
|
|
|
|
// Omit: super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
|
|
if (this._isOrderChangedFromLastMeasurement) {
|
|
this._reorderedIndices = this._createReorderedIndices();
|
|
}
|
|
if (!this._childrenFrozen || this._childrenFrozen.length < this.getChildrenCount()) {
|
|
this._childrenFrozen = new Array(this.getChildrenCount());
|
|
}
|
|
|
|
switch(this.flexDirection) {
|
|
case FlexDirection.ROW:
|
|
case FlexDirection.ROW_REVERSE:
|
|
this._measureHorizontal(widthMeasureSpec, heightMeasureSpec);
|
|
break;
|
|
case FlexDirection.COLUMN:
|
|
case FlexDirection.COLUMN_REVERSE:
|
|
this._measureVertical(widthMeasureSpec, heightMeasureSpec);
|
|
break;
|
|
default:
|
|
throw new Error("Invalid value for the flex direction is set: " + this.flexDirection);
|
|
}
|
|
|
|
this._childrenFrozen.length = 0;
|
|
}
|
|
|
|
private _getReorderedChildAt(index: number): View {
|
|
let child: View;
|
|
if (index < 0 || index >= this._reorderedIndices.length) {
|
|
child = null;
|
|
} else {
|
|
let reorderedIndex = this._reorderedIndices[index];
|
|
child = this.getChildAt(reorderedIndex);
|
|
}
|
|
return child;
|
|
}
|
|
|
|
public addChild(child: View) {
|
|
let index = this.getChildrenCount(); // Goes last
|
|
this._reorderedIndices = this._createReorderedIndices(child, index, FlexboxLayout.getLayoutParams(child));
|
|
super.addChild(child);
|
|
}
|
|
|
|
public insertChild(child: View, index: number): void {
|
|
this._reorderedIndices = this._createReorderedIndices(child, index, FlexboxLayout.getLayoutParams(child));
|
|
super.addChild(child);
|
|
}
|
|
|
|
private _createReorderedIndices(viewBeforeAdded: View, indexForViewBeforeAdded: number, paramsForViewBeforeAdded: FlexboxLayout.LayoutParams): number[];
|
|
private _createReorderedIndices(): number[];
|
|
private _createReorderedIndices(viewBeforeAdded?: View, indexForViewBeforeAdded?: number, paramsForViewBeforeAdded?: FlexboxLayout.LayoutParams) {
|
|
if (arguments.length === 0) {
|
|
let childCount = this.getChildrenCount();
|
|
let orders = this._createOrders(childCount);
|
|
return this._sortOrdersIntoReorderedIndices(childCount, orders);
|
|
} else {
|
|
let childCount: number = this.getChildrenCount();
|
|
let orders = this._createOrders(childCount);
|
|
let orderForViewToBeAdded = new Order();
|
|
if (viewBeforeAdded !== null) {
|
|
orderForViewToBeAdded.order = paramsForViewBeforeAdded.order;
|
|
} else {
|
|
orderForViewToBeAdded.order = 1 /* Default order */;
|
|
}
|
|
|
|
if (indexForViewBeforeAdded === -1 || indexForViewBeforeAdded === childCount) {
|
|
orderForViewToBeAdded.index = childCount;
|
|
} else if (indexForViewBeforeAdded) {
|
|
orderForViewToBeAdded.index = indexForViewBeforeAdded;
|
|
for (let i = indexForViewBeforeAdded; i < childCount; i++) {
|
|
orders[i].index++;
|
|
}
|
|
} else {
|
|
orderForViewToBeAdded.index = childCount;
|
|
}
|
|
orders.push(orderForViewToBeAdded);
|
|
|
|
return this._sortOrdersIntoReorderedIndices(childCount + 1, orders);
|
|
}
|
|
}
|
|
|
|
private _sortOrdersIntoReorderedIndices(childCount: number, orders: Order[]): number[] {
|
|
orders.sort((a, b) => a.compareTo(b));
|
|
if (!this._orderCache) {
|
|
this._orderCache = [];
|
|
}
|
|
|
|
this._orderCache.length = 0;
|
|
let reorderedIndices: number[] = [];
|
|
orders.forEach((order, i) => {
|
|
reorderedIndices[i] = order.index;
|
|
this._orderCache[i] = order.order;
|
|
});
|
|
return reorderedIndices;
|
|
}
|
|
|
|
private _createOrders(childCount: number): Order[] {
|
|
let orders: Order[] = [];
|
|
for (let i = 0; i < childCount; i++) {
|
|
let child = this.getChildAt(i);
|
|
let params = FlexboxLayout.getLayoutParams(child);
|
|
let order = new Order();
|
|
order.order = params.order;
|
|
order.index = i;
|
|
orders.push(order);
|
|
}
|
|
return orders;
|
|
}
|
|
|
|
private get _isOrderChangedFromLastMeasurement(): boolean {
|
|
let childCount = this.getChildrenCount();
|
|
if (this._orderCache === null) {
|
|
this._orderCache = [];
|
|
}
|
|
if (this._orderCache.length !== childCount) {
|
|
return true;
|
|
}
|
|
for (let i = 0; i < childCount; i++) {
|
|
let view = this.getChildAt(i);
|
|
if (view === null) {
|
|
continue;
|
|
}
|
|
let lp = FlexboxLayout.getLayoutParams(view);
|
|
if (lp.order !== this._orderCache[i]) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private _measureHorizontal(widthMeasureSpec: number, heightMeasureSpec: number): void {
|
|
let widthMode = getMeasureSpecMode(widthMeasureSpec);
|
|
let widthSize = getMeasureSpecSize(widthMeasureSpec);
|
|
let childState = 0;
|
|
|
|
this._flexLines.length = 0;
|
|
|
|
(() => {
|
|
let childCount = this.getChildrenCount();
|
|
let paddingStart = FlexboxLayout.getPaddingStart(this);
|
|
let paddingEnd = FlexboxLayout.getPaddingEnd(this);
|
|
let largestHeightInRow = Number.MIN_VALUE;
|
|
let flexLine = new FlexLine();
|
|
|
|
let indexInFlexLine = 0;
|
|
flexLine._mainSize = paddingStart + paddingEnd;
|
|
for (let i = 0; i < childCount; i++) {
|
|
let child = this._getReorderedChildAt(i);
|
|
if (child === null) {
|
|
this._addFlexLineIfLastFlexItem(i, childCount, flexLine);
|
|
continue;
|
|
} else if (FlexboxLayout.isGone(child)) {
|
|
flexLine._itemCount++;
|
|
this._addFlexLineIfLastFlexItem(i, childCount, flexLine);
|
|
continue;
|
|
}
|
|
|
|
let lp: FlexboxLayout.LayoutParams = FlexboxLayout.getLayoutParams(child);
|
|
if (lp.alignSelf === AlignSelf.STRETCH) {
|
|
flexLine._indicesAlignSelfStretch.push(i);
|
|
}
|
|
|
|
let childWidth = lp.width;
|
|
if (lp.flexBasisPercent !== FlexBasisPercent.DEFAULT && widthMode === EXACTLY) {
|
|
childWidth = Math.round(widthSize * lp.flexBasisPercent);
|
|
}
|
|
|
|
let childWidthMeasureSpec = FlexboxLayout.getChildMeasureSpec(widthMeasureSpec,
|
|
this.paddingLeft + this.paddingRight + lp.leftMargin
|
|
+ lp.rightMargin, childWidth < 0 ? WRAP_CONTENT : childWidth);
|
|
|
|
let childHeightMeasureSpec = FlexboxLayout.getChildMeasureSpec(heightMeasureSpec,
|
|
this.paddingTop + this.paddingBottom + lp.topMargin
|
|
+ lp.bottomMargin, lp.height < 0 ? WRAP_CONTENT : lp.height);
|
|
|
|
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
|
|
|
|
this._checkSizeConstraints(child);
|
|
|
|
childState = View.combineMeasuredStates(childState, child.getMeasuredState());
|
|
largestHeightInRow = Math.max(largestHeightInRow,
|
|
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
|
|
|
|
if (this._isWrapRequired(widthMode, widthSize, flexLine._mainSize,
|
|
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin, lp,
|
|
i, indexInFlexLine)) {
|
|
if (flexLine.itemCount > 0) {
|
|
this._addFlexLine(flexLine);
|
|
}
|
|
|
|
flexLine = new FlexLine();
|
|
flexLine._itemCount = 1;
|
|
flexLine._mainSize = paddingStart + paddingEnd;
|
|
largestHeightInRow = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
|
|
indexInFlexLine = 0;
|
|
} else {
|
|
flexLine._itemCount++;
|
|
indexInFlexLine++;
|
|
}
|
|
flexLine._mainSize += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
|
|
flexLine._totalFlexGrow += lp.flexGrow;
|
|
flexLine._totalFlexShrink += lp.flexShrink;
|
|
|
|
flexLine._crossSize = Math.max(flexLine._crossSize, largestHeightInRow);
|
|
|
|
// Omit divider
|
|
|
|
if (this.flexWrap !== FlexWrap.WRAP_REVERSE) {
|
|
flexLine._maxBaseline = Math.max(flexLine._maxBaseline, FlexboxLayout.getBaseline(child) + lp.topMargin)
|
|
} else {
|
|
flexLine._maxBaseline = Math.max(flexLine._maxBaseline, child.getMeasuredHeight() - FlexboxLayout.getBaseline(child) + lp.bottomMargin);
|
|
}
|
|
this._addFlexLineIfLastFlexItem(i, childCount, flexLine);
|
|
}
|
|
})();
|
|
|
|
this._determineMainSize(this.flexDirection, widthMeasureSpec, heightMeasureSpec);
|
|
|
|
if (this.alignItems === AlignItems.BASELINE) {
|
|
let viewIndex = 0;
|
|
this._flexLines.forEach(flexLine => {
|
|
let largestHeightInLine = Number.MIN_VALUE;
|
|
for (let i = viewIndex; i < viewIndex + flexLine._itemCount; i++) {
|
|
let child = this._getReorderedChildAt(i);
|
|
let lp = FlexboxLayout.getLayoutParams(child);
|
|
if (this.flexWrap !== FlexWrap.WRAP_REVERSE) {
|
|
let marginTop = flexLine._maxBaseline - FlexboxLayout.getBaseline(child);
|
|
marginTop = Math.max(marginTop, lp.topMargin);
|
|
largestHeightInLine = Math.max(largestHeightInLine, child.getActualSize().height + marginTop + lp.bottomMargin);
|
|
} else {
|
|
let marginBottom = flexLine._maxBaseline - child.getMeasuredHeight() + FlexboxLayout.getBaseline(child);
|
|
marginBottom = Math.max(marginBottom, lp.bottomMargin);
|
|
largestHeightInLine = Math.max(largestHeightInLine, child.getActualSize().height + lp.topMargin + marginBottom);
|
|
}
|
|
}
|
|
flexLine._crossSize = largestHeightInLine;
|
|
viewIndex += flexLine.itemCount;
|
|
});
|
|
}
|
|
|
|
this._determineCrossSize(this.flexDirection, widthMeasureSpec, heightMeasureSpec, this.paddingTop + this.paddingBottom);
|
|
this._stretchViews(this.flexDirection, this.alignItems);
|
|
this._setMeasuredDimensionForFlex(this.flexDirection, widthMeasureSpec, heightMeasureSpec, childState);
|
|
}
|
|
|
|
private _measureVertical(widthMeasureSpec, heightMeasureSpec): void {
|
|
let heightMode = getMeasureSpecMode(heightMeasureSpec);
|
|
let heightSize = getMeasureSpecSize(heightMeasureSpec);
|
|
let childState = 0;
|
|
|
|
this._flexLines.length = 0;
|
|
|
|
let childCount = this.getChildrenCount();
|
|
let paddingTop = this.paddingTop;
|
|
let paddingBottom = this.paddingBottom;
|
|
let largestWidthInColumn = Number.MIN_VALUE;
|
|
let flexLine = new FlexLine();
|
|
flexLine._mainSize = paddingTop + paddingBottom;
|
|
|
|
let indexInFlexLine = 0;
|
|
for(let i = 0; i < childCount; i++) {
|
|
let child = this._getReorderedChildAt(i);
|
|
if (child === null) {
|
|
this._addFlexLineIfLastFlexItem(i, childCount, flexLine);
|
|
continue;
|
|
} else if (FlexboxLayout.isGone(child)) {
|
|
flexLine._itemCount++;
|
|
this._addFlexLineIfLastFlexItem(i, childCount, flexLine);
|
|
continue;
|
|
}
|
|
|
|
let lp = FlexboxLayout.getLayoutParams(child);
|
|
if (lp.alignSelf === AlignSelf.STRETCH) {
|
|
flexLine._indicesAlignSelfStretch.push(i);
|
|
}
|
|
|
|
let childHeight = lp.height;
|
|
if (lp.flexBasisPercent !== FlexBasisPercent.DEFAULT && heightMode === EXACTLY) {
|
|
childHeight = Math.round(heightSize * lp.flexBasisPercent);
|
|
}
|
|
|
|
let childWidthMeasureSpec = FlexboxLayout.getChildMeasureSpec(widthMeasureSpec,
|
|
this.paddingLeft + this.paddingRight + lp.leftMargin
|
|
+ lp.rightMargin, lp.width < 0 ? WRAP_CONTENT : lp.width);
|
|
let childHeightMeasureSpec = FlexboxLayout.getChildMeasureSpec(heightMeasureSpec,
|
|
this.paddingTop + this.paddingBottom + lp.topMargin
|
|
+ lp.bottomMargin, childHeight < 0 ? WRAP_CONTENT : childHeight);
|
|
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
|
|
|
|
this._checkSizeConstraints(child);
|
|
|
|
childState = View.combineMeasuredStates(childState, child.getMeasuredState());
|
|
largestWidthInColumn = Math.max(largestWidthInColumn,
|
|
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
|
|
|
|
if (this._isWrapRequired(heightMode, heightSize, flexLine.mainSize,
|
|
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin, lp,
|
|
i, indexInFlexLine)) {
|
|
|
|
if (flexLine._itemCount > 0) {
|
|
this._addFlexLine(flexLine);
|
|
}
|
|
|
|
flexLine = new FlexLine();
|
|
flexLine._itemCount = 1;
|
|
flexLine._mainSize = paddingTop + paddingBottom;
|
|
largestWidthInColumn = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
|
|
indexInFlexLine = 0;
|
|
} else {
|
|
flexLine._itemCount++;
|
|
indexInFlexLine++;
|
|
}
|
|
|
|
flexLine._mainSize += child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
|
|
flexLine._totalFlexGrow += lp.flexGrow;
|
|
flexLine._totalFlexShrink += lp.flexShrink;
|
|
|
|
flexLine._crossSize = Math.max(flexLine._crossSize, largestWidthInColumn);
|
|
|
|
// Omit divider
|
|
this._addFlexLineIfLastFlexItem(i, childCount, flexLine);
|
|
}
|
|
|
|
this._determineMainSize(this.flexDirection, widthMeasureSpec, heightMeasureSpec);
|
|
this._determineCrossSize(this.flexDirection, widthMeasureSpec, heightMeasureSpec, this.paddingLeft + this.paddingRight);
|
|
this._stretchViews(this.flexDirection, this.alignItems);
|
|
this._setMeasuredDimensionForFlex(this.flexDirection, widthMeasureSpec, heightMeasureSpec, childState);
|
|
}
|
|
|
|
private _checkSizeConstraints(view: View) {
|
|
let needsMeasure = false;
|
|
let lp = FlexboxLayout.getLayoutParams(view);
|
|
let childWidth = view.getMeasuredWidth();
|
|
let 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(makeMeasureSpec(childWidth, EXACTLY), makeMeasureSpec(childHeight, EXACTLY));
|
|
}
|
|
}
|
|
|
|
private _addFlexLineIfLastFlexItem(childIndex: number, childCount: number, flexLine: FlexLine) {
|
|
if (childIndex === childCount - 1 && flexLine.itemCount !== 0) {
|
|
this._addFlexLine(flexLine);
|
|
}
|
|
}
|
|
|
|
private _addFlexLine(flexLine: FlexLine) {
|
|
// Omit divider
|
|
this._flexLines.push(flexLine);
|
|
}
|
|
|
|
private _determineMainSize(flexDirection: FlexDirection, widthMeasureSpec: number, heightMeasureSpec: number) {
|
|
let mainSize: number;
|
|
let paddingAlongMainAxis: number;
|
|
|
|
switch(flexDirection) {
|
|
case FlexDirection.ROW:
|
|
case FlexDirection.ROW_REVERSE:
|
|
let widthMode = getMeasureSpecMode(widthMeasureSpec);
|
|
let widthSize = getMeasureSpecSize(widthMeasureSpec);
|
|
if (widthMode === EXACTLY) {
|
|
mainSize = widthSize;
|
|
} else {
|
|
mainSize = this._getLargestMainSize();
|
|
}
|
|
paddingAlongMainAxis = this.paddingLeft + this.paddingRight;
|
|
break;
|
|
case FlexDirection.COLUMN:
|
|
case FlexDirection.COLUMN_REVERSE:
|
|
let heightMode = getMeasureSpecMode(heightMeasureSpec);
|
|
let heightSize = getMeasureSpecSize(heightMeasureSpec);
|
|
if (heightMode === EXACTLY) {
|
|
mainSize = heightSize;
|
|
} else {
|
|
mainSize = this._getLargestMainSize();
|
|
}
|
|
paddingAlongMainAxis = this.paddingTop + this.paddingBottom;
|
|
break;
|
|
default:
|
|
throw new Error("Invalid flex direction: " + flexDirection);
|
|
}
|
|
|
|
let childIndex = 0;
|
|
this._flexLines.forEach(flexLine => {
|
|
if (flexLine.mainSize < mainSize) {
|
|
childIndex = this._expandFlexItems(flexLine, flexDirection, mainSize, paddingAlongMainAxis, childIndex);
|
|
} else {
|
|
childIndex = this._shrinkFlexItems(flexLine, flexDirection, mainSize, paddingAlongMainAxis, childIndex);
|
|
}
|
|
});
|
|
}
|
|
|
|
private _expandFlexItems(flexLine: FlexLine, flexDirection: FlexDirection, maxMainSize: number, paddingAlongMainAxis: number, startIndex: number) {
|
|
let childIndex = startIndex;
|
|
if (flexLine._totalFlexGrow <= 0 || maxMainSize < flexLine._mainSize) {
|
|
childIndex += flexLine._itemCount;
|
|
return childIndex;
|
|
}
|
|
let sizeBeforeExpand = flexLine._mainSize;
|
|
let needsReexpand = false;
|
|
let pendingSpace = maxMainSize - flexLine._mainSize;
|
|
let unitSpace = pendingSpace / flexLine._totalFlexGrow;
|
|
flexLine._mainSize = paddingAlongMainAxis + flexLine._dividerLengthInMainSize;
|
|
let accumulatedRoundError = 0;
|
|
for (let i = 0; i < flexLine.itemCount; i++) {
|
|
let child = this._getReorderedChildAt(childIndex);
|
|
if (child === null) {
|
|
continue;
|
|
} else if (FlexboxLayout.isGone(child)) {
|
|
childIndex++;
|
|
continue;
|
|
}
|
|
let lp = FlexboxLayout.getLayoutParams(child);
|
|
if (this._isMainAxisDirectionHorizontal(flexDirection)) {
|
|
if (!this._childrenFrozen[childIndex]) {
|
|
let rawCalculatedWidth = child.getMeasuredWidth() + unitSpace * lp.flexGrow + accumulatedRoundError;
|
|
let roundedCalculatedWidth = Math.round(rawCalculatedWidth);
|
|
if (roundedCalculatedWidth > lp.maxWidth) {
|
|
needsReexpand = true;
|
|
roundedCalculatedWidth = lp.maxWidth;
|
|
this._childrenFrozen[childIndex] = true;
|
|
flexLine._totalFlexGrow -= lp.flexGrow;
|
|
} else {
|
|
accumulatedRoundError = rawCalculatedWidth - roundedCalculatedWidth;
|
|
}
|
|
child.measure(makeMeasureSpec(roundedCalculatedWidth, EXACTLY), makeMeasureSpec(child.getMeasuredHeight(), EXACTLY));
|
|
}
|
|
flexLine._mainSize += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
|
|
} else {
|
|
if (!this._childrenFrozen[childIndex]) {
|
|
let rawCalculatedHeight = child.getMeasuredHeight() + unitSpace * lp.flexGrow + accumulatedRoundError;
|
|
let roundedCalculatedHeight = Math.round(rawCalculatedHeight);
|
|
if (roundedCalculatedHeight > lp.maxHeight) {
|
|
needsReexpand = true;
|
|
roundedCalculatedHeight = lp.maxHeight;
|
|
this._childrenFrozen[childIndex] = true;
|
|
flexLine._totalFlexGrow -= lp.flexGrow;
|
|
} else {
|
|
accumulatedRoundError = rawCalculatedHeight - roundedCalculatedHeight;
|
|
}
|
|
child.measure(makeMeasureSpec(child.getMeasuredWidth(), EXACTLY), makeMeasureSpec(roundedCalculatedHeight, EXACTLY));
|
|
}
|
|
flexLine._mainSize += child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
|
|
}
|
|
childIndex++;
|
|
}
|
|
|
|
if (needsReexpand && sizeBeforeExpand !== flexLine._mainSize) {
|
|
this._expandFlexItems(flexLine, flexDirection, maxMainSize, paddingAlongMainAxis, startIndex);
|
|
}
|
|
return childIndex;
|
|
}
|
|
|
|
private _shrinkFlexItems(flexLine: FlexLine, flexDirection: FlexDirection, maxMainSize: number, paddingAlongMainAxis: number, startIndex: number): number {
|
|
let childIndex = startIndex;
|
|
let sizeBeforeShrink = flexLine._mainSize;
|
|
if (flexLine._totalFlexShrink <= 0 || maxMainSize > flexLine._mainSize) {
|
|
childIndex += flexLine.itemCount;
|
|
return childIndex;
|
|
}
|
|
let needsReshrink = false;
|
|
let unitShrink = (flexLine._mainSize - maxMainSize) / flexLine._totalFlexShrink;
|
|
let accumulatedRoundError = 0;
|
|
flexLine._mainSize = paddingAlongMainAxis + flexLine._dividerLengthInMainSize;
|
|
for (let i = 0; i < flexLine.itemCount; i++) {
|
|
let child = this._getReorderedChildAt(childIndex);
|
|
if (child === null) {
|
|
continue;
|
|
} else if (FlexboxLayout.isGone(child)) {
|
|
childIndex++;
|
|
continue;
|
|
}
|
|
let lp = FlexboxLayout.getLayoutParams(child);
|
|
if (this._isMainAxisDirectionHorizontal(flexDirection)) {
|
|
// The direction of main axis is horizontal
|
|
if (!this._childrenFrozen[childIndex]) {
|
|
let rawCalculatedWidth = child.getMeasuredWidth() - unitShrink * lp.flexShrink;
|
|
if (i === flexLine.itemCount - 1) {
|
|
rawCalculatedWidth += accumulatedRoundError;
|
|
accumulatedRoundError = 0;
|
|
}
|
|
let newWidth = Math.round(rawCalculatedWidth);
|
|
if (newWidth < lp.minWidth) {
|
|
needsReshrink = true;
|
|
newWidth = lp.minWidth;
|
|
this._childrenFrozen[childIndex] = true;
|
|
flexLine._totalFlexShrink -= lp.flexShrink;
|
|
} else {
|
|
accumulatedRoundError += (rawCalculatedWidth - newWidth);
|
|
if (accumulatedRoundError > 1.0) {
|
|
newWidth += 1;
|
|
accumulatedRoundError -= 1;
|
|
} else if (accumulatedRoundError < -1.0) {
|
|
newWidth -= 1;
|
|
accumulatedRoundError += 1;
|
|
}
|
|
}
|
|
child.measure(makeMeasureSpec(newWidth, EXACTLY), makeMeasureSpec(child.getMeasuredHeight(), EXACTLY));
|
|
}
|
|
flexLine._mainSize += child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
|
|
} else {
|
|
if (!this._childrenFrozen[childIndex]) {
|
|
let rawCalculatedHeight = child.getMeasuredHeight() - unitShrink * lp.flexShrink;
|
|
if (i === flexLine.itemCount - 1) {
|
|
rawCalculatedHeight += accumulatedRoundError;
|
|
accumulatedRoundError = 0;
|
|
}
|
|
let newHeight = Math.round(rawCalculatedHeight);
|
|
if (newHeight < lp.minHeight) {
|
|
needsReshrink = true;
|
|
newHeight = lp.minHeight;
|
|
this._childrenFrozen[childIndex] = true;
|
|
flexLine._totalFlexShrink -= lp.flexShrink;
|
|
} else {
|
|
accumulatedRoundError += (rawCalculatedHeight - newHeight);
|
|
if (accumulatedRoundError > 1.0) {
|
|
newHeight += 1;
|
|
accumulatedRoundError -= 1;
|
|
} else if (accumulatedRoundError < -1.0) {
|
|
newHeight -= 1;
|
|
accumulatedRoundError += 1;
|
|
}
|
|
}
|
|
child.measure(makeMeasureSpec(child.getMeasuredWidth(), EXACTLY), makeMeasureSpec(newHeight, EXACTLY));
|
|
}
|
|
flexLine._mainSize += child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
|
|
}
|
|
childIndex++;
|
|
}
|
|
|
|
if (needsReshrink && sizeBeforeShrink !== flexLine._mainSize) {
|
|
this._shrinkFlexItems(flexLine, flexDirection, maxMainSize, paddingAlongMainAxis, startIndex);
|
|
}
|
|
return childIndex;
|
|
}
|
|
|
|
private _determineCrossSize(flexDirection: FlexDirection, widthMeasureSpec: number, heightMeasureSpec: number, paddingAlongCrossAxis: number) {
|
|
let mode;
|
|
let size;
|
|
switch (flexDirection) {
|
|
case FlexDirection.ROW:
|
|
case FlexDirection.ROW_REVERSE:
|
|
mode = getMeasureSpecMode(heightMeasureSpec);
|
|
size = getMeasureSpecSize(heightMeasureSpec);
|
|
break;
|
|
case FlexDirection.COLUMN:
|
|
case FlexDirection.COLUMN_REVERSE:
|
|
mode = getMeasureSpecMode(widthMeasureSpec);
|
|
size = getMeasureSpecSize(widthMeasureSpec);
|
|
break;
|
|
default:
|
|
throw new Error("Invalid flex direction: " + flexDirection);
|
|
}
|
|
if (mode === EXACTLY) {
|
|
let totalCrossSize = this._getSumOfCrossSize() + paddingAlongCrossAxis;
|
|
if (this._flexLines.length === 1) {
|
|
this._flexLines[0]._crossSize = size - paddingAlongCrossAxis;
|
|
} else if (this._flexLines.length >= 2 && totalCrossSize < size) {
|
|
switch (this.alignContent) {
|
|
case AlignContent.STRETCH:
|
|
(() => {
|
|
let freeSpaceUnit = (size - totalCrossSize) / this._flexLines.length;
|
|
let accumulatedError = 0;
|
|
for (let i = 0, flexLinesSize = this._flexLines.length; i < flexLinesSize; i++) {
|
|
let flexLine = this._flexLines[i];
|
|
let newCrossSizeAsFloat = flexLine._crossSize + freeSpaceUnit;
|
|
if (i === this._flexLines.length - 1) {
|
|
newCrossSizeAsFloat += accumulatedError;
|
|
accumulatedError = 0;
|
|
}
|
|
let newCrossSize = Math.round(newCrossSizeAsFloat);
|
|
accumulatedError += (newCrossSizeAsFloat - newCrossSize);
|
|
if (accumulatedError > 1) {
|
|
newCrossSize += 1;
|
|
accumulatedError -= 1;
|
|
} else if (accumulatedError < -1) {
|
|
newCrossSize -= 1;
|
|
accumulatedError += 1;
|
|
}
|
|
flexLine._crossSize = newCrossSize;
|
|
}
|
|
})();
|
|
break;
|
|
case AlignContent.SPACE_AROUND:
|
|
(() => {
|
|
let spaceTopAndBottom = size - totalCrossSize;
|
|
let numberOfSpaces = this._flexLines.length * 2;
|
|
spaceTopAndBottom = spaceTopAndBottom / numberOfSpaces;
|
|
let newFlexLines: FlexLine[] = [];
|
|
let dummySpaceFlexLine = new FlexLine();
|
|
dummySpaceFlexLine._crossSize = spaceTopAndBottom;
|
|
this._flexLines.forEach(flexLine => {
|
|
newFlexLines.push(dummySpaceFlexLine);
|
|
newFlexLines.push(flexLine);
|
|
newFlexLines.push(dummySpaceFlexLine);
|
|
});
|
|
this._flexLines = newFlexLines;
|
|
})();
|
|
break;
|
|
case AlignContent.SPACE_BETWEEN:
|
|
(() => {
|
|
let spaceBetweenFlexLine = size - totalCrossSize;
|
|
let numberOfSpaces = this._flexLines.length - 1;
|
|
spaceBetweenFlexLine = spaceBetweenFlexLine / numberOfSpaces;
|
|
let accumulatedError = 0;
|
|
let newFlexLines: FlexLine[] = [];
|
|
for (let i = 0, flexLineSize = this._flexLines.length; i < flexLineSize; i++) {
|
|
let flexLine = this._flexLines[i];
|
|
newFlexLines.push(flexLine);
|
|
|
|
if (i !== this._flexLines.length - 1) {
|
|
let dummySpaceFlexLine = new FlexLine();
|
|
if (i === this._flexLines.length - 2) {
|
|
dummySpaceFlexLine._crossSize = Math.round(spaceBetweenFlexLine + accumulatedError);
|
|
accumulatedError = 0;
|
|
} else {
|
|
dummySpaceFlexLine._crossSize = Math.round(spaceBetweenFlexLine);
|
|
}
|
|
accumulatedError += (spaceBetweenFlexLine - dummySpaceFlexLine._crossSize);
|
|
if (accumulatedError > 1) {
|
|
dummySpaceFlexLine._crossSize += 1;
|
|
accumulatedError -= 1;
|
|
} else if (accumulatedError < -1) {
|
|
dummySpaceFlexLine._crossSize -= 1;
|
|
accumulatedError += 1;
|
|
}
|
|
newFlexLines.push(dummySpaceFlexLine);
|
|
}
|
|
}
|
|
this._flexLines = newFlexLines;
|
|
})();
|
|
break;
|
|
case AlignContent.CENTER: {
|
|
let spaceAboveAndBottom = size - totalCrossSize;
|
|
spaceAboveAndBottom = spaceAboveAndBottom / 2;
|
|
let newFlexLines: FlexLine[] = [];
|
|
let dummySpaceFlexLine = new FlexLine();
|
|
dummySpaceFlexLine._crossSize = spaceAboveAndBottom;
|
|
for (let i = 0, flexLineSize = this._flexLines.length; i < flexLineSize; i++) {
|
|
if (i === 0) {
|
|
newFlexLines.push(dummySpaceFlexLine);
|
|
}
|
|
let flexLine = this._flexLines[i];
|
|
newFlexLines.push(flexLine);
|
|
if (i === this._flexLines.length - 1) {
|
|
newFlexLines.push(dummySpaceFlexLine);
|
|
}
|
|
}
|
|
this._flexLines = newFlexLines;
|
|
break;
|
|
}
|
|
case AlignContent.FLEX_END: {
|
|
let spaceTop = size - totalCrossSize;
|
|
let dummySpaceFlexLine = new FlexLine();
|
|
dummySpaceFlexLine._crossSize = spaceTop;
|
|
this._flexLines.unshift(dummySpaceFlexLine);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private _stretchViews(flexDirection: FlexDirection, alignItems: AlignItems) {
|
|
if (alignItems === AlignItems.STRETCH) {
|
|
let viewIndex = 0;
|
|
this._flexLines.forEach(flexLine => {
|
|
for (let i = 0; i < flexLine.itemCount; i++, viewIndex++) {
|
|
let view = this._getReorderedChildAt(viewIndex);
|
|
let lp = FlexboxLayout.getLayoutParams(view);
|
|
if (lp.alignSelf !== AlignSelf.AUTO && lp.alignSelf !== AlignSelf.STRETCH) {
|
|
continue;
|
|
}
|
|
switch (flexDirection) {
|
|
case FlexDirection.ROW:
|
|
case FlexDirection.ROW_REVERSE:
|
|
this._stretchViewVertically(view, flexLine._crossSize);
|
|
break;
|
|
case FlexDirection.COLUMN:
|
|
case FlexDirection.COLUMN_REVERSE:
|
|
this._stretchViewHorizontally(view, flexLine._crossSize);
|
|
break;
|
|
default:
|
|
throw new Error("Invalid flex direction: " + flexDirection);
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
this._flexLines.forEach(flexLine => {
|
|
flexLine._indicesAlignSelfStretch.forEach(index => {
|
|
let view = this._getReorderedChildAt(index);
|
|
switch (flexDirection) {
|
|
case FlexDirection.ROW:
|
|
case FlexDirection.ROW_REVERSE:
|
|
this._stretchViewVertically(view, flexLine._crossSize);
|
|
break;
|
|
case FlexDirection.COLUMN:
|
|
case FlexDirection.COLUMN_REVERSE:
|
|
this._stretchViewHorizontally(view, flexLine._crossSize);
|
|
break;
|
|
default:
|
|
throw new Error("Invalid flex direction: " + flexDirection);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
private _stretchViewVertically(view: View, crossSize: number) {
|
|
let lp = FlexboxLayout.getLayoutParams(view);
|
|
let newHeight = crossSize - lp.topMargin - lp.bottomMargin;
|
|
newHeight = Math.max(newHeight, 0);
|
|
view.measure(makeMeasureSpec(view.getMeasuredWidth(), EXACTLY), makeMeasureSpec(newHeight, EXACTLY));
|
|
}
|
|
|
|
private _stretchViewHorizontally(view: View, crossSize: number) {
|
|
let lp = FlexboxLayout.getLayoutParams(view);
|
|
let newWidth = crossSize - lp.leftMargin - lp.rightMargin;
|
|
newWidth = Math.max(newWidth, 0);
|
|
view.measure(makeMeasureSpec(newWidth, EXACTLY), makeMeasureSpec(view.getMeasuredHeight(), EXACTLY));
|
|
}
|
|
|
|
private _setMeasuredDimensionForFlex(flexDirection: FlexDirection, widthMeasureSpec: number, heightMeasureSpec: number, childState: number) {
|
|
let widthMode = getMeasureSpecMode(widthMeasureSpec);
|
|
let widthSize = getMeasureSpecSize(widthMeasureSpec);
|
|
let heightMode = getMeasureSpecMode(heightMeasureSpec);
|
|
let heightSize = getMeasureSpecSize(heightMeasureSpec);
|
|
let calculatedMaxHeight;
|
|
let calculatedMaxWidth;
|
|
switch (flexDirection) {
|
|
case FlexDirection.ROW:
|
|
case FlexDirection.ROW_REVERSE:
|
|
calculatedMaxHeight = this._getSumOfCrossSize() + this.paddingTop + this.paddingBottom;
|
|
calculatedMaxWidth = this._getLargestMainSize();
|
|
break;
|
|
case FlexDirection.COLUMN:
|
|
case FlexDirection.COLUMN_REVERSE:
|
|
calculatedMaxHeight = this._getLargestMainSize();
|
|
calculatedMaxWidth = this._getSumOfCrossSize() + this.paddingLeft + this.paddingRight;
|
|
break;
|
|
default:
|
|
throw new Error("Invalid flex direction: " + flexDirection);
|
|
}
|
|
|
|
let widthSizeAndState;
|
|
switch (widthMode) {
|
|
case EXACTLY:
|
|
if (widthSize < calculatedMaxWidth) {
|
|
childState = View.combineMeasuredStates(childState, MEASURED_STATE_TOO_SMALL);
|
|
}
|
|
widthSizeAndState = View.resolveSizeAndState(widthSize, widthSize, widthMode, childState);
|
|
break;
|
|
case AT_MOST: {
|
|
if (widthSize < calculatedMaxWidth) {
|
|
childState = View.combineMeasuredStates(childState, MEASURED_STATE_TOO_SMALL);
|
|
} else {
|
|
widthSize = calculatedMaxWidth;
|
|
}
|
|
widthSizeAndState = View.resolveSizeAndState(widthSize, widthSize, widthMode, childState);
|
|
break;
|
|
}
|
|
case UNSPECIFIED: {
|
|
widthSizeAndState = View.resolveSizeAndState(calculatedMaxWidth, widthSize, widthMode, childState);
|
|
break;
|
|
}
|
|
default:
|
|
throw new Error("Unknown width mode is set: " + widthMode);
|
|
}
|
|
let heightSizeAndState;
|
|
switch (heightMode) {
|
|
case EXACTLY:
|
|
if (heightSize < calculatedMaxHeight) {
|
|
childState = View.combineMeasuredStates(childState, MEASURED_STATE_TOO_SMALL >> utils.layout.MEASURED_HEIGHT_STATE_SHIFT);
|
|
}
|
|
heightSizeAndState = View.resolveSizeAndState(heightSize, heightSize, heightMode, childState);
|
|
break;
|
|
case AT_MOST: {
|
|
if (heightSize < calculatedMaxHeight) {
|
|
childState = View.combineMeasuredStates(childState, MEASURED_STATE_TOO_SMALL >> utils.layout.MEASURED_HEIGHT_STATE_SHIFT);
|
|
} else {
|
|
heightSize = calculatedMaxHeight;
|
|
}
|
|
|
|
heightSizeAndState = View.resolveSizeAndState(heightSize, heightSize, heightMode, childState);
|
|
break;
|
|
}
|
|
case UNSPECIFIED: {
|
|
heightSizeAndState = View.resolveSizeAndState(calculatedMaxHeight, heightSize, heightMode, childState);
|
|
break;
|
|
}
|
|
default:
|
|
throw new Error("Unknown height mode is set: " + heightMode);
|
|
}
|
|
this.setMeasuredDimension(widthSizeAndState, heightSizeAndState);
|
|
}
|
|
|
|
private _isWrapRequired(mode: number, maxSize: number, currentLength: number, childLength: number, lp: FlexboxLayout.LayoutParams, childAbsoluteIndex: number, childRelativeIndexInFlexLine: number): boolean {
|
|
if (this.flexWrap === FlexWrap.NOWRAP) {
|
|
return false;
|
|
}
|
|
if (lp.wrapBefore) {
|
|
return true;
|
|
}
|
|
if (mode === UNSPECIFIED) {
|
|
return false;
|
|
}
|
|
|
|
// Omit divider
|
|
|
|
return maxSize < currentLength + childLength;
|
|
}
|
|
|
|
private _getLargestMainSize(): number {
|
|
return this._flexLines.reduce((max, flexLine) => Math.max(max, flexLine.mainSize), Number.MIN_VALUE);
|
|
}
|
|
|
|
private _getSumOfCrossSize(): number {
|
|
// Omit divider
|
|
return this._flexLines.reduce((sum, flexLine) => sum + flexLine._crossSize, 0);
|
|
}
|
|
|
|
private _isMainAxisDirectionHorizontal(flexDirection: FlexDirection): boolean {
|
|
return flexDirection === FlexDirection.ROW || flexDirection === FlexDirection.ROW_REVERSE;
|
|
}
|
|
|
|
public onLayout(left: number, top: number, right: number, bottom: number) {
|
|
let isRtl;
|
|
switch (this.flexDirection) {
|
|
case FlexDirection.ROW:
|
|
isRtl = false;
|
|
this._layoutHorizontal(isRtl, left, top, right, bottom);
|
|
break;
|
|
case FlexDirection.ROW_REVERSE:
|
|
isRtl = true;
|
|
this._layoutHorizontal(isRtl, left, top, right, bottom);
|
|
break;
|
|
case FlexDirection.COLUMN:
|
|
isRtl = false;
|
|
if (this.flexWrap === FlexWrap.WRAP_REVERSE) {
|
|
isRtl = !isRtl;
|
|
}
|
|
this._layoutVertical(isRtl, false, left, top, right, bottom);
|
|
break;
|
|
case FlexDirection.COLUMN_REVERSE:
|
|
isRtl = false;
|
|
if (this.flexWrap === FlexWrap.WRAP_REVERSE) {
|
|
isRtl = !isRtl;
|
|
}
|
|
this._layoutVertical(isRtl, true, left, top, right, bottom);
|
|
break;
|
|
default:
|
|
throw new Error("Invalid flex direction is set: " + this.flexDirection);
|
|
}
|
|
|
|
LayoutBase.restoreOriginalParams(this);
|
|
}
|
|
|
|
private _layoutHorizontal(isRtl: boolean, left: number, top: number, right: number, bottom: number) {
|
|
let paddingLeft = this.paddingLeft;
|
|
let paddingRight = this.paddingRight;
|
|
|
|
let childLeft;
|
|
let currentViewIndex = 0;
|
|
|
|
let height = bottom - top;
|
|
let width = right - left;
|
|
|
|
let childBottom = height - this.paddingBottom;
|
|
let childTop = this.paddingTop;
|
|
|
|
let childRight;
|
|
this._flexLines.forEach((flexLine, i) => {
|
|
|
|
// Omit divider
|
|
|
|
let spaceBetweenItem = 0.0;
|
|
switch (this.justifyContent) {
|
|
case JustifyContent.FLEX_START:
|
|
childLeft = paddingLeft;
|
|
childRight = width - paddingRight;
|
|
break;
|
|
case JustifyContent.FLEX_END:
|
|
childLeft = width - flexLine._mainSize + paddingRight;
|
|
childRight = flexLine._mainSize - paddingLeft;
|
|
break;
|
|
case JustifyContent.CENTER:
|
|
childLeft = paddingLeft + (width - flexLine._mainSize) / 2.0;
|
|
childRight = width - paddingRight - (width - flexLine._mainSize) / 2.0;
|
|
break;
|
|
case JustifyContent.SPACE_AROUND:
|
|
if (flexLine._itemCount !== 0) {
|
|
spaceBetweenItem = (width - flexLine.mainSize) / flexLine._itemCount;
|
|
}
|
|
childLeft = paddingLeft + spaceBetweenItem / 2.0;
|
|
childRight = width - paddingRight - spaceBetweenItem / 2.0;
|
|
break;
|
|
case JustifyContent.SPACE_BETWEEN:
|
|
childLeft = paddingLeft;
|
|
let denominator = flexLine.itemCount !== 1 ? flexLine.itemCount - 1 : 1.0;
|
|
spaceBetweenItem = (width - flexLine.mainSize) / denominator;
|
|
childRight = width - paddingRight;
|
|
break;
|
|
default:
|
|
throw new Error("Invalid justifyContent is set: " + this.justifyContent);
|
|
}
|
|
spaceBetweenItem = Math.max(spaceBetweenItem, 0);
|
|
|
|
for (let j = 0; j < flexLine.itemCount; j++) {
|
|
let child = this._getReorderedChildAt(currentViewIndex);
|
|
if (child === null) {
|
|
continue;
|
|
} else if (FlexboxLayout.isGone(child)) {
|
|
currentViewIndex++;
|
|
continue;
|
|
}
|
|
let lp = FlexboxLayout.getLayoutParams(child);
|
|
childLeft += lp.leftMargin;
|
|
childRight -= lp.rightMargin;
|
|
|
|
// Omit divider
|
|
|
|
if (this.flexWrap === FlexWrap.WRAP_REVERSE) {
|
|
if (isRtl) {
|
|
this._layoutSingleChildHorizontal(child, flexLine, this.flexWrap, this.alignItems,
|
|
Math.round(childRight) - child.getMeasuredWidth(),
|
|
childBottom - child.getMeasuredHeight(), Math.round(childRight),
|
|
childBottom);
|
|
} else {
|
|
this._layoutSingleChildHorizontal(child, flexLine, this.flexWrap, this.alignItems,
|
|
Math.round(childLeft), childBottom - child.getMeasuredHeight(),
|
|
Math.round(childLeft) + child.getMeasuredWidth(),
|
|
childBottom);
|
|
}
|
|
} else {
|
|
if (isRtl) {
|
|
this._layoutSingleChildHorizontal(child, flexLine, this.flexWrap, this.alignItems,
|
|
Math.round(childRight) - child.getMeasuredWidth(), childTop,
|
|
Math.round(childRight), childTop + child.getMeasuredHeight());
|
|
} else {
|
|
this._layoutSingleChildHorizontal(child, flexLine, this.flexWrap, this.alignItems,
|
|
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++;
|
|
|
|
let bounds = child._getCurrentLayoutBounds();
|
|
flexLine._left = Math.min(flexLine._left, bounds.left - lp.leftMargin);
|
|
flexLine._top = Math.min(flexLine._top, bounds.top - lp.topMargin);
|
|
flexLine._right = Math.max(flexLine._right, bounds.right + lp.rightMargin);
|
|
flexLine._bottom = Math.max(flexLine._bottom, bounds.bottom + lp.bottomMargin);
|
|
}
|
|
|
|
childTop += flexLine._crossSize;
|
|
childBottom -= flexLine._crossSize;
|
|
});
|
|
}
|
|
|
|
private _layoutSingleChildHorizontal(view: View, flexLine: FlexLine, flexWrap: FlexWrap, alignItems: AlignItems, left: number, top: number, right: number, bottom: number): void {
|
|
let lp = FlexboxLayout.getLayoutParams(view);
|
|
|
|
if (lp.alignSelf !== AlignSelf.AUTO) {
|
|
alignItems = lp.alignSelf;
|
|
}
|
|
|
|
let crossSize = flexLine._crossSize;
|
|
switch (alignItems) {
|
|
case AlignItems.FLEX_START:
|
|
case AlignItems.STRETCH:
|
|
if (flexWrap !== FlexWrap.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 AlignItems.BASELINE:
|
|
if (flexWrap !== FlexWrap.WRAP_REVERSE) {
|
|
let marginTop = flexLine._maxBaseline - FlexboxLayout.getBaseline(view);
|
|
marginTop = Math.max(marginTop, lp.topMargin);
|
|
view.layout(left, top + marginTop, right, bottom + marginTop);
|
|
} else {
|
|
let marginBottom = flexLine._maxBaseline - view.getMeasuredHeight() + FlexboxLayout.getBaseline(view);
|
|
marginBottom = Math.max(marginBottom, lp.bottomMargin);
|
|
view.layout(left, top - marginBottom, right, bottom - marginBottom);
|
|
}
|
|
break;
|
|
case AlignItems.FLEX_END:
|
|
if (flexWrap !== FlexWrap.WRAP_REVERSE) {
|
|
view.layout(left,
|
|
top + crossSize - view.getMeasuredHeight() - lp.bottomMargin,
|
|
right, top + crossSize - lp.bottomMargin);
|
|
} else {
|
|
view.layout(left, top - crossSize + view.getMeasuredHeight() + lp.topMargin,
|
|
right, bottom - crossSize + view.getMeasuredHeight() + lp.topMargin);
|
|
}
|
|
break;
|
|
case AlignItems.CENTER:
|
|
let topFromCrossAxis = (crossSize - view.getMeasuredHeight()) / 2;
|
|
if (flexWrap !== FlexWrap.WRAP_REVERSE) {
|
|
view.layout(left, top + topFromCrossAxis + lp.topMargin - lp.bottomMargin,
|
|
right, top + topFromCrossAxis + view.getMeasuredHeight() + lp.topMargin
|
|
- lp.bottomMargin);
|
|
} else {
|
|
view.layout(left, top - topFromCrossAxis + lp.topMargin - lp.bottomMargin,
|
|
right, top - topFromCrossAxis + view.getMeasuredHeight() + lp.topMargin
|
|
- lp.bottomMargin);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
private _layoutVertical(isRtl: boolean, fromBottomToTop: boolean, left: number, top: number, right: number, bottom: number) {
|
|
let paddingTop = this.paddingTop;
|
|
let paddingBottom = this.paddingBottom;
|
|
|
|
let paddingRight = this.paddingRight;
|
|
let childLeft = this.paddingLeft;
|
|
let currentViewIndex = 0;
|
|
|
|
let width = right - left;
|
|
let height = bottom - top;
|
|
let childRight = width - paddingRight;
|
|
|
|
let childTop;
|
|
let childBottom;
|
|
|
|
this._flexLines.forEach(flexLine => {
|
|
|
|
// Omit divider.
|
|
|
|
let spaceBetweenItem = 0.0;
|
|
|
|
switch (this.justifyContent) {
|
|
case JustifyContent.FLEX_START:
|
|
childTop = paddingTop;
|
|
childBottom = height - paddingBottom;
|
|
break;
|
|
case JustifyContent.FLEX_END:
|
|
childTop = height - flexLine._mainSize + paddingBottom;
|
|
childBottom = flexLine._mainSize - paddingTop;
|
|
break;
|
|
case JustifyContent.CENTER:
|
|
childTop = paddingTop + (height - flexLine._mainSize) / 2.0;
|
|
childBottom = height - paddingBottom - (height - flexLine._mainSize) / 2.0;
|
|
break;
|
|
case JustifyContent.SPACE_AROUND:
|
|
if (flexLine._itemCount !== 0) {
|
|
spaceBetweenItem = (height - flexLine._mainSize) / flexLine.itemCount;
|
|
}
|
|
childTop = paddingTop + spaceBetweenItem / 2.0;
|
|
childBottom = height - paddingBottom - spaceBetweenItem / 2.0;
|
|
break;
|
|
case JustifyContent.SPACE_BETWEEN:
|
|
childTop = paddingTop;
|
|
let denominator = flexLine.itemCount !== 1 ? flexLine.itemCount - 1 : 1.0;
|
|
spaceBetweenItem = (height - flexLine.mainSize) / denominator;
|
|
childBottom = height - paddingBottom;
|
|
break;
|
|
default:
|
|
throw new Error("Invalid justifyContent is set: " + this.justifyContent);
|
|
}
|
|
spaceBetweenItem = Math.max(spaceBetweenItem, 0);
|
|
|
|
for (let j = 0; j < flexLine.itemCount; j++) {
|
|
let child = this._getReorderedChildAt(currentViewIndex);
|
|
if (child === null) {
|
|
continue;
|
|
} else if (FlexboxLayout.isGone(child)) {
|
|
currentViewIndex++;
|
|
continue;
|
|
}
|
|
let lp = FlexboxLayout.getLayoutParams(child);
|
|
childTop += lp.topMargin;
|
|
childBottom -= lp.bottomMargin;
|
|
|
|
// Omit divider.
|
|
|
|
if (isRtl) {
|
|
if (fromBottomToTop) {
|
|
this._layoutSingleChildVertical(child, flexLine, true, this.alignItems,
|
|
childRight - child.getMeasuredWidth(),
|
|
Math.round(childBottom) - child.getMeasuredHeight(), childRight,
|
|
Math.round(childBottom));
|
|
} else {
|
|
this._layoutSingleChildVertical(child, flexLine, true, this.alignItems,
|
|
childRight - child.getMeasuredWidth(), Math.round(childTop),
|
|
childRight, Math.round(childTop) + child.getMeasuredHeight());
|
|
}
|
|
} else {
|
|
if (fromBottomToTop) {
|
|
this._layoutSingleChildVertical(child, flexLine, false, this.alignItems,
|
|
childLeft, Math.round(childBottom) - child.getMeasuredHeight(),
|
|
childLeft + child.getMeasuredWidth(), Math.round(childBottom));
|
|
} else {
|
|
this._layoutSingleChildVertical(child, flexLine, false, this.alignItems,
|
|
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++;
|
|
|
|
let bounds = child._getCurrentLayoutBounds();
|
|
flexLine._left = Math.min(flexLine._left, bounds.left - lp.leftMargin);
|
|
flexLine._top = Math.min(flexLine._top, bounds.top - lp.topMargin);
|
|
flexLine._right = Math.max(flexLine._right, bounds.right + lp.rightMargin);
|
|
flexLine._bottom = Math.max(flexLine._bottom, bounds.bottom + lp.bottomMargin);
|
|
}
|
|
|
|
childLeft += flexLine.crossSize;
|
|
childRight -= flexLine.crossSize;
|
|
});
|
|
}
|
|
|
|
private _layoutSingleChildVertical(view: View, flexLine: FlexLine, isRtl: boolean, alignItems: AlignItems, left: number, top: number, right: number, bottom: number) {
|
|
let lp = FlexboxLayout.getLayoutParams(view);
|
|
if (lp.alignSelf !== AlignSelf.AUTO) {
|
|
alignItems = lp.alignSelf;
|
|
}
|
|
let crossSize = flexLine.crossSize;
|
|
switch (alignItems) {
|
|
case AlignItems.FLEX_START:
|
|
case AlignItems.STRETCH:
|
|
case AlignItems.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 AlignItems.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 AlignItems.CENTER:
|
|
let leftFromCrossAxis = (crossSize - view.getMeasuredWidth()) / 2;
|
|
if (!isRtl) {
|
|
view.layout(left + leftFromCrossAxis + lp.leftMargin - lp.rightMargin,
|
|
top, right + leftFromCrossAxis + lp.leftMargin - lp.rightMargin,
|
|
bottom);
|
|
} else {
|
|
view.layout(left - leftFromCrossAxis + lp.leftMargin - lp.rightMargin,
|
|
top, right - leftFromCrossAxis + lp.leftMargin - lp.rightMargin,
|
|
bottom);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Omit divider in onDraw(), drawDividersHorizontal, drawDividersVertical, drawVerticalDivider
|
|
|
|
// requestLayout on set flexDirection, set flexWrap, set justifyContent, set alignItems, set alignContent
|
|
|
|
// NOTE Consider moving to View if frequently used
|
|
private static getChildMeasureSpec(spec: number, padding: number, childDimension: number): number {
|
|
let specMode = utils.layout.getMeasureSpecMode(spec);
|
|
let specSize = utils.layout.getMeasureSpecSize(spec);
|
|
|
|
let size = Math.max(0, specSize - padding);
|
|
|
|
let resultSize = 0;
|
|
let resultMode = 0;
|
|
|
|
switch (specMode) {
|
|
// Parent has imposed an exact size on us
|
|
case EXACTLY:
|
|
if (childDimension >= 0) {
|
|
resultSize = childDimension;
|
|
resultMode = EXACTLY;
|
|
} else if (childDimension === MATCH_PARENT) {
|
|
resultSize = size;
|
|
resultMode = EXACTLY;
|
|
} else if (childDimension === WRAP_CONTENT) {
|
|
resultSize = size;
|
|
resultMode = AT_MOST;
|
|
}
|
|
break;
|
|
|
|
case AT_MOST:
|
|
if (childDimension >= 0) {
|
|
resultSize = childDimension;
|
|
resultMode = EXACTLY;
|
|
} else if (childDimension === MATCH_PARENT) {
|
|
resultSize = size;
|
|
resultMode = AT_MOST;
|
|
} else if (childDimension === WRAP_CONTENT) {
|
|
resultSize = size;
|
|
resultMode = AT_MOST;
|
|
}
|
|
break;
|
|
|
|
case UNSPECIFIED:
|
|
if (childDimension >= 0) {
|
|
resultSize = childDimension;
|
|
resultMode = EXACTLY;
|
|
} else if (childDimension === MATCH_PARENT) {
|
|
resultSize = View_sUseZeroUnspecifiedMeasureSpec ? 0 : size;
|
|
resultMode = UNSPECIFIED;
|
|
} else if (childDimension === WRAP_CONTENT) {
|
|
resultSize = View_sUseZeroUnspecifiedMeasureSpec ? 0 : size;
|
|
resultMode = UNSPECIFIED;
|
|
}
|
|
break;
|
|
}
|
|
return utils.layout.makeMeasureSpec(resultSize, resultMode);
|
|
}
|
|
}
|
|
|
|
export namespace FlexboxLayout {
|
|
export interface LayoutParams extends CommonLayoutParams {
|
|
order: number;
|
|
alignSelf: AlignSelf;
|
|
flexGrow: number;
|
|
flexShrink: number;
|
|
flexBasisPercent: number;
|
|
wrapBefore: boolean;
|
|
minWidth: number;
|
|
minHeight: number;
|
|
maxWidth: number;
|
|
maxHeight: number;
|
|
}
|
|
|
|
export function getLayoutParams(child: View): FlexboxLayout.LayoutParams {
|
|
let lp: CommonLayoutParams = child.style._getValue(nativeLayoutParamsProperty);
|
|
let flp: FlexboxLayout.LayoutParams = {
|
|
width: lp.width,
|
|
height: lp.height,
|
|
|
|
widthPercent: lp.widthPercent,
|
|
heightPercent: lp.heightPercent,
|
|
|
|
leftMargin: lp.leftMargin,
|
|
topMargin: lp.topMargin,
|
|
rightMargin: lp.rightMargin,
|
|
bottomMargin: lp.bottomMargin,
|
|
|
|
leftMarginPercent: lp.leftMarginPercent,
|
|
topMarginPercent: lp.topMarginPercent,
|
|
rightMarginPercent: lp.rightMarginPercent,
|
|
bottomMarginPercent: lp.bottomMarginPercent,
|
|
|
|
horizontalAlignment: lp.horizontalAlignment,
|
|
verticalAlignment: lp.verticalAlignment,
|
|
|
|
alignSelf: FlexboxLayout.getAlignSelf(child),
|
|
flexBasisPercent: FlexBasisPercent.DEFAULT,
|
|
flexGrow: FlexboxLayout.getFlexGrow(child),
|
|
flexShrink: FlexboxLayout.getFlexShrink(child),
|
|
maxHeight: MAX_SIZE,
|
|
maxWidth: MAX_SIZE,
|
|
minHeight: child.minHeight,
|
|
minWidth: child.minWidth,
|
|
order: FlexboxLayout.getOrder(child),
|
|
wrapBefore: FlexboxLayout.getFlexWrapBefore(child)
|
|
};
|
|
|
|
return flp;
|
|
}
|
|
|
|
export function getBaseline(child: View): number {
|
|
// TODO: Check if we support baseline for iOS.
|
|
return 0;
|
|
}
|
|
|
|
export function isGone(child: View): boolean {
|
|
return child.visibility !== enums.Visibility.visible;
|
|
}
|
|
|
|
export function getPaddingStart(child: View): number {
|
|
return child.style.paddingLeft;
|
|
}
|
|
|
|
export function getPaddingEnd(child: View): number {
|
|
return child.style.paddingRight;
|
|
}
|
|
}
|