mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
iOS layout positioning now respects native properties like automaticallyAdjustsScrollViewInsets, edgesForExtendedLayout, extendedLayoutIncludesOpaqueBars, navigationBar.translucent, tabBar.translucent Removed frame-tests.ios.ts - those tests are now invalid Added new layout tests inside page-tests.ios.ts Commented few asserts in scroll-view-tests View now expose ios namespace with layoutView method and UILayoutViewController used by page, tab-view and application module ViewBase now expose viewController property that should be set from all widgets that are using viewcontrollers internally (like Page, Frame, TabView) ViewBase now sets ios property to either the view returned from createNativeView or to nativeViewProptected fragment.transitions now use animation/transition start to add fragments to waitingQueue. Before we did it manually in navigate/goBack. This way we can reuse the fragment.transition when calling showDialog. Also when animation/transition ends we check the animation/transition to see if this fragment should be set as current. Frame expose new loadViewFromEntry method (to load a view from URI) Frame navigation happens once frame is loaded Frame now supports Page as a child in XML Fixed GridLayout row, rowSpan, column, columnSpan properties type Fixed bug in GridLayout where add/remove of columns/rows won't update the internal state of the grid (backport from android when GridLayout is recycled) ListView will no longer invalidate layout when cell is removed Fixed bug in ScrollView ios where effectiveMinWidth/Height was multiplied to density (it is already on device pixels so no need to multiply) TabView android now calls loaded only on the selected child (not all) Core refactoring
968 lines
35 KiB
TypeScript
968 lines
35 KiB
TypeScript
import {
|
|
GridLayoutBase, ItemSpec, View, layout
|
|
} from "./grid-layout-common";
|
|
|
|
export * from "./grid-layout-common";
|
|
|
|
export class GridLayout extends GridLayoutBase {
|
|
private helper: MeasureHelper;
|
|
private columnOffsets = new Array<number>();
|
|
private rowOffsets = new Array<number>();
|
|
private map = new Map<View, MeasureSpecs>();
|
|
|
|
constructor() {
|
|
super();
|
|
this.helper = new MeasureHelper(this);
|
|
}
|
|
|
|
public _onRowAdded(itemSpec: ItemSpec) {
|
|
this.helper.rows.push(new ItemGroup(itemSpec));
|
|
}
|
|
|
|
public _onColumnAdded(itemSpec: ItemSpec) {
|
|
this.helper.columns.push(new ItemGroup(itemSpec));
|
|
}
|
|
|
|
public _onRowRemoved(itemSpec: ItemSpec, index: number) {
|
|
this.helper.rows[index].children.length = 0;
|
|
this.helper.rows.splice(index, 1);
|
|
}
|
|
|
|
public _onColumnRemoved(itemSpec: ItemSpec, index: number) {
|
|
this.helper.columns[index].children.length = 0;
|
|
this.helper.columns.splice(index, 1);
|
|
}
|
|
|
|
public _registerLayoutChild(child: View) {
|
|
this.addToMap(child);
|
|
}
|
|
|
|
public _unregisterLayoutChild(child: View) {
|
|
this.removeFromMap(child);
|
|
}
|
|
|
|
private getColumnIndex(view: View): number {
|
|
return Math.max(0, Math.min(GridLayout.getColumn(view), this.columnsInternal.length - 1));
|
|
}
|
|
|
|
private getRowIndex(view: View): number {
|
|
return Math.max(0, Math.min(GridLayout.getRow(view), this.rowsInternal.length - 1));
|
|
}
|
|
|
|
private getColumnSpan(view: View, columnIndex: number): number {
|
|
return Math.max(1, Math.min(GridLayout.getColumnSpan(view), this.columnsInternal.length - columnIndex));
|
|
}
|
|
|
|
private getRowSpan(view: View, rowIndex: number): number {
|
|
return Math.max(1, Math.min(GridLayout.getRowSpan(view), this.rowsInternal.length - rowIndex));
|
|
}
|
|
|
|
private getColumnSpec(view: View): ItemSpec {
|
|
return this.columnsInternal[this.getColumnIndex(view)] || this.helper.singleColumn;
|
|
}
|
|
|
|
private getRowSpec(view: View): ItemSpec {
|
|
return this.rowsInternal[this.getRowIndex(view)] || this.helper.singleRow;
|
|
}
|
|
|
|
private updateMeasureSpecs(child: View, measureSpec: MeasureSpecs): void {
|
|
let column = this.getColumnSpec(child);
|
|
let columnIndex = this.getColumnIndex(child);
|
|
let columnSpan = this.getColumnSpan(child, columnIndex);
|
|
|
|
let row = this.getRowSpec(child);
|
|
let rowIndex = this.getRowIndex(child);
|
|
let rowSpan = this.getRowSpan(child, rowIndex);
|
|
|
|
measureSpec.setColumn(column);
|
|
measureSpec.setColumnIndex(columnIndex);
|
|
measureSpec.setColumnSpan(columnSpan);
|
|
|
|
measureSpec.setRow(row);
|
|
measureSpec.setRowIndex(rowIndex);
|
|
measureSpec.setRowSpan(rowSpan);
|
|
|
|
measureSpec.autoColumnsCount = 0;
|
|
measureSpec.autoRowsCount = 0;
|
|
measureSpec.measured = false;
|
|
measureSpec.pixelHeight = 0;
|
|
measureSpec.pixelWidth = 0;
|
|
measureSpec.starColumnsCount = 0;
|
|
measureSpec.starRowsCount = 0;
|
|
}
|
|
|
|
private addToMap(child: View): void {
|
|
this.map.set(child, new MeasureSpecs(child));
|
|
}
|
|
|
|
private removeFromMap(child: View): void {
|
|
this.map.delete(child);
|
|
}
|
|
|
|
public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void {
|
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
|
|
|
let measureWidth = 0;
|
|
let measureHeight = 0;
|
|
|
|
const width = layout.getMeasureSpecSize(widthMeasureSpec);
|
|
const widthMode = layout.getMeasureSpecMode(widthMeasureSpec);
|
|
|
|
const height = layout.getMeasureSpecSize(heightMeasureSpec);
|
|
const heightMode = layout.getMeasureSpecMode(heightMeasureSpec);
|
|
|
|
const horizontalPaddingsAndMargins = this.effectivePaddingLeft + this.effectivePaddingRight + this.effectiveBorderLeftWidth + this.effectiveBorderRightWidth;
|
|
const verticalPaddingsAndMargins = this.effectivePaddingTop + this.effectivePaddingBottom + this.effectiveBorderTopWidth + this.effectiveBorderBottomWidth;
|
|
|
|
let infinityWidth = widthMode === layout.UNSPECIFIED;
|
|
let infinityHeight = heightMode === layout.UNSPECIFIED;
|
|
|
|
this.helper.width = Math.max(0, width - horizontalPaddingsAndMargins);
|
|
this.helper.height = Math.max(0, height - verticalPaddingsAndMargins);
|
|
|
|
this.helper.stretchedHorizontally = widthMode === layout.EXACTLY || (this.horizontalAlignment === "stretch" && !infinityWidth);
|
|
this.helper.stretchedVertically = heightMode === layout.EXACTLY || (this.verticalAlignment === "stretch" && !infinityHeight);
|
|
|
|
this.helper.setInfinityWidth(infinityWidth);
|
|
this.helper.setInfinityHeight(infinityHeight);
|
|
|
|
this.helper.clearMeasureSpecs();
|
|
this.helper.init();
|
|
|
|
this.eachLayoutChild((child, last) => {
|
|
let measureSpecs = this.map.get(child);
|
|
this.updateMeasureSpecs(child, measureSpecs);
|
|
this.helper.addMeasureSpec(measureSpecs);
|
|
});
|
|
|
|
this.helper.measure();
|
|
|
|
// Add in our padding
|
|
measureWidth = this.helper.measuredWidth + horizontalPaddingsAndMargins;
|
|
measureHeight = this.helper.measuredHeight + verticalPaddingsAndMargins;
|
|
|
|
// Check against our minimum sizes
|
|
measureWidth = Math.max(measureWidth, this.effectiveMinWidth);
|
|
measureHeight = Math.max(measureHeight, this.effectiveMinHeight);
|
|
|
|
const widthSizeAndState = View.resolveSizeAndState(measureWidth, width, widthMode, 0);
|
|
const heightSizeAndState = View.resolveSizeAndState(measureHeight, height, heightMode, 0);
|
|
|
|
this.setMeasuredDimension(widthSizeAndState, heightSizeAndState);
|
|
}
|
|
|
|
public onLayout(left: number, top: number, right: number, bottom: number): void {
|
|
super.onLayout(left, top, right, bottom);
|
|
|
|
let paddingLeft = this.effectiveBorderLeftWidth + this.effectivePaddingLeft;
|
|
let paddingTop = this.effectiveBorderTopWidth + this.effectivePaddingTop;
|
|
|
|
this.columnOffsets.length = 0;
|
|
this.rowOffsets.length = 0;
|
|
|
|
this.columnOffsets.push(paddingLeft);
|
|
this.rowOffsets.push(paddingTop);
|
|
|
|
let offset = this.columnOffsets[0];
|
|
let roundedOffset = paddingLeft;
|
|
let roundedLength = 0;
|
|
let actualLength = 0;
|
|
|
|
for (let i = 0, size = this.helper.columns.length; i < size; i++) {
|
|
let columnGroup = this.helper.columns[i];
|
|
offset += columnGroup.length;
|
|
|
|
actualLength = offset - roundedOffset;
|
|
roundedLength = Math.round(actualLength);
|
|
columnGroup.rowOrColumn._actualLength = layout.round(layout.toDeviceIndependentPixels(roundedLength));
|
|
roundedOffset += roundedLength;
|
|
|
|
this.columnOffsets.push(roundedOffset);
|
|
}
|
|
|
|
offset = this.rowOffsets[0];
|
|
roundedOffset = paddingTop;
|
|
roundedLength = 0;
|
|
actualLength = 0;
|
|
|
|
for (let i = 0, size = this.helper.rows.length; i < size; i++) {
|
|
let rowGroup = this.helper.rows[i];
|
|
offset += rowGroup.length;
|
|
|
|
actualLength = offset - roundedOffset;
|
|
roundedLength = Math.round(actualLength);
|
|
rowGroup.rowOrColumn._actualLength = layout.round(layout.toDeviceIndependentPixels(roundedLength));
|
|
roundedOffset += roundedLength;
|
|
|
|
this.rowOffsets.push(roundedOffset);
|
|
}
|
|
|
|
for (let i = 0, columns = this.helper.columns.length; i < columns; i++) {
|
|
let columnGroup = this.helper.columns[i];
|
|
for (let j = 0, children = columnGroup.children.length; j < children; j++) {
|
|
|
|
let measureSpec = columnGroup.children[j];
|
|
let childLeft = this.columnOffsets[measureSpec.getColumnIndex()];
|
|
let childRight = this.columnOffsets[measureSpec.getColumnIndex() + measureSpec.getColumnSpan()];
|
|
let childTop = this.rowOffsets[measureSpec.getRowIndex()];
|
|
let childBottom = this.rowOffsets[measureSpec.getRowIndex() + measureSpec.getRowSpan()];
|
|
|
|
// No need to include margins in the width, height
|
|
View.layoutChild(this, measureSpec.child, childLeft, childTop, childRight, childBottom);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class MeasureSpecs {
|
|
private _columnSpan = 1;
|
|
private _rowSpan = 1;
|
|
|
|
public pixelWidth = 0;
|
|
public pixelHeight = 0;
|
|
|
|
public starColumnsCount = 0;
|
|
public starRowsCount = 0;
|
|
|
|
public autoColumnsCount = 0;
|
|
public autoRowsCount = 0;
|
|
|
|
public measured = false;
|
|
|
|
public child: View;
|
|
private column: ItemSpec;
|
|
private row: ItemSpec
|
|
private columnIndex: number = 0;
|
|
private rowIndex: number = 0;
|
|
|
|
constructor(child: View) {
|
|
this.child = child;
|
|
}
|
|
|
|
public getSpanned(): boolean {
|
|
return this._columnSpan > 1 || this._rowSpan > 1;
|
|
}
|
|
|
|
public getIsStar(): boolean {
|
|
return this.starRowsCount > 0 || this.starColumnsCount > 0;
|
|
}
|
|
|
|
public getColumnSpan(): number {
|
|
return this._columnSpan;
|
|
}
|
|
|
|
public getRowSpan(): number {
|
|
return this._rowSpan;
|
|
}
|
|
|
|
public setRowSpan(value: number): void {
|
|
// cannot have zero rowSpan.
|
|
this._rowSpan = Math.max(1, value);
|
|
}
|
|
|
|
public setColumnSpan(value: number): void {
|
|
// cannot have zero colSpan.
|
|
this._columnSpan = Math.max(1, value);
|
|
}
|
|
|
|
public getRowIndex(): number {
|
|
return this.rowIndex;
|
|
}
|
|
|
|
public getColumnIndex(): number {
|
|
return this.columnIndex;
|
|
}
|
|
|
|
public setRowIndex(value: number): void {
|
|
this.rowIndex = value;
|
|
}
|
|
|
|
public setColumnIndex(value: number): void {
|
|
this.columnIndex = value;
|
|
}
|
|
|
|
public getRow(): ItemSpec {
|
|
return this.row;
|
|
}
|
|
|
|
public getColumn(): ItemSpec {
|
|
return this.column;
|
|
}
|
|
|
|
public setRow(value: ItemSpec): void {
|
|
this.row = value;
|
|
}
|
|
|
|
public setColumn(value: ItemSpec): void {
|
|
this.column = value;
|
|
}
|
|
}
|
|
|
|
class ItemGroup {
|
|
public length = 0;
|
|
public measuredCount = 0;
|
|
public rowOrColumn: ItemSpec;
|
|
public children: Array<MeasureSpecs> = new Array<MeasureSpecs>();
|
|
|
|
public measureToFix = 0;
|
|
public currentMeasureToFixCount = 0;
|
|
private infinityLength = false;
|
|
|
|
constructor(spec: ItemSpec) {
|
|
this.rowOrColumn = spec;
|
|
}
|
|
|
|
public setIsLengthInfinity(infinityLength: boolean): void {
|
|
this.infinityLength = infinityLength;
|
|
}
|
|
|
|
public init(density): void {
|
|
this.measuredCount = 0;
|
|
this.currentMeasureToFixCount = 0;
|
|
this.length = this.rowOrColumn.isAbsolute ? this.rowOrColumn.value * density : 0;
|
|
}
|
|
|
|
public getAllMeasured(): boolean {
|
|
return this.measuredCount === this.children.length;
|
|
}
|
|
|
|
public getCanBeFixed(): boolean {
|
|
return this.currentMeasureToFixCount === this.measureToFix;
|
|
}
|
|
|
|
public getIsAuto(): boolean {
|
|
return this.rowOrColumn.isAuto || (this.rowOrColumn.isStar && this.infinityLength);
|
|
}
|
|
|
|
public getIsStar(): boolean {
|
|
return this.rowOrColumn.isStar && !this.infinityLength;
|
|
}
|
|
|
|
public getIsAbsolute(): boolean {
|
|
return this.rowOrColumn.isAbsolute;
|
|
}
|
|
}
|
|
|
|
class MeasureHelper {
|
|
singleRow: ItemSpec;
|
|
singleColumn: ItemSpec;
|
|
grid: GridLayout;
|
|
|
|
infinity: number = layout.makeMeasureSpec(0, layout.UNSPECIFIED);
|
|
rows: Array<ItemGroup> = new Array<ItemGroup>();
|
|
columns: Array<ItemGroup> = new Array<ItemGroup>();
|
|
|
|
width: number = 0;
|
|
height: number = 0;
|
|
stretchedHorizontally: boolean = false;
|
|
stretchedVertically: boolean = false;
|
|
|
|
infinityWidth: boolean = false;
|
|
infinityHeight: boolean = false;
|
|
|
|
private minColumnStarValue: number = 0;
|
|
private maxColumnStarValue: number = 0;
|
|
|
|
private minRowStarValue: number = 0;
|
|
private maxRowStarValue: number = 0;
|
|
|
|
measuredWidth: number = 0;
|
|
measuredHeight: number = 0;
|
|
|
|
private fakeRowAdded: boolean = false;
|
|
private fakeColumnAdded: boolean = false;
|
|
|
|
private singleRowGroup: ItemGroup;
|
|
private singleColumnGroup: ItemGroup;
|
|
|
|
constructor(grid: GridLayout) {
|
|
this.grid = grid;
|
|
this.singleRow = new ItemSpec();
|
|
this.singleColumn = new ItemSpec();
|
|
this.singleRowGroup = new ItemGroup(this.singleRow);
|
|
this.singleColumnGroup = new ItemGroup(this.singleColumn);
|
|
}
|
|
|
|
public setInfinityWidth(value: boolean): void {
|
|
this.infinityWidth = value;
|
|
for (let i = 0, size = this.columns.length; i < size; i++) {
|
|
let columnGroup: ItemGroup = this.columns[i];
|
|
columnGroup.setIsLengthInfinity(value);
|
|
}
|
|
}
|
|
|
|
public setInfinityHeight(value: boolean): void {
|
|
this.infinityHeight = value;
|
|
for (let i = 0, size = this.rows.length; i < size; i++) {
|
|
let rowGroup: ItemGroup = this.rows[i];
|
|
rowGroup.setIsLengthInfinity(value);
|
|
}
|
|
}
|
|
|
|
public addMeasureSpec(measureSpec: MeasureSpecs): void {
|
|
// Get column stats
|
|
let size = measureSpec.getColumnIndex() + measureSpec.getColumnSpan();
|
|
for (let i = measureSpec.getColumnIndex(); i < size; i++) {
|
|
let columnGroup: ItemGroup = this.columns[i];
|
|
if (columnGroup.getIsAuto()) {
|
|
measureSpec.autoColumnsCount++;
|
|
}
|
|
else if (columnGroup.getIsStar()) {
|
|
measureSpec.starColumnsCount += columnGroup.rowOrColumn.value;
|
|
}
|
|
else if (columnGroup.getIsAbsolute()) {
|
|
measureSpec.pixelWidth += layout.toDevicePixels(columnGroup.rowOrColumn.value);
|
|
}
|
|
}
|
|
|
|
if (measureSpec.autoColumnsCount > 0 && measureSpec.starColumnsCount === 0) {
|
|
// Determine which auto columns are affected by this element
|
|
for (let i = measureSpec.getColumnIndex(); i < size; i++) {
|
|
let columnGroup: ItemGroup = this.columns[i];
|
|
if (columnGroup.getIsAuto()) {
|
|
columnGroup.measureToFix++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get row stats
|
|
size = measureSpec.getRowIndex() + measureSpec.getRowSpan();
|
|
for (let i = measureSpec.getRowIndex(); i < size; i++) {
|
|
let rowGroup: ItemGroup = this.rows[i];
|
|
if (rowGroup.getIsAuto()) {
|
|
measureSpec.autoRowsCount++;
|
|
}
|
|
else if (rowGroup.getIsStar()) {
|
|
measureSpec.starRowsCount += rowGroup.rowOrColumn.value;
|
|
}
|
|
else if (rowGroup.getIsAbsolute()) {
|
|
measureSpec.pixelHeight += layout.toDevicePixels(rowGroup.rowOrColumn.value);
|
|
}
|
|
}
|
|
|
|
if (measureSpec.autoRowsCount > 0 && measureSpec.starRowsCount === 0) {
|
|
// Determine which auto rows are affected by this element
|
|
for (let i = measureSpec.getRowIndex(); i < size; i++) {
|
|
let rowGroup: ItemGroup = this.rows[i];
|
|
if (rowGroup.getIsAuto()) {
|
|
rowGroup.measureToFix++;
|
|
}
|
|
}
|
|
}
|
|
|
|
this.columns[measureSpec.getColumnIndex()].children.push(measureSpec);
|
|
this.rows[measureSpec.getRowIndex()].children.push(measureSpec);
|
|
}
|
|
|
|
public clearMeasureSpecs(): void {
|
|
for (let i = 0, size = this.columns.length; i < size; i++) {
|
|
this.columns[i].children.length = 0;
|
|
}
|
|
|
|
for (let i = 0, size = this.rows.length; i < size; i++) {
|
|
this.rows[i].children.length = 0;
|
|
}
|
|
}
|
|
|
|
private static initList(list: Array<ItemGroup>): void {
|
|
let density = layout.getDisplayDensity();
|
|
for (let i = 0, size = list.length; i < size; i++) {
|
|
let item: ItemGroup = list[i];
|
|
item.init(density);
|
|
}
|
|
}
|
|
|
|
init(): void {
|
|
|
|
let rows = this.rows.length;
|
|
if (rows === 0) {
|
|
this.singleRowGroup.setIsLengthInfinity(this.infinityHeight);
|
|
this.rows.push(this.singleRowGroup);
|
|
this.fakeRowAdded = true;
|
|
} else if (rows > 1 && this.fakeRowAdded) {
|
|
this.rows.splice(0, 1);
|
|
this.fakeRowAdded = false;
|
|
}
|
|
|
|
let cols = this.columns.length;
|
|
if (cols === 0) {
|
|
this.fakeColumnAdded = true;
|
|
this.singleColumnGroup.setIsLengthInfinity(this.infinityWidth);
|
|
this.columns.push(this.singleColumnGroup);
|
|
}
|
|
else if (cols > 1 && this.fakeColumnAdded) {
|
|
this.columns.splice(0, 1);
|
|
this.fakeColumnAdded = false;
|
|
}
|
|
|
|
MeasureHelper.initList(this.rows);
|
|
MeasureHelper.initList(this.columns);
|
|
|
|
this.minColumnStarValue = -1;
|
|
this.minRowStarValue = -1;
|
|
this.maxColumnStarValue = -1;
|
|
this.maxRowStarValue = -1;
|
|
}
|
|
|
|
private itemMeasured(measureSpec: MeasureSpecs, isFakeMeasure: boolean): void {
|
|
if (!isFakeMeasure) {
|
|
this.columns[measureSpec.getColumnIndex()].measuredCount++;
|
|
this.rows[measureSpec.getRowIndex()].measuredCount++;
|
|
measureSpec.measured = true;
|
|
}
|
|
|
|
if (measureSpec.autoColumnsCount > 0 && measureSpec.starColumnsCount === 0) {
|
|
let size = measureSpec.getColumnIndex() + measureSpec.getColumnSpan();
|
|
for (let i = measureSpec.getColumnIndex(); i < size; i++) {
|
|
let columnGroup: ItemGroup = this.columns[i];
|
|
if (columnGroup.getIsAuto()) {
|
|
columnGroup.currentMeasureToFixCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (measureSpec.autoRowsCount > 0 && measureSpec.starRowsCount === 0) {
|
|
let size = measureSpec.getRowIndex() + measureSpec.getRowSpan();
|
|
for (let i = measureSpec.getRowIndex(); i < size; i++) {
|
|
let rowGroup: ItemGroup = this.rows[i];
|
|
if (rowGroup.getIsAuto()) {
|
|
rowGroup.currentMeasureToFixCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fixColumns(): void {
|
|
let currentColumnWidth = 0;
|
|
let columnStarCount = 0;
|
|
|
|
let columnCount = this.columns.length;
|
|
for (let i = 0; i < columnCount; i++) {
|
|
let item: ItemGroup = this.columns[i];
|
|
if (item.rowOrColumn.isStar) {
|
|
columnStarCount += item.rowOrColumn.value;
|
|
}
|
|
else {
|
|
// Star columns are still zeros (not calculated).
|
|
currentColumnWidth += item.length;
|
|
}
|
|
}
|
|
|
|
let widthForStarColumns = Math.max(0, this.width - currentColumnWidth);
|
|
this.maxColumnStarValue = columnStarCount > 0 ? widthForStarColumns / columnStarCount : 0;
|
|
|
|
MeasureHelper.updateStarLength(this.columns, this.maxColumnStarValue);
|
|
}
|
|
|
|
private fixRows(): void {
|
|
let currentRowHeight = 0;
|
|
let rowStarCount = 0;
|
|
|
|
let rowCount = this.rows.length;
|
|
for (let i = 0; i < rowCount; i++) {
|
|
let item: ItemGroup = this.rows[i];
|
|
if (item.rowOrColumn.isStar) {
|
|
rowStarCount += item.rowOrColumn.value;
|
|
}
|
|
else {
|
|
// Star rows are still zeros (not calculated).
|
|
currentRowHeight += item.length;
|
|
}
|
|
}
|
|
|
|
let heightForStarRows = Math.max(0, this.height - currentRowHeight);
|
|
this.maxRowStarValue = rowStarCount > 0 ? heightForStarRows / rowStarCount : 0;
|
|
|
|
MeasureHelper.updateStarLength(this.rows, this.maxRowStarValue);
|
|
}
|
|
|
|
private static updateStarLength(list: Array<ItemGroup>, starValue: number): void {
|
|
let offset = 0;
|
|
let roundedOffset = 0;
|
|
for (let i = 0, rowCount = list.length; i < rowCount; i++) {
|
|
let item = list[i];
|
|
if (item.getIsStar()) {
|
|
offset += item.rowOrColumn.value * starValue;
|
|
|
|
let actualLength = offset - roundedOffset;
|
|
let roundedLength = Math.round(actualLength);
|
|
item.length = roundedLength;
|
|
roundedOffset += roundedLength;
|
|
}
|
|
}
|
|
}
|
|
|
|
private fakeMeasure(): void {
|
|
// Fake measure - measure all elements that have star rows and auto columns - with infinity Width and infinity Height
|
|
for (let i = 0, size = this.columns.length; i < size; i++) {
|
|
let columnGroup: ItemGroup = this.columns[i];
|
|
if (columnGroup.getAllMeasured()) {
|
|
continue;
|
|
}
|
|
|
|
for (let j = 0, childrenCount = columnGroup.children.length; j < childrenCount; j++) {
|
|
let measureSpec: MeasureSpecs = columnGroup.children[j];
|
|
if (measureSpec.starRowsCount > 0 && measureSpec.autoColumnsCount > 0 && measureSpec.starColumnsCount === 0) {
|
|
this.measureChild(measureSpec, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private measureFixedColumnsNoStarRows(): void {
|
|
for (let i = 0, size = this.columns.length; i < size; i++) {
|
|
let columnGroup: ItemGroup = this.columns[i];
|
|
for (let j = 0, childrenCount = columnGroup.children.length; j < childrenCount; j++) {
|
|
let measureSpec: MeasureSpecs = columnGroup.children[j];
|
|
if (measureSpec.starColumnsCount > 0 && measureSpec.starRowsCount === 0) {
|
|
this.measureChildFixedColumns(measureSpec);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private measureNoStarColumnsFixedRows(): void {
|
|
for (let i = 0, size = this.columns.length; i < size; i++) {
|
|
let columnGroup: ItemGroup = this.columns[i];
|
|
for (let j = 0, childrenCount = columnGroup.children.length; j < childrenCount; j++) {
|
|
let measureSpec: MeasureSpecs = columnGroup.children[j];
|
|
if (measureSpec.starRowsCount > 0 && measureSpec.starColumnsCount === 0) {
|
|
this.measureChildFixedRows(measureSpec);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static canFix(list: Array<ItemGroup>): boolean {
|
|
for (let i = 0, size = list.length; i < size; i++) {
|
|
let item: ItemGroup = list[i];
|
|
if (!item.getCanBeFixed()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static getMeasureLength(list: Array<ItemGroup>): number {
|
|
let result = 0;
|
|
for (let i = 0, size = list.length; i < size; i++) {
|
|
let item: ItemGroup = list[i];
|
|
result += item.length;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public measure(): void {
|
|
// Measure auto & pixel columns and rows (no spans).
|
|
let size = this.columns.length;
|
|
for (let i = 0; i < size; i++) {
|
|
let columnGroup: ItemGroup = this.columns[i];
|
|
for (let j = 0, childrenCount = columnGroup.children.length; j < childrenCount; j++) {
|
|
let measureSpec: MeasureSpecs = columnGroup.children[j];
|
|
if (measureSpec.getIsStar() || measureSpec.getSpanned()) {
|
|
continue;
|
|
}
|
|
|
|
this.measureChild(measureSpec, false);
|
|
}
|
|
}
|
|
|
|
// Measure auto & pixel columns and rows (with spans).
|
|
for (let i = 0; i < size; i++) {
|
|
let columnGroup: ItemGroup = this.columns[i];
|
|
for (let j = 0, childrenCount = columnGroup.children.length; j < childrenCount; j++) {
|
|
let measureSpec = columnGroup.children[j];
|
|
if (measureSpec.getIsStar() || !measureSpec.getSpanned()) {
|
|
continue;
|
|
}
|
|
|
|
this.measureChild(measureSpec, false);
|
|
}
|
|
}
|
|
|
|
// try fix stars!
|
|
let fixColumns: boolean = MeasureHelper.canFix(this.columns);
|
|
let fixRows: boolean = MeasureHelper.canFix(this.rows);
|
|
|
|
if (fixColumns) {
|
|
this.fixColumns();
|
|
}
|
|
|
|
if (fixRows) {
|
|
this.fixRows();
|
|
}
|
|
|
|
if (!fixColumns && !fixRows) {
|
|
// Fake measure - measure all elements that have star rows and auto columns - with infinity Width and infinity Height
|
|
// should be able to fix rows after that
|
|
this.fakeMeasure();
|
|
|
|
this.fixColumns();
|
|
|
|
// Measure all elements that have star columns(already fixed), but no stars at the rows
|
|
this.measureFixedColumnsNoStarRows();
|
|
|
|
this.fixRows();
|
|
}
|
|
else if (fixColumns && !fixRows) {
|
|
// Measure all elements that have star columns(already fixed) but no stars at the rows
|
|
this.measureFixedColumnsNoStarRows();
|
|
|
|
// Then
|
|
this.fixRows();
|
|
}
|
|
else if (!fixColumns && fixRows) {
|
|
// Measure all elements that have star rows(already fixed) but no star at the columns
|
|
this.measureNoStarColumnsFixedRows();
|
|
|
|
// Then
|
|
this.fixColumns();
|
|
}
|
|
|
|
// Rows and columns are fixed here - measure remaining elements
|
|
size = this.columns.length;
|
|
for (let i = 0; i < size; i++) {
|
|
let columnGroup: ItemGroup = this.columns[i];
|
|
for (let j = 0, childCount = columnGroup.children.length; j < childCount; j++) {
|
|
let measureSpec: MeasureSpecs = columnGroup.children[j];
|
|
if (!measureSpec.measured) {
|
|
this.measureChildFixedColumnsAndRows(measureSpec);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we are not stretched and minColumnStarValue is less than maxColumnStarValue
|
|
// we need to reduce the width of star columns.
|
|
if (!this.stretchedHorizontally && this.minColumnStarValue !== -1 && this.minColumnStarValue < this.maxColumnStarValue) {
|
|
MeasureHelper.updateStarLength(this.columns, this.minColumnStarValue);
|
|
}
|
|
|
|
// If we are not stretched and minRowStarValue is less than maxRowStarValue
|
|
// we need to reduce the height of star maxRowStarValue.
|
|
if (!this.stretchedVertically && this.minRowStarValue !== -1 && this.minRowStarValue < this.maxRowStarValue) {
|
|
MeasureHelper.updateStarLength(this.rows, this.minRowStarValue);
|
|
}
|
|
|
|
this.measuredWidth = MeasureHelper.getMeasureLength(this.columns);
|
|
this.measuredHeight = MeasureHelper.getMeasureLength(this.rows);
|
|
}
|
|
|
|
private measureChild(measureSpec: MeasureSpecs, isFakeMeasure: boolean): void {
|
|
let widthMeasureSpec = (measureSpec.autoColumnsCount > 0) ? this.infinity : layout.makeMeasureSpec(measureSpec.pixelWidth, layout.EXACTLY);
|
|
let heightMeasureSpec = (isFakeMeasure || measureSpec.autoRowsCount > 0) ? this.infinity : layout.makeMeasureSpec(measureSpec.pixelHeight, layout.EXACTLY);
|
|
|
|
let childSize = View.measureChild(this.grid, measureSpec.child, widthMeasureSpec, heightMeasureSpec);
|
|
let childMeasuredWidth: number = childSize.measuredWidth;
|
|
let childMeasuredHeight: number = childSize.measuredHeight;
|
|
|
|
let columnSpanEnd: number = measureSpec.getColumnIndex() + measureSpec.getColumnSpan();
|
|
let rowSpanEnd: number = measureSpec.getRowIndex() + measureSpec.getRowSpan();
|
|
|
|
if (measureSpec.autoColumnsCount > 0) {
|
|
let remainingSpace = childMeasuredWidth;
|
|
|
|
for (let i = measureSpec.getColumnIndex(); i < columnSpanEnd; i++) {
|
|
let columnGroup: ItemGroup = this.columns[i];
|
|
remainingSpace -= columnGroup.length;
|
|
}
|
|
|
|
if (remainingSpace > 0) {
|
|
let growSize = remainingSpace / measureSpec.autoColumnsCount;
|
|
for (let i = measureSpec.getColumnIndex(); i < columnSpanEnd; i++) {
|
|
let columnGroup: ItemGroup = this.columns[i];
|
|
if (columnGroup.getIsAuto()) {
|
|
columnGroup.length += growSize;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isFakeMeasure && measureSpec.autoRowsCount > 0) {
|
|
let remainingSpace: number = childMeasuredHeight;
|
|
|
|
for (let i = measureSpec.getRowIndex(); i < rowSpanEnd; i++) {
|
|
let rowGroup: ItemGroup = this.rows[i];
|
|
remainingSpace -= rowGroup.length;
|
|
}
|
|
|
|
if (remainingSpace > 0) {
|
|
let growSize = remainingSpace / measureSpec.autoRowsCount;
|
|
for (let i = measureSpec.getRowIndex(); i < rowSpanEnd; i++) {
|
|
let rowGroup: ItemGroup = this.rows[i];
|
|
if (rowGroup.getIsAuto()) {
|
|
rowGroup.length += growSize;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.itemMeasured(measureSpec, isFakeMeasure);
|
|
}
|
|
|
|
private measureChildFixedColumns(measureSpec: MeasureSpecs): void {
|
|
let columnIndex = measureSpec.getColumnIndex();
|
|
let columnSpanEnd = columnIndex + measureSpec.getColumnSpan();
|
|
let rowIndex = measureSpec.getRowIndex();
|
|
let rowSpanEnd = rowIndex + measureSpec.getRowSpan();
|
|
|
|
let measureWidth = 0;
|
|
let growSize = 0;
|
|
|
|
for (let i = columnIndex; i < columnSpanEnd; i++) {
|
|
let columnGroup: ItemGroup = this.columns[i];
|
|
measureWidth += columnGroup.length;
|
|
}
|
|
|
|
let widthMeasureSpec = layout.makeMeasureSpec(measureWidth, this.stretchedHorizontally ? layout.EXACTLY : layout.AT_MOST);
|
|
let heightMeasureSpec = (measureSpec.autoRowsCount > 0) ? this.infinity : layout.makeMeasureSpec(measureSpec.pixelHeight, layout.EXACTLY);
|
|
|
|
let childSize = View.measureChild(this.grid, measureSpec.child, widthMeasureSpec, heightMeasureSpec);
|
|
let childMeasuredWidth = childSize.measuredWidth;
|
|
let childMeasuredHeight = childSize.measuredHeight;
|
|
|
|
this.updateMinColumnStarValueIfNeeded(measureSpec, childMeasuredWidth);
|
|
|
|
// Distribute height over auto rows
|
|
if (measureSpec.autoRowsCount > 0) {
|
|
let remainingSpace = childMeasuredHeight;
|
|
|
|
for (let i = rowIndex; i < rowSpanEnd; i++) {
|
|
let rowGroup: ItemGroup = this.rows[i];
|
|
remainingSpace -= rowGroup.length;
|
|
}
|
|
|
|
if (remainingSpace > 0) {
|
|
growSize = remainingSpace / measureSpec.autoRowsCount;
|
|
for (let i = rowIndex; i < rowSpanEnd; i++) {
|
|
let rowGroup: ItemGroup = this.rows[i];
|
|
if (rowGroup.getIsAuto()) {
|
|
rowGroup.length += growSize;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.itemMeasured(measureSpec, false);
|
|
}
|
|
|
|
private measureChildFixedRows(measureSpec: MeasureSpecs): void {
|
|
let columnIndex = measureSpec.getColumnIndex();
|
|
let columnSpanEnd = columnIndex + measureSpec.getColumnSpan();
|
|
let rowIndex = measureSpec.getRowIndex();
|
|
let rowSpanEnd = rowIndex + measureSpec.getRowSpan();
|
|
let measureHeight = 0;
|
|
|
|
for (let i = rowIndex; i < rowSpanEnd; i++) {
|
|
let rowGroup: ItemGroup = this.rows[i];
|
|
measureHeight += rowGroup.length;
|
|
}
|
|
|
|
let widthMeasureSpec = (measureSpec.autoColumnsCount > 0) ? this.infinity : layout.makeMeasureSpec(measureSpec.pixelWidth, layout.EXACTLY);
|
|
let heightMeasureSpec = layout.makeMeasureSpec(measureHeight, this.stretchedVertically ? layout.EXACTLY : layout.AT_MOST);
|
|
|
|
let childSize = View.measureChild(this.grid, measureSpec.child, widthMeasureSpec, heightMeasureSpec);
|
|
let childMeasuredWidth = childSize.measuredWidth;
|
|
let childMeasuredHeight = childSize.measuredHeight;
|
|
|
|
let remainingSpace = 0;
|
|
let growSize = 0;
|
|
|
|
// Distribute width over auto columns
|
|
if (measureSpec.autoColumnsCount > 0) {
|
|
remainingSpace = childMeasuredWidth;
|
|
|
|
for (let i = columnIndex; i < columnSpanEnd; i++) {
|
|
let columnGroup: ItemGroup = this.columns[i];
|
|
remainingSpace -= columnGroup.length;
|
|
}
|
|
|
|
if (remainingSpace > 0) {
|
|
growSize = remainingSpace / measureSpec.autoColumnsCount;
|
|
for (let i = columnIndex; i < columnSpanEnd; i++) {
|
|
let columnGroup: ItemGroup = this.columns[i];
|
|
|
|
if (columnGroup.getIsAuto()) {
|
|
columnGroup.length += growSize;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.updateMinRowStarValueIfNeeded(measureSpec, childMeasuredHeight);
|
|
this.itemMeasured(measureSpec, false);
|
|
}
|
|
|
|
private measureChildFixedColumnsAndRows(measureSpec: MeasureSpecs): void {
|
|
let columnIndex = measureSpec.getColumnIndex();
|
|
let columnSpanEnd = columnIndex + measureSpec.getColumnSpan();
|
|
let rowIndex = measureSpec.getRowIndex();
|
|
let rowSpanEnd = rowIndex + measureSpec.getRowSpan();
|
|
|
|
let measureWidth = 0;
|
|
for (let i = columnIndex; i < columnSpanEnd; i++) {
|
|
let columnGroup: ItemGroup = this.columns[i];
|
|
measureWidth += columnGroup.length;
|
|
}
|
|
|
|
let measureHeight = 0;
|
|
for (let i = rowIndex; i < rowSpanEnd; i++) {
|
|
let rowGroup: ItemGroup = this.rows[i];
|
|
measureHeight += rowGroup.length;
|
|
}
|
|
|
|
// if (have stars) & (not stretch) - at most
|
|
let widthMeasureSpec = layout.makeMeasureSpec(measureWidth,
|
|
(measureSpec.starColumnsCount > 0 && !this.stretchedHorizontally) ? layout.AT_MOST : layout.EXACTLY);
|
|
|
|
let heightMeasureSpec = layout.makeMeasureSpec(measureHeight,
|
|
(measureSpec.starRowsCount > 0 && !this.stretchedVertically) ? layout.AT_MOST : layout.EXACTLY);
|
|
|
|
let childSize = View.measureChild(this.grid, measureSpec.child, widthMeasureSpec, heightMeasureSpec);
|
|
let childMeasuredWidth = childSize.measuredWidth;
|
|
let childMeasuredHeight = childSize.measuredHeight;
|
|
|
|
this.updateMinColumnStarValueIfNeeded(measureSpec, childMeasuredWidth);
|
|
this.updateMinRowStarValueIfNeeded(measureSpec, childMeasuredHeight);
|
|
this.itemMeasured(measureSpec, false);
|
|
}
|
|
|
|
private updateMinRowStarValueIfNeeded(measureSpec: MeasureSpecs, childMeasuredHeight: number): void {
|
|
if (!this.stretchedVertically && measureSpec.starRowsCount > 0) {
|
|
let remainingSpace = childMeasuredHeight;
|
|
let rowIndex = measureSpec.getRowIndex();
|
|
let rowSpanEnd = rowIndex + measureSpec.getRowSpan();
|
|
for (let i = rowIndex; i < rowSpanEnd; i++) {
|
|
let rowGroup = this.rows[i];
|
|
if (!rowGroup.getIsStar()) {
|
|
remainingSpace -= rowGroup.length;
|
|
}
|
|
}
|
|
|
|
if (remainingSpace > 0) {
|
|
this.minRowStarValue = Math.max(remainingSpace / measureSpec.starRowsCount, this.minRowStarValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
private updateMinColumnStarValueIfNeeded(measureSpec: MeasureSpecs, childMeasuredWidth: number): void {
|
|
// When not stretched star columns are not fixed so we may grow them here
|
|
// if there is an element that spans on multiple columns
|
|
if (!this.stretchedHorizontally && measureSpec.starColumnsCount > 0) {
|
|
let remainingSpace = childMeasuredWidth;
|
|
let columnIndex = measureSpec.getColumnIndex();
|
|
let columnSpanEnd = columnIndex + measureSpec.getColumnSpan();
|
|
for (let i = columnIndex; i < columnSpanEnd; i++) {
|
|
let columnGroup = this.columns[i];
|
|
if (!columnGroup.getIsStar()) {
|
|
remainingSpace -= columnGroup.length;
|
|
}
|
|
}
|
|
|
|
if (remainingSpace > 0) {
|
|
this.minColumnStarValue = Math.max(remainingSpace / measureSpec.starColumnsCount, this.minColumnStarValue);
|
|
}
|
|
}
|
|
}
|
|
} |