perf(android): gridlayout with less JNI calls (#10402)

This commit is contained in:
farfromrefuge
2024-07-01 18:20:14 +02:00
committed by GitHub
parent 7036f12b5c
commit 6dd441d6ba
8 changed files with 288 additions and 125 deletions

View File

@@ -16,15 +16,31 @@ class RemovalTrackingGridLayout extends GridLayout {
public removedRows = 0;
public removedCols = 0;
// this trick wont work on android anymore as removeRows
// is done in one call and does not go through _onRowRemoved
public _onRowRemoved(itemSpec: ItemSpec, index: number) {
this.removedRows++;
super._onRowRemoved(itemSpec, index);
}
// this trick wont work on android anymore as removeColumns
// is done in one call and does not go through _onColumnRemoved
public _onColumnRemoved(itemSpec: ItemSpec, index: number) {
this.removedCols++;
super._onColumnRemoved(itemSpec, index);
}
public removeRows() {
if (__ANDROID__) {
this.removedRows = this.getRows().length;
}
super.removeRows();
}
public removeColumns() {
if (__ANDROID__) {
this.removedCols = this.getColumns().length;
}
super.removeColumns();
}
}
export class GridLayoutTest extends testModule.UITest<RemovalTrackingGridLayout> {
@@ -39,7 +55,7 @@ export class GridLayoutTest extends testModule.UITest<RemovalTrackingGridLayout>
public test_item_recycling() {
helper.nativeView_recycling_test(
() => new Button(),
() => new GridLayout()
() => new GridLayout(),
);
}
@@ -350,6 +366,21 @@ export class GridLayoutTest extends testModule.UITest<RemovalTrackingGridLayout>
TKUnit.assertTrue(this.testView.getMeasuredWidth() < platform.Screen.mainScreen.widthPixels);
}
private getGridLayoutRowActualLength(view: GridLayout, index: number) {
if (__ANDROID__) {
return Math.round(layoutHelper.dp(view.nativeViewProtected.getRowActualLength(index)));
} else {
return view.getRows()[index].actualLength;
}
}
private getGridLayoutColumnActualLength(view: GridLayout, index: number) {
if (__ANDROID__) {
return Math.round(layoutHelper.dp(view.nativeViewProtected.getColumnActualLength(index)));
} else {
return view.getColumns()[index].actualLength;
}
}
public test_measuredWidth_when_not_stretched_two_columns() {
this.testView.horizontalAlignment = 'center';
this.testView.addColumn(new ItemSpec(layoutHelper.dp(80), 'pixel'));
@@ -363,9 +394,8 @@ export class GridLayoutTest extends testModule.UITest<RemovalTrackingGridLayout>
this.waitUntilTestElementLayoutIsValid();
var cols = this.testView.getColumns();
TKUnit.assertAreClose(cols[0].actualLength, Math.round(layoutHelper.dp(80)), DELTA, 'column 0');
TKUnit.assertAreClose(cols[1].actualLength, Math.round(layoutHelper.dp(20)), DELTA, 'column 1');
TKUnit.assertAreClose(this.getGridLayoutColumnActualLength(this.testView, 0), Math.round(layoutHelper.dp(80)), DELTA, 'column 0');
TKUnit.assertAreClose(this.getGridLayoutColumnActualLength(this.testView, 1), Math.round(layoutHelper.dp(20)), DELTA, 'column 1');
TKUnit.assertAreClose(this.testView.getMeasuredWidth(), 100, DELTA, 'measured width');
}
@@ -392,9 +422,9 @@ export class GridLayoutTest extends testModule.UITest<RemovalTrackingGridLayout>
this.waitUntilTestElementLayoutIsValid();
var cols = this.testView.getColumns();
TKUnit.assertAreClose(cols[0].actualLength, Math.round(layoutHelper.dp(80)), DELTA, 'column 0');
TKUnit.assertAreClose(cols[1].actualLength, Math.round(layoutHelper.dp(40)), DELTA, 'column 1');
TKUnit.assertAreClose(cols[2].actualLength, Math.round(layoutHelper.dp(60)), DELTA, 'column 2');
TKUnit.assertAreClose(this.getGridLayoutColumnActualLength(this.testView, 0), Math.round(layoutHelper.dp(80)), DELTA, 'column 0');
TKUnit.assertAreClose(this.getGridLayoutColumnActualLength(this.testView, 1), Math.round(layoutHelper.dp(40)), DELTA, 'column 1');
TKUnit.assertAreClose(this.getGridLayoutColumnActualLength(this.testView, 2), Math.round(layoutHelper.dp(60)), DELTA, 'column 2');
TKUnit.assertAreClose(this.testView.getMeasuredWidth(), 180, DELTA, 'measured width');
}
@@ -484,7 +514,7 @@ export class GridLayoutTest extends testModule.UITest<RemovalTrackingGridLayout>
for (var c = 0; c < 4; c++) {
var btn = <layoutHelper.MyButton>this.testView.getChildAt(i++);
if (cols[c].isAbsolute) {
width += layoutHelper.dip(cols[c].actualLength);
width += layoutHelper.dip(this.getGridLayoutColumnActualLength(this.testView, c));
} else {
width += btn.getMeasuredWidth();
}
@@ -495,7 +525,7 @@ export class GridLayoutTest extends testModule.UITest<RemovalTrackingGridLayout>
maxWidth = Math.max(maxWidth, width);
if (rows[r].isAbsolute) {
maxHeight += layoutHelper.dip(rows[r].actualLength);
maxHeight += layoutHelper.dip(this.getGridLayoutRowActualLength(this.testView, r));
} else {
maxHeight += height;
}
@@ -510,21 +540,19 @@ export class GridLayoutTest extends testModule.UITest<RemovalTrackingGridLayout>
public test_columnsActualWidth_isCorrect() {
this.prepareGridLayout(true);
var cols = this.testView.getColumns();
TKUnit.assertEqual(cols[0].actualLength, Math.round(layoutHelper.dp(50)), 'Star column should be 50px width');
TKUnit.assertEqual(cols[1].actualLength, Math.round(layoutHelper.dp(100)), '2*Star column should be 100px width');
TKUnit.assertEqual(cols[2].actualLength, Math.round(layoutHelper.dp(50)), 'Absolute column should be 50px width');
TKUnit.assertEqual(cols[3].actualLength, Math.round(layoutHelper.dp(100)), 'Auto column should be 100px width');
TKUnit.assertEqual(this.getGridLayoutColumnActualLength(this.testView, 0), Math.round(layoutHelper.dp(50)), 'Star column should be 50px width');
TKUnit.assertEqual(this.getGridLayoutColumnActualLength(this.testView, 1), Math.round(layoutHelper.dp(100)), '2*Star column should be 100px width');
TKUnit.assertEqual(this.getGridLayoutColumnActualLength(this.testView, 2), Math.round(layoutHelper.dp(50)), 'Absolute column should be 50px width');
TKUnit.assertEqual(this.getGridLayoutColumnActualLength(this.testView, 3), Math.round(layoutHelper.dp(100)), 'Auto column should be 100px width');
}
public test_rowsActualHeight_isCorrect() {
this.prepareGridLayout(true);
var rows = this.testView.getRows();
TKUnit.assertEqual(rows[0].actualLength, Math.round(layoutHelper.dp(50)), 'Star row should be 50px width');
TKUnit.assertEqual(rows[1].actualLength, Math.round(layoutHelper.dp(100)), '2*Star row should be 100px width');
TKUnit.assertEqual(rows[2].actualLength, Math.round(layoutHelper.dp(50)), 'Absolute row should be 50px width');
TKUnit.assertEqual(rows[3].actualLength, Math.round(layoutHelper.dp(100)), 'Auto row should be 100px width');
TKUnit.assertEqual(this.getGridLayoutRowActualLength(this.testView, 0), Math.round(layoutHelper.dp(50)), 'Star row should be 50px width');
TKUnit.assertEqual(this.getGridLayoutRowActualLength(this.testView, 1), Math.round(layoutHelper.dp(100)), '2*Star row should be 100px width');
TKUnit.assertEqual(this.getGridLayoutRowActualLength(this.testView, 2), Math.round(layoutHelper.dp(50)), 'Absolute row should be 50px width');
TKUnit.assertEqual(this.getGridLayoutRowActualLength(this.testView, 3), Math.round(layoutHelper.dp(100)), 'Auto row should be 100px width');
}
public test_Measure_and_Layout_Children_withCorrect_size() {
@@ -575,12 +603,10 @@ export class GridLayoutTest extends testModule.UITest<RemovalTrackingGridLayout>
this.waitUntilTestElementLayoutIsValid();
var cols = this.testView.getColumns();
TKUnit.assertAreClose(cols[0].actualLength, Math.round(layoutHelper.dp(28)), DELTA, 'Column[0] actual length should be 28');
TKUnit.assertAreClose(cols[1].actualLength, Math.round(layoutHelper.dp(27)), DELTA, 'Column[1] actual length should be 27');
TKUnit.assertAreClose(cols[2].actualLength, Math.round(layoutHelper.dp(28)), DELTA, 'Column[2] actual length should be 28');
TKUnit.assertAreClose(cols[3].actualLength, Math.round(layoutHelper.dp(27)), DELTA, 'Column[3] actual length should be 27');
TKUnit.assertAreClose(this.getGridLayoutColumnActualLength(this.testView, 0), Math.round(layoutHelper.dp(28)), DELTA, 'Column[0] actual length should be 28');
TKUnit.assertAreClose(this.getGridLayoutColumnActualLength(this.testView, 1), Math.round(layoutHelper.dp(27)), DELTA, 'Column[1] actual length should be 27');
TKUnit.assertAreClose(this.getGridLayoutColumnActualLength(this.testView, 2), Math.round(layoutHelper.dp(28)), DELTA, 'Column[2] actual length should be 28');
TKUnit.assertAreClose(this.getGridLayoutColumnActualLength(this.testView, 3), Math.round(layoutHelper.dp(27)), DELTA, 'Column[3] actual length should be 27');
}
public test_margins_and_verticalAlignment_center() {
@@ -799,15 +825,13 @@ export class GridLayoutTest extends testModule.UITest<RemovalTrackingGridLayout>
GridLayout.setRowSpan(btn, 3);
this.waitUntilTestElementLayoutIsValid();
var cols = grid.getColumns();
TKUnit.assertAreClose(cols[0].actualLength, layoutHelper.dp(33), DELTA, 'Column[0] actual length should be 67');
TKUnit.assertAreClose(cols[1].actualLength, layoutHelper.dp(100), DELTA, 'Column[1] actual length should be 100');
TKUnit.assertAreClose(cols[2].actualLength, layoutHelper.dp(67), DELTA, 'Column[2] actual length should be 133');
TKUnit.assertAreClose(this.getGridLayoutColumnActualLength(grid, 0), layoutHelper.dp(33), DELTA, 'Column[0] actual length should be 67');
TKUnit.assertAreClose(this.getGridLayoutColumnActualLength(grid, 1), layoutHelper.dp(100), DELTA, 'Column[1] actual length should be 100');
TKUnit.assertAreClose(this.getGridLayoutColumnActualLength(grid, 2), layoutHelper.dp(67), DELTA, 'Column[2] actual length should be 133');
var rows = grid.getRows();
TKUnit.assertAreClose(rows[0].actualLength, layoutHelper.dp(67), DELTA, 'Row[0] actual length should be 133');
TKUnit.assertAreClose(rows[1].actualLength, layoutHelper.dp(100), DELTA, 'Row[1] actual length should be 100');
TKUnit.assertAreClose(rows[2].actualLength, layoutHelper.dp(133), DELTA, 'Row[2] actual length should be 267');
TKUnit.assertAreClose(this.getGridLayoutRowActualLength(grid, 0), layoutHelper.dp(67), DELTA, 'Row[0] actual length should be 133');
TKUnit.assertAreClose(this.getGridLayoutRowActualLength(grid, 1), layoutHelper.dp(100), DELTA, 'Row[1] actual length should be 100');
TKUnit.assertAreClose(this.getGridLayoutRowActualLength(grid, 2), layoutHelper.dp(133), DELTA, 'Row[2] actual length should be 267');
}
}

View File

Binary file not shown.

View File

@@ -63,20 +63,23 @@ function convertGridLength(value: string): ItemSpec {
}
}
function parseAndAddItemSpecs(value: string, func: (itemSpec: ItemSpec) => void): void {
function parseAndAddItemSpecs(value: string) {
// ensure value is a string since view bindings could be parsed as number/int's here
const specs: ItemSpec[] = [];
const arr = `${value}`.split(/[\s,]+/);
for (let i = 0, length = arr.length; i < length; i++) {
const str = arr[i].trim();
if (str.length > 0) {
func(convertGridLength(arr[i].trim()));
specs.push(convertGridLength(arr[i].trim()));
}
}
return specs;
}
export class ItemSpec extends Observable implements ItemSpecDefinition {
private _value: number;
private _unitType: GridUnitType;
toJSON?: () => any;
constructor(...args) {
super();
@@ -147,8 +150,8 @@ export class ItemSpec extends Observable implements ItemSpecDefinition {
@CSSType('GridLayout')
export class GridLayoutBase extends LayoutBase implements GridLayoutDefinition {
private _rows: Array<ItemSpec> = new Array<ItemSpec>();
private _cols: Array<ItemSpec> = new Array<ItemSpec>();
protected _rows: Array<ItemSpec> = new Array<ItemSpec>();
protected _cols: Array<ItemSpec> = new Array<ItemSpec>();
public static getColumn(element: View): number {
return validateArgs(element).col;
@@ -182,22 +185,48 @@ export class GridLayoutBase extends LayoutBase implements GridLayoutDefinition {
validateArgs(element).rowSpan = value;
}
public addRow(itemSpec: ItemSpec) {
public _addRow(itemSpec: ItemSpec) {
validateItemSpec(itemSpec);
itemSpec.owner = this;
this._rows.push(itemSpec);
}
public addRow(itemSpec: ItemSpec) {
this._addRow(itemSpec);
this._onRowAdded(itemSpec);
this.invalidate();
}
public addColumn(itemSpec: ItemSpec) {
public addRows(itemSpecs: ItemSpec[]) {
for (let index = 0; index < itemSpecs.length; index++) {
const itemSpec = itemSpecs[index];
this._addRow(itemSpec);
this._onRowAdded(itemSpec);
}
this.invalidate();
}
public _addColumn(itemSpec: ItemSpec) {
validateItemSpec(itemSpec);
itemSpec.owner = this;
this._cols.push(itemSpec);
}
public addColumn(itemSpec: ItemSpec) {
this._addColumn(itemSpec);
this._onColumnAdded(itemSpec);
this.invalidate();
}
public addColumns(itemSpecs: ItemSpec[]) {
for (let index = 0; index < itemSpecs.length; index++) {
const itemSpec = itemSpecs[index];
this._addColumn(itemSpec);
this._onColumnAdded(itemSpec);
}
this.invalidate();
}
public addChildAtCell(view: View, row: number, column: number, rowSpan?: number, columnSpan?: number): void {
this.addChild(view);
GridLayoutBase.setRow(view, row);
@@ -316,12 +345,14 @@ export class GridLayoutBase extends LayoutBase implements GridLayoutDefinition {
set rows(value: string) {
this.removeRows();
parseAndAddItemSpecs(value, (spec: ItemSpec) => this.addRow(spec));
const specs = parseAndAddItemSpecs(value);
this.addRows(specs);
}
set columns(value: string) {
this.removeColumns();
parseAndAddItemSpecs(value, (spec: ItemSpec) => this.addColumn(spec));
const specs = parseAndAddItemSpecs(value);
this.addColumns(specs);
}
}

View File

@@ -20,32 +20,26 @@ View.prototype[columnProperty.setNative] = makeNativeSetter<number>((lp, value)
View.prototype[rowSpanProperty.setNative] = makeNativeSetter<number>((lp, value) => (lp.rowSpan = value));
View.prototype[columnSpanProperty.setNative] = makeNativeSetter<number>((lp, value) => (lp.columnSpan = value));
function createNativeSpec(itemSpec: ItemSpec): org.nativescript.widgets.ItemSpec {
switch (itemSpec.gridUnitType) {
ItemSpecBase.prototype.toJSON = function () {
let result;
switch (this.gridUnitType) {
case GridUnitType.AUTO:
return new org.nativescript.widgets.ItemSpec(itemSpec.value, org.nativescript.widgets.GridUnitType.auto);
case GridUnitType.STAR:
return new org.nativescript.widgets.ItemSpec(itemSpec.value, org.nativescript.widgets.GridUnitType.star);
result = { type: 0 /* org.nativescript.widgets.GridUnitType.auto */, value: this.value };
break;
case GridUnitType.PIXEL:
return new org.nativescript.widgets.ItemSpec(itemSpec.value * layout.getDisplayDensity(), org.nativescript.widgets.GridUnitType.pixel);
result = { type: 1 /* org.nativescript.widgets.GridUnitType.pixel */, value: this.value * layout.getDisplayDensity() };
break;
case GridUnitType.STAR:
result = { type: 2 /* org.nativescript.widgets.GridUnitType.star */, value: this.value };
break;
default:
throw new Error('Invalid gridUnitType: ' + itemSpec.gridUnitType);
return null;
}
}
return result;
};
export class ItemSpec extends ItemSpecBase {
nativeSpec: org.nativescript.widgets.ItemSpec;
public get actualLength(): number {
if (this.nativeSpec) {
return Math.round(this.nativeSpec.getActualLength() / layout.getDisplayDensity());
}
return 0;
}
interface ItemSpec extends ItemSpecBase {
toJSON(): { value: number; type: number };
}
export class GridLayout extends GridLayoutBase {
@@ -58,54 +52,86 @@ export class GridLayout extends GridLayoutBase {
public initNativeView(): void {
super.initNativeView();
// Update native GridLayout
this.rowsInternal.forEach((itemSpec: ItemSpec, index, rows) => {
this._onRowAdded(itemSpec);
}, this);
this.columnsInternal.forEach((itemSpec: ItemSpec, index, rows) => {
this._onColumnAdded(itemSpec);
}, this);
const jsonRows = JSON.stringify(this.rowsInternal.map((itemSpec: ItemSpec) => itemSpec.toJSON()).filter((j) => !!j));
const jsonColumns = JSON.stringify(this.columnsInternal.map((itemSpec: ItemSpec) => itemSpec.toJSON()).filter((j) => !!j));
this.nativeViewProtected.addRowsAndColumnsFromJSON(jsonRows, jsonColumns);
}
public resetNativeView() {
// Update native GridLayout
for (let i = this.rowsInternal.length; i--; i >= 0) {
const itemSpec = <ItemSpec>this.rowsInternal[i];
this._onRowRemoved(itemSpec, i);
}
for (let i = this.columnsInternal.length; i--; i >= 0) {
const itemSpec = <ItemSpec>this.columnsInternal[i];
this._onColumnRemoved(itemSpec, i);
}
this.nativeViewProtected.reset();
super.resetNativeView();
}
public _onRowAdded(itemSpec: ItemSpec) {
if (this.nativeViewProtected) {
const nativeSpec = createNativeSpec(itemSpec);
itemSpec.nativeSpec = nativeSpec;
this.nativeViewProtected.addRow(nativeSpec);
this.nativeViewProtected.addRowsFromJSON(JSON.stringify([itemSpec.toJSON()]));
}
}
public addRows(itemSpecs: ItemSpec[]) {
const jsonArray = [];
const nativeView = this.nativeViewProtected;
const initialized = !!nativeView;
for (let index = 0; index < itemSpecs.length; index++) {
const itemSpec = itemSpecs[index];
this._addRow(itemSpec);
if (initialized) {
jsonArray.push(itemSpec.toJSON());
}
}
if (initialized) {
nativeView.addRowsFromJSON(JSON.stringify(jsonArray.filter((s) => !!s)));
}
}
public addColumns(itemSpecs: ItemSpec[]) {
const jsonArray = [];
const nativeView = this.nativeViewProtected;
const initialized = !!nativeView;
for (let index = 0; index < itemSpecs.length; index++) {
const itemSpec = itemSpecs[index];
this._addColumn(itemSpec);
if (initialized) {
jsonArray.push(itemSpec.toJSON());
}
}
if (initialized) {
nativeView.addColumnsFromJSON(JSON.stringify(jsonArray.filter((s) => !!s)));
}
}
public _onColumnAdded(itemSpec: ItemSpec) {
if (this.nativeViewProtected) {
const nativeSpec = createNativeSpec(itemSpec);
itemSpec.nativeSpec = nativeSpec;
this.nativeViewProtected.addColumn(nativeSpec);
this.nativeViewProtected.addColumnsFromJSON(JSON.stringify([itemSpec.toJSON()]));
}
}
public removeColumns() {
if (this._cols.length) {
if (this.nativeViewProtected) {
this.nativeViewProtected.clearColumns();
}
this._cols.length = 0;
}
}
public removeRows() {
if (this._rows.length) {
if (this.nativeViewProtected) {
this.nativeViewProtected.clearRows();
}
this._rows.length = 0;
}
}
public _onRowRemoved(itemSpec: ItemSpec, index: number) {
itemSpec.nativeSpec = null;
if (this.nativeViewProtected) {
this.nativeViewProtected.removeRowAt(index);
}
}
public _onColumnRemoved(itemSpec: ItemSpec, index: number) {
itemSpec.nativeSpec = null;
if (this.nativeViewProtected) {
this.nativeViewProtected.removeColumnAt(index);
}

View File

@@ -19,9 +19,13 @@ export class Page extends PageBase {
public createNativeView() {
const layout = new org.nativescript.widgets.GridLayout(this._context);
layout.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.auto));
layout.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.star));
layout.addRowsFromJSON(
JSON.stringify([
{ value: 1, type: 0 /* org.nativescript.widgets.GridUnitType.auto */ },
{ value: 1, type: 2 /* org.nativescript.widgets.GridUnitType.star */ },
])
);
console.log('Page', 'createNativeView');
return layout;
}

View File

@@ -490,18 +490,24 @@ export class TabView extends TabViewBase {
lp.row = 1;
if (this.androidTabsPosition === 'top') {
nativeView.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.auto));
nativeView.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.star));
nativeView.addRowsFromJSON(
JSON.stringify([
{ value: 1, type: 0 /* org.nativescript.widgets.GridUnitType.auto */ },
{ value: 1, type: 2 /* org.nativescript.widgets.GridUnitType.star */ },
])
);
viewPager.setLayoutParams(lp);
if (!this.androidSwipeEnabled) {
viewPager.setSwipePageEnabled(false);
}
} else {
nativeView.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.star));
nativeView.addRow(new org.nativescript.widgets.ItemSpec(1, org.nativescript.widgets.GridUnitType.auto));
nativeView.addRowsFromJSON(
JSON.stringify([
{ value: 1, type: 2 /* org.nativescript.widgets.GridUnitType.star */ },
{ value: 1, type: 0 /* org.nativescript.widgets.GridUnitType.auto */ },
])
);
tabLayout.setLayoutParams(lp);
viewPager.setSwipePageEnabled(false);
// set completely transparent accent color for tab selected indicator.

View File

@@ -292,17 +292,24 @@
export class GridLayout extends LayoutBase {
constructor(context: android.content.Context);
public addRow(itemSpec: ItemSpec): void;
public addColumn(itemSpec: ItemSpec): void;
public removeRow(itemSpec: ItemSpec): void;
public removeColumn(itemSpec: ItemSpec): void;
public addRow(value: number, type: org.nativescript.widgets.GridUnitType): void;
public addColumn(value: number, type: org.nativescript.widgets.GridUnitType): void;
public removeRowAt(index: number): void;
public removeColumnAt(index: number): void;
public getColumns(): Array<ItemSpec>;
public getRows(): Array<ItemSpec>;
public clearRows();
public clearColumns();
public reset();
public addRowsFromJSON(value: string);
public addColumnsFromJSON(value: string);
public addRowsAndColumnsFromJSON(rows: string, columns: string);
public getRowActualLength(index: number): number; // for testing purpose
public getColumnActualLength(index: number): number; // for testing purpose
}
export class FlexboxLayout extends LayoutBase {

View File

@@ -1,11 +1,16 @@
package org.nativescript.widgets;
import android.content.Context;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.View.MeasureSpec;
import android.util.AttributeSet;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
@@ -13,6 +18,7 @@ import java.util.HashMap;
* @author hhristov
*/
public class GridLayout extends LayoutBase {
protected final static String TAG = "GridLayout";
private final MeasureHelper helper = new MeasureHelper(this);
@@ -31,6 +37,14 @@ public class GridLayout extends LayoutBase {
public GridLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public GridLayout(Context context, String rows) {
this(context);
this.addRowsFromJSON(rows);
}
public GridLayout(Context context, String rows, String columns) {
this(context, rows);
this.addColumnsFromJSON(rows);
}
private static void validateItemSpec(ItemSpec itemSpec) {
if (itemSpec == null) {
@@ -53,6 +67,10 @@ public class GridLayout extends LayoutBase {
this.requestLayout();
}
public void addRow(int value, GridUnitType type) {
addRow(new ItemSpec(value, type));
}
public void addColumn(ItemSpec itemSpec) {
validateItemSpec(itemSpec);
itemSpec.owner = this;
@@ -64,19 +82,46 @@ public class GridLayout extends LayoutBase {
this.requestLayout();
}
public void removeColumn(ItemSpec itemSpec) {
if (itemSpec == null) {
throw new Error("itemSpec is null.");
}
int index = this._cols.indexOf(itemSpec);
if (itemSpec.owner != this || index < 0) {
throw new Error("itemSpec is not child of this GridLayout");
}
this.removeColumnAt(index);
public void addColumn(int value, GridUnitType type) {
addColumn(new ItemSpec(value, type));
}
public void addRowsFromJSON(String value) {
try {
if (value == null) {
return;
}
JSONArray rows = new JSONArray(value);
for (int i = 0; i < rows.length() ; i++) {
JSONObject row = rows.getJSONObject(i);
addRow(row.getInt("value"), GridUnitType.values()[row.getInt("type")]);
}
} catch (JSONException exception) {
Log.e(TAG, "Caught JSONException...");
exception.printStackTrace();
}
}
public void addColumnsFromJSON(String value) {
try {
if (value == null) {
return;
}
JSONArray columns = new JSONArray(value);
for (int i = 0; i < columns.length() ; i++) {
JSONObject column = columns.getJSONObject(i);
addColumn(column.getInt("value"), GridUnitType.values()[column.getInt("type")]);
}
} catch (JSONException exception) {
Log.e(TAG, "Caught JSONException...");
exception.printStackTrace();
}
}
public void addRowsAndColumnsFromJSON(String rowsString, String jsonString) {
addRowsFromJSON(rowsString);
addColumnsFromJSON(jsonString);
}
public void removeColumnAt(int index) {
this._cols.remove(index);
this.helper.columns.get(index).children.clear();
@@ -84,19 +129,6 @@ public class GridLayout extends LayoutBase {
this.requestLayout();
}
public void removeRow(ItemSpec itemSpec) {
if (itemSpec == null) {
throw new Error("itemSpec is null.");
}
int index = this._rows.indexOf(itemSpec);
if (itemSpec.owner != this || index < 0) {
throw new Error("itemSpec is not child of this GridLayout");
}
this.removeRowAt(index);
}
public void removeRowAt(int index) {
this._rows.remove(index);
this.helper.rows.get(index).children.clear();
@@ -116,6 +148,30 @@ public class GridLayout extends LayoutBase {
return copy;
}
public void reset() {
clearRows();
clearColumns();
}
public void clearRows() {
this._rows.clear();
for (int i = 0; i < this.helper.rows.size(); i++) {
this.helper.rows.get(i).children.clear();
}
this.helper.rows.clear();
this.requestLayout();
}
public void clearColumns() {
this._cols.clear();
for (int i = 0; i < this.helper.columns.size(); i++) {
this.helper.columns.get(i).children.clear();
}
this.helper.columns.clear();
this.requestLayout();
}
@Override
public void addView(View child) {
super.addView(child);
@@ -235,6 +291,16 @@ public class GridLayout extends LayoutBase {
}
}
/* only used for N tests */
public float getRowActualLength(int index) {
return this.helper.rows.get(index).rowOrColumn._actualLength;
}
/* only used for N tests */
public float getColumnActualLength(int index) {
return this.helper.columns.get(index).rowOrColumn._actualLength;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
CommonLayoutParams.adjustChildrenLayoutParams(this, widthMeasureSpec, heightMeasureSpec);
@@ -823,7 +889,6 @@ class MeasureHelper {
this.measureChild(measureSpec, false);
}
}
// try fix stars!
boolean fixColumns = canFix(this.columns);
boolean fixRows = canFix(this.rows);