Files
NativeScript/tns-core-modules/ui/layouts/flexbox-layout/flexbox-layout.ios.ts
Panayot Cankov ae02bbdff3 Fix order set at runtime, polish unit test app example.
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
2016-10-24 14:34:26 +03:00

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;
}
}