Disable recycling, refactoring & fixes (#4705)

* Added tests for native view recycling
Disabled android native view recycling
Move toString from view-common to view-base
Fix crash on application restore and navigation back on API26
Added setAsRootView method
Added missing logo into perf-tests/recycling app

* additional fix for image-source-tests. ios is case sensitive.

* Add @private to some internal properties
Fix where padding is not respected when background is reset.
This commit is contained in:
Hristo Hristov
2017-08-17 09:15:35 +03:00
committed by GitHub
parent 2701ea3c1e
commit bba7a82bdf
18 changed files with 532 additions and 251 deletions

BIN
apps/app/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -1,6 +1,8 @@
import { StackLayout } from "tns-core-modules/ui/layouts/stack-layout";
import { TextView } from "tns-core-modules/ui/text-view";
import { Button } from "tns-core-modules/ui/button";
import * as platform from "tns-core-modules/platform";
import * as application from "tns-core-modules/application";
import * as tests from "./tests";
@ -13,6 +15,28 @@ function getStack(stack: StackLayout): StackLayout {
return p;
}
export function navigatingTo(args) {
// Request permission to write test-results.xml file for API >= 23
if (platform.isAndroid && parseInt(platform.device.sdkVersion) >= 23) {
let handler = (args: application.AndroidActivityRequestPermissionsEventData) => {
application.android.off(application.AndroidApplication.activityRequestPermissionsEvent, handler);
if (args.requestCode === 1234 && args.grantResults.length > 0 && args.grantResults[0] === android.content.pm.PackageManager.PERMISSION_GRANTED) {
console.log("Permission for write to external storage GRANTED!")
} else {
console.log("Permission for write to external storage not granted!");
}
};
application.android.on(application.AndroidApplication.activityRequestPermissionsEvent, handler);
if ((<any>android.support.v4.content.ContextCompat).checkSelfPermission(application.android.currentContext, (<any>android).Manifest.permission.WRITE_EXTERNAL_STORAGE) !== android.content.pm.PackageManager.PERMISSION_GRANTED) {
(<any>android.support.v4.app.ActivityCompat).requestPermissions(application.android.currentContext, [(<any>android).Manifest.permission.WRITE_EXTERNAL_STORAGE], 1234);
}
} else {
console.log("Permission for write to external storage GRANTED!")
}
}
export function onNavigatingFrom() {
clearInterval(runner);
}
@ -39,7 +63,30 @@ export function onTap(args) {
track(text);
let tasks = [
() => track(tests.testAll(p)),
() => track(tests.testSetup(p)),
() => track(tests.testFlexboxLayout(p)),
() => track(tests.testDockLayout(p)),
() => track(tests.testGridLayout(p)),
() => track(tests.testStackLayout(p)),
() => track(tests.testWrapLayout(p)),
() => track(tests.testAbsoluteLayout(p)),
() => track(tests.testButton(p)),
() => track(tests.testActionBar(p)),
() => track(tests.testActivityIndicator(p)),
() => track(tests.testBorder(p)),
() => track(tests.testContentView(p)),
() => track(tests.testDatePicker(p)),
() => track(tests.testHtmlView(p)),
() => track(tests.testImage(p)),
() => track(tests.testLabel(p)),
() => track(tests.testListPicker(p)),
() => track(tests.testListView(p)),
() => track(tests.testPage(p)),
() => track(tests.testProgress(p)),
() => track(tests.testRepeater(p)),
() => track(tests.testSwitch(p)),
() => track(tests.testTextField(p)),
() => track(tests.testTextView(p)),
() => track("Complete!")
];
let i = 0;

View File

@ -1,7 +1,9 @@
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingFrom="onNavigatingFrom">
<GridLayout rows="30,100,*">
<Button text="Start test..." tap="onTap" style="font-size:8" />
<Page xmlns="http://schemas.nativescript.org/tns.xsd"
navigatingFrom="onNavigatingFrom"
navigatingTo="navigatingTo">
<StackLayout>
<Button text="Start test..." tap="onTap" style="font-size:11" />
<StackLayout id="placeholder" row="1"/>
<TextView id="result" row="2" />
</GridLayout>
<TextView id="result" row="2" editable="false" />
</StackLayout>
</Page>

View File

@ -26,42 +26,107 @@ import { TextField } from 'tns-core-modules/ui/text-field';
import { TextView } from 'tns-core-modules/ui/text-view';
import { TimePicker } from 'tns-core-modules/ui/time-picker';
import { View } from 'tns-core-modules/ui/core/view';
import { FormattedString, Span } from "tns-core-modules/text/formatted-string";
import { FormattedString, Span } from 'tns-core-modules/text/formatted-string';
import { _getProperties, _getStyleProperties } from 'tns-core-modules/ui/core/properties';
declare var __startCPUProfiler;
declare var __stopCPUProfiler;
export function testAll(layout: StackLayout): string {
const count = 200;
export function testSetup(layout: StackLayout): string {
setupSetters();
const count = 100;
let result = '';
// result += test(layout, () => new FlexboxLayout(), count);
// result += test(layout, () => new ActionBar(), count);
// result += test(layout, () => new ActivityIndicator(), count);
// result += test(layout, () => new Border(), count);
result += test(layout, () => new Button(), count);
// result += test(layout, () => new ContentView(), count);
// result += test(layout, () => new DatePicker(), count);
// result += test(layout, () => new HtmlView(), count);
// result += test(layout, () => new Image(), count);
// result += test(layout, () => new Label(), count);
// result += test(layout, () => new AbsoluteLayout(), count);
// result += test(layout, () => new DockLayout(), count);
// result += test(layout, () => new GridLayout(), count);
// result += test(layout, () => new StackLayout(), count);
// result += test(layout, () => new WrapLayout(), count);
// result += test(layout, () => new ListPicker(), count);
// result += test(layout, () => new ListView(), count);
// result += test(layout, () => new Page(), count);
// result += test(layout, () => new Progress(), count);
// result += test(layout, () => new Repeater(), count);
// result += test(layout, () => new Switch(), count);
// result += test(layout, () => new TextField(), count);
// result += test(layout, () => new TextView(), count);
return '';
}
// Throws
// result += test(layout, () => new TabView(), count);
// result += test(layout, () => new SegmentedBar(), count);
// result += test(layout, () => new TimePicker(), count);
export function testFlexboxLayout(layout: StackLayout): string {
return test(layout, () => new FlexboxLayout(), count);
}
return result;
export function testDockLayout(layout: StackLayout): string {
return test(layout, () => new DockLayout(), count);
}
export function testGridLayout(layout: StackLayout): string {
return test(layout, () => new GridLayout(), count);
}
export function testStackLayout(layout: StackLayout): string {
return test(layout, () => new StackLayout(), count);
}
export function testWrapLayout(layout: StackLayout): string {
return test(layout, () => new WrapLayout(), count);
}
export function testAbsoluteLayout(layout: StackLayout): string {
return test(layout, () => new AbsoluteLayout(), count);
}
export function testButton(layout: StackLayout): string {
return test(layout, () => new Button(), count);
}
export function testActionBar(layout: StackLayout): string {
return test(layout, () => new ActionBar(), count);
}
export function testActivityIndicator(layout: StackLayout): string {
return test(layout, () => new ActivityIndicator(), count);
}
export function testBorder(layout: StackLayout): string {
return test(layout, () => new Border(), count);
}
export function testContentView(layout: StackLayout): string {
return test(layout, () => new ContentView(), count);
}
export function testDatePicker(layout: StackLayout): string {
return test(layout, () => new DatePicker(), count);
}
export function testHtmlView(layout: StackLayout): string {
return test(layout, () => new HtmlView(), count);
}
export function testImage(layout: StackLayout): string {
return test(layout, () => new Image(), count);
}
export function testLabel(layout: StackLayout): string {
return test(layout, () => new Label(), count);
}
export function testListPicker(layout: StackLayout): string {
return test(layout, () => new ListPicker(), count);
}
export function testListView(layout: StackLayout): string {
return test(layout, () => new ListView(), count);
}
export function testPage(layout: StackLayout): string {
return test(layout, () => new Page(), count);
}
export function testProgress(layout: StackLayout): string {
return test(layout, () => new Progress(), count);
}
export function testRepeater(layout: StackLayout): string {
return test(layout, () => new Repeater(), count);
}
export function testSwitch(layout: StackLayout): string {
return test(layout, () => new Switch(), count);
}
export function testTextField(layout: StackLayout): string {
return test(layout, () => new TextField(), count);
}
export function testTextView(layout: StackLayout): string {
return test(layout, () => new TextView(), count);
}
function test(layout: StackLayout, createView: () => View, count: number): string {
@ -69,14 +134,14 @@ function test(layout: StackLayout, createView: () => View, count: number): strin
const cssMap1 = new Map<string, any>();
viewMap1.set('isEnabled', false);
let result = execute(layout, createView, count, viewMap1, cssMap1) + ', ';
let result = execute(layout, createView, count, viewMap1, cssMap1)
viewMap1.set('text', 'text');
viewMap1.set('automationText', "automationText");
cssMap1.set('width', 100);
cssMap1.set('height', 100);
cssMap1.set('rotate', '90');
result += execute(layout, createView, count, viewMap1, cssMap1) + ', ';
result += execute(layout, createView, count, viewMap1, cssMap1)
viewMap1.set('clipToBounds', false);
viewMap1.set('left', '20');
@ -91,7 +156,7 @@ function test(layout: StackLayout, createView: () => View, count: number): strin
cssMap1.set('horizontalAlignment', 'center');
cssMap1.set('verticalAlignment', 'center');
result += execute(layout, createView, count, viewMap1, cssMap1) + ', ';
result += execute(layout, createView, count, viewMap1, cssMap1)
viewMap1.set('row', '1');
viewMap1.set('rowSpan', '2');
@ -110,45 +175,79 @@ function test(layout: StackLayout, createView: () => View, count: number): strin
cssMap1.set('backgroundColor', 'red');
cssMap1.set('backgroundImage', '~/logo.png');
result += execute(layout, createView, count, viewMap1, cssMap1) + ', ';
result += execute(layout, createView, count, viewMap1, cssMap1)
result += execute(layout, createView, count, setters, cssSetters);
return `${createView().typeName}: ${result}\n`;
return `${createView().typeName}: ${result}`;
}
let b = false;
function execute(layout: StackLayout, createView: () => View, count: number,
viewProps: Map<string, any>, cssProps: Map<string, any>): string {
const not = profile(layout, createView, count, false, viewProps, cssProps);
const recycled = profile(layout, createView, count, true, viewProps, cssProps);
const improved = ((not - recycled) / not) * 100;
console.log(`recycled time: ${recycled}`);
console.log(`not recycled time: ${not}`);
const propCount = viewProps.size + cssProps.size;
return `${propCount}: ${improved.toFixed(0)}%`;
gc();
java.lang.System.gc();
gc();
java.lang.System.gc();
// b = !b;
// let not: { time: number, count: number };
let recycled: { time: number, count: number };
// if (b) {
// not = profile(layout, createView, count, false, viewProps, cssProps);
// recycled = profile(layout, createView, count, true, viewProps, cssProps);
// } else {
recycled = profile(layout, createView, count, true, viewProps, cssProps);
// not = profile(layout, createView, count, false, viewProps, cssProps);
// }
// console.log(`recycled: ${recycled.time}`);
// console.log(`not: ${not.time}`);
// const improved = ((not.time - recycled.time) / not.time) * 100;
const propCount = recycled.count;
return `\t${recycled.time.toFixed(0)}`;
}
const props = _getProperties();
const styleProps = _getStyleProperties();
function profile(layout: StackLayout, createView: () => View, count: number, recycle: boolean,
viewProps: Map<string, any>, cssProps: Map<string, any>): number {
viewProps: Map<string, any>, cssProps: Map<string, any>): { time: number, count: number } {
const view = createView();
view.recycleNativeView = recycle ? 'always' : 'never';
const style = view.style;
// DatePicker throws OOM
const c = view.typeName === 'DatePicker' ? 1 : 5;
let total = 0;
let x = 0;
for (let i = 0; i < c; i++) {
x = 0;
viewProps.forEach((v, k) => {
const p = props.find(vp => (<any>vp).name === k);
// if (p && view[p.setNative]) {
view[k] = v;
x++;
// }
});
viewProps.forEach((v, k) => view[k] = v);
cssProps.forEach((v, k) => style[k] = v);
cssProps.forEach((v, k) => {
const p = styleProps.find(vp => (<any>vp).name === k);
// if (p && view[p.setNative]) {
style[k] = v;
x++;
// }
});
gc();
java.lang.System.gc();
const start = time();
for (let i = 0; i < count; i++) {
layout.addChild(view);
layout.removeChild(view);
}
const end = time() - start;
return end;
total += end;
}
return { time: total / c, count: x };
}
let setters: Map<string, any>;
@ -282,11 +381,11 @@ function setupSetters(): void {
setters.set('androidOffscreenTabLimit', '2');
// text-base
const formattedText = new FormattedString();
const span = new Span();
span.text = 'span';
formattedText.spans.push(span);
setters.set('formattedText', formattedText);
// const formattedText = new FormattedString();
// const span = new Span();
// span.text = 'span';
// formattedText.spans.push(span);
// setters.set('formattedText', formattedText);
// text-base
setters.set('secure', 'true');

View File

@ -6,7 +6,7 @@ export function imageSourceFromAsset(imageAsset){
let source = new imageSource.ImageSource();
source.fromAsset(imageAsset).then((source) => {
let folder = fs.knownFolders.documents().path;
let fileName = "Test.png"
let fileName = "test.png"
let path = fs.path.join(folder, fileName);
let saved = source.saveToFile(path, "png");
if(saved){

View File

@ -56,7 +56,7 @@ export function testSaveToFile() {
// >> imagesource-save-to
var img = imageSource.fromFile(imagePath);
var folder = fs.knownFolders.documents();
var path = fs.path.join(folder.path, "Test.png");
var path = fs.path.join(folder.path, "test.png");
var saved = img.saveToFile(path, "png");
// << imagesource-save-to
TKUnit.assert(saved, "Image not saved to file");
@ -66,16 +66,16 @@ export function testSaveToFile() {
export function testFromFile() {
// >> imagesource-load-local
var folder = fs.knownFolders.documents();
var path = fs.path.join(folder.path, "Test.png");
var path = fs.path.join(folder.path, "test.png");
var img = imageSource.fromFile(path);
// << imagesource-load-local
TKUnit.assert(img.height > 0, "image.fromResource failed");
// remove the image from the file system
var file = folder.getFile("Test.png");
var file = folder.getFile("test.png");
file.remove();
TKUnit.assert(!fs.File.exists(path), "Test.png not removed");
TKUnit.assert(!fs.File.exists(path), "test.png not removed");
}
export function testNativeFields() {

View File

@ -248,63 +248,65 @@ let cssSetters: Map<string, any>;
let defaultNativeGetters: Map<string, (view) => any>;
export function nativeView_recycling_test(createNew: () => View, createLayout?: () => LayoutBase, nativeGetters?: Map<string, (view) => any>, customSetters?: Map<string, any>) {
if (isIOS) {
// recycling not implemented yet.
return;
}
setupSetters();
const page = getClearCurrentPage();
let layout: LayoutBase = new FlexboxLayout();
if (createLayout) {
// This is done on purpose. We need the constructor of Flexbox
// to run otherwise some module fileds stays uninitialized.
layout = createLayout();
}
// if (isIOS) {
// // recycling not implemented yet.
// return;
// }
page.content = layout;
// setupSetters();
// const page = getClearCurrentPage();
// let layout: LayoutBase = new FlexboxLayout();
// if (createLayout) {
// // This is done on purpose. We need the constructor of Flexbox
// // to run otherwise some module fileds stays uninitialized.
// layout = createLayout();
// }
const first = createNew();
const test = createNew();
// page.content = layout;
// Make sure we are not reusing a native views.
first.recycleNativeView = "never";
test.recycleNativeView = "never";
// const first = createNew();
// const test = createNew();
page.content = layout;
// // Make sure we are not reusing a native views.
// first.recycleNativeView = "never";
// test.recycleNativeView = "never";
layout.addChild(test);
// page.content = layout;
setValue(test.style, cssSetters);
setValue(test, setters, customSetters);
// Needed so we can reset formattedText
test["secure"] = false;
// layout.addChild(test);
const nativeView = test.nativeViewProtected;
// Mark so we reuse the native views.
test.recycleNativeView = "always";
layout.removeChild(test);
const newer = createNew();
newer.recycleNativeView = "always";
layout.addChild(newer);
layout.addChild(first);
// setValue(test.style, cssSetters);
// setValue(test, setters, customSetters);
// // Needed so we can reset formattedText
// test["secure"] = false;
if (first.typeName !== "SearchBar") {
// There are way too many differences in native methods for search-bar.
// There are too many methods that just throw for newly created views in API lvl 19 and 17
if (sdkVersion < 21) {
TKUnit.waitUntilReady(() => layout.isLayoutValid);
}
// const nativeView = test.nativeViewProtected;
// // Mark so we reuse the native views.
// test.recycleNativeView = "always";
// layout.removeChild(test);
// const newer = createNew();
// newer.recycleNativeView = "always";
// layout.addChild(newer);
// layout.addChild(first);
compareUsingReflection(newer, first);
}
// if (first.typeName !== "SearchBar") {
// // There are way too many differences in native methods for search-bar.
// // There are too many methods that just throw for newly created views in API lvl 19 and 17
// if (sdkVersion < 21) {
// TKUnit.waitUntilReady(() => layout.isLayoutValid);
// }
TKUnit.assertEqual(newer.nativeViewProtected, nativeView, "nativeView not reused.");
checkDefaults(newer, first, props, nativeGetters || defaultNativeGetters);
checkDefaults(newer, first, styleProps, nativeGetters || defaultNativeGetters);
// compareUsingReflection(newer, first);
// }
layout.removeChild(newer);
layout.removeChild(first);
// TKUnit.assertEqual(newer.nativeViewProtected, nativeView, "nativeView not reused.");
// checkDefaults(newer, first, props, nativeGetters || defaultNativeGetters);
// checkDefaults(newer, first, styleProps, nativeGetters || defaultNativeGetters);
// layout.removeChild(newer);
// layout.removeChild(first);
}
function compareUsingReflection(recycledNativeView: View, newNativeView: View): void {

View File

@ -601,8 +601,12 @@ export function test_NativeSetter_called_when_add_and_remove_and_recycled() {
firstView.removeChild(secondView);
// we don't recycle nativeViews on iOS yet so reset is not called.
TKUnit.assertEqual(secondView.cssPropCounter, isIOS ? 2 : 3, "7");
TKUnit.assertEqual(secondView.viewPropCounter, isIOS ? 2 : 3, "8");
// Recycling disabled for android too
// TKUnit.assertEqual(secondView.cssPropCounter, isIOS ? 2 : 3, "7");
// TKUnit.assertEqual(secondView.viewPropCounter, isIOS ? 2 : 3, "8");
TKUnit.assertEqual(secondView.cssPropCounter, 2, "7");
TKUnit.assertEqual(secondView.viewPropCounter,2, "8");
});
};

View File

@ -2,9 +2,9 @@
import * as commonTests from "./view-tests-common";
import * as helper from "../../ui/helper";
import * as view from "tns-core-modules/ui/core/view";
import * as button from "tns-core-modules/ui/button";
import { Button } from "tns-core-modules/ui/button";
import * as types from "tns-core-modules/utils/types";
import * as stack from "tns-core-modules/ui/layouts/stack-layout";
import { StackLayout } from "tns-core-modules/ui/layouts/stack-layout";
import * as labelModule from "tns-core-modules/ui/label";
import * as frame from "tns-core-modules/ui/frame";
import * as trace from "tns-core-modules/trace";
@ -14,6 +14,101 @@ trace.enable();
global.moduleMerge(commonTests, exports);
// function setup(): StackLayout {
// const page = helper.getClearCurrentPage();
// const stack = new StackLayout();
// page.content = stack;
// return stack;
// }
// export function test_recycle_native_view_never() {
// const stack = setup();
// const btn = new Button();
// btn.recycleNativeView = 'never';
// stack.addChild(btn);
// TKUnit.assertNotNull(btn.nativeViewProtected);
// const oldNativeView = btn.nativeViewProtected;
// stack.removeChild(btn);
// stack.addChild(btn);
// const newNativeView = btn.nativeViewProtected;
// TKUnit.assertNotEqual(oldNativeView, newNativeView);
// }
// export function test_recycle_native_view_always() {
// const stack = setup();
// const btn = new Button();
// btn.recycleNativeView = 'always';
// stack.addChild(btn);
// TKUnit.assertNotNull(btn.nativeViewProtected);
// const oldNativeView = btn.nativeViewProtected;
// stack.removeChild(btn);
// stack.addChild(btn);
// const newNativeView = btn.nativeViewProtected;
// TKUnit.assertEqual(oldNativeView, newNativeView);
// }
// export function test_recycle_native_view_auto_access_nativeView() {
// const stack = setup();
// const btn = new Button();
// btn.recycleNativeView = 'auto';
// stack.addChild(btn);
// TKUnit.assertNotNull(btn.nativeView);
// const oldNativeView = btn.nativeViewProtected;
// stack.removeChild(btn);
// stack.addChild(btn);
// const newNativeView = btn.nativeViewProtected;
// TKUnit.assertNotEqual(oldNativeView, newNativeView);
// }
// export function test_recycle_native_view_auto_access_android() {
// const stack = setup();
// const btn = new Button();
// btn.recycleNativeView = 'auto';
// stack.addChild(btn);
// TKUnit.assertNotNull(btn.android);
// const oldNativeView = btn.nativeViewProtected;
// stack.removeChild(btn);
// stack.addChild(btn);
// const newNativeView = btn.nativeViewProtected;
// TKUnit.assertNotEqual(oldNativeView, newNativeView);
// }
// export function test_recycle_property_counter_few_properties() {
// const stack = setup();
// const btn = new Button();
// btn.text = "text";
// btn.recycleNativeView = 'auto';
// stack.addChild(btn);
// TKUnit.assertNotNull(btn.nativeViewProtected);
// const oldNativeView = btn.nativeViewProtected;
// stack.removeChild(btn);
// stack.addChild(btn);
// const newNativeView = btn.nativeViewProtected;
// TKUnit.assertEqual(oldNativeView, newNativeView);
// }
// export function test_recycle_property_counter_more_properties() {
// const stack = setup();
// const btn = new Button();
// btn.recyclePropertyCounter = 1;
// btn.recycleNativeView = 'auto';
// btn.text = "text";
// btn.style.margin = "20";
// stack.addChild(btn);
// TKUnit.assertNotNull(btn.nativeViewProtected);
// const oldNativeView = btn.nativeViewProtected;
// stack.removeChild(btn);
// stack.addChild(btn);
// const newNativeView = btn.nativeViewProtected;
// TKUnit.assertNotEqual(oldNativeView, newNativeView);
// }
export const test_event_setupUI_IsRaised = function () {
const listener = new Listener("_setupUI");
trace.addEventListener(listener);
@ -38,8 +133,8 @@ export const test_event_setupUI_IsRaised_WhenAttached_Dynamically = function ()
const listener = new Listener("_setupUI");
trace.addEventListener(listener);
const newButton = new button.Button();
(<stack.StackLayout>views[1]).addChild(newButton);
const newButton = new Button();
(<StackLayout>views[1]).addChild(newButton);
TKUnit.assertEqual(listener.receivedEvents.length, 1);
TKUnit.assertEqual(listener.receivedEvents[0].name, "_setupUI");
@ -75,8 +170,8 @@ export const test_event_onContextChanged_IsRaised_WhenAttached_Dynamically = fun
const listener = new Listener("_onContextChanged");
trace.addEventListener(listener);
const newButton = new button.Button();
(<stack.StackLayout>views[1]).addChild(newButton);
const newButton = new Button();
(<StackLayout>views[1]).addChild(newButton);
TKUnit.assertEqual(listener.receivedEvents.length, 1);
TKUnit.assertEqual(listener.receivedEvents[0].name, "_onContextChanged");
@ -125,7 +220,7 @@ export const test_event_tearDownUI_IsRaised_WhenRemoved_Dynamically = function (
trace.addEventListener(listener);
// remove the button from the layout
(<stack.StackLayout>views[1]).removeChild(views[2]);
(<StackLayout>views[1]).removeChild(views[2]);
TKUnit.assertEqual(listener.receivedEvents.length, 1);
TKUnit.assertEqual(listener.receivedEvents[0].name, "_tearDownUI");
@ -142,8 +237,8 @@ export const test_events_tearDownUIAndRemovedFromNativeVisualTree_AreRaised_When
let removeFromNativeVisualTreeListener = new Listener("_removeViewFromNativeVisualTree");
let page = frame.topmost().currentPage;
let stackLayout = new stack.StackLayout();
let btn = new button.Button();
let stackLayout = new StackLayout();
let btn = new Button();
stackLayout.addChild(btn);
page.content = stackLayout;
@ -174,11 +269,11 @@ export const test_events_tearDownUIAndRemovedFromNativeVisualTree_AreRaised_When
export const test_cachedProperties_Applied_WhenNativeWidged_IsCreated = function () {
const test = function (views: Array<view.View>) {
const newButton = new button.Button();
const newButton = new Button();
newButton.text = "Test Button";
TKUnit.assert(types.isUndefined(newButton.android));
(<stack.StackLayout>views[1]).addChild(newButton);
(<StackLayout>views[1]).addChild(newButton);
TKUnit.assert(types.isDefined(newButton.android));
// TODO: There is currently an issue with the getText conversion to JavaScript string
@ -190,9 +285,9 @@ export const test_cachedProperties_Applied_WhenNativeWidged_IsCreated = function
export function test_automation_text_set_to_native() {
const test = function (views: Array<view.View>) {
const newButton = new button.Button();
const newButton = new Button();
newButton.automationText = "Button1";
(<stack.StackLayout>views[1]).addChild(newButton);
(<StackLayout>views[1]).addChild(newButton);
TKUnit.assertEqual((<android.widget.Button>newButton.android).getContentDescription(), "Button1", "contentDescription not set to native view.");
};
@ -234,9 +329,9 @@ class Listener implements trace.EventListener {
export const test_StylePropertiesDefaultValuesCache = function () {
const testValue = 35;
const test = function (views: [view.View, stack.StackLayout, button.Button, view.View]) {
const test = function (views: [view.View, StackLayout, Button, view.View]) {
const testLabel = new labelModule.Label();
const testButton = new button.Button();
const testButton = new Button();
const stack = views[1];

View File

@ -215,6 +215,7 @@ function createRootView(v?: View) {
rootView = frame;
}
rootView._setupAsRootView({});
return rootView;
}

View File

@ -144,8 +144,6 @@ export abstract class ViewBase extends Observable {
* read-only. If you want to set out-of-band the nativeView use the setNativeView method.
*/
public nativeViewProtected: any;
public recycleNativeView: "always" | "never" | "auto";
public nativeView: any;
public bindingContext: any;
@ -306,6 +304,14 @@ export abstract class ViewBase extends Observable {
public ensureDomNode();
//@private
/**
* @private
*/
public recycleNativeView: "always" | "never" | "auto";
/**
* @private
*/
public _isPaddingRelative: boolean;
public _styleScope: any;
/**
@ -321,6 +327,10 @@ export abstract class ViewBase extends Observable {
* Allow multiple updates to be performed on the instance at once.
*/
public _batchUpdate<T>(callback: () => T): T;
/**
* @private
*/
_setupAsRootView(context: any): void;
//@endprivate
}

View File

@ -6,6 +6,7 @@ import { Order, FlexGrow, FlexShrink, FlexWrapBefore, AlignSelf } from "../../la
import { KeyframeAnimation } from "../../animation/keyframe-animation";
// Types.
import { Source } from "../../../utils/debug";
import { Property, CssProperty, CssAnimationProperty, InheritedProperty, Style, clearInheritedProperties, propagateInheritableProperties, propagateInheritableCssProperties, resetCSSProperties, initNativeView, resetNativeView } from "../properties";
import { Binding, BindingOptions, Observable, WrappedValue, PropertyChangeData, traceEnabled, traceWrite, traceCategories, traceNotifyEvent } from "../bindable";
import { isIOS, isAndroid } from "../../../platform";
@ -94,40 +95,40 @@ export function eachDescendant(view: ViewBaseDefinition, callback: (child: ViewB
let viewIdCounter = 1;
const contextMap = new WeakMap<Object, Map<string, WeakRef<Object>[]>>();
// const contextMap = new WeakMap<Object, Map<string, WeakRef<Object>[]>>();
function getNativeView(context: Object, typeName: string): Object {
let typeMap = contextMap.get(context);
if (!typeMap) {
typeMap = new Map<string, WeakRef<Object>[]>();
contextMap.set(context, typeMap);
return undefined;
}
// function getNativeView(context: Object, typeName: string): Object {
// let typeMap = contextMap.get(context);
// if (!typeMap) {
// typeMap = new Map<string, WeakRef<Object>[]>();
// contextMap.set(context, typeMap);
// return undefined;
// }
const array = typeMap.get(typeName);
if (array) {
let nativeView;
while (array.length > 0) {
const weakRef = array.pop();
nativeView = weakRef.get();
if (nativeView) {
return nativeView;
}
}
}
return undefined;
}
// const array = typeMap.get(typeName);
// if (array) {
// let nativeView;
// while (array.length > 0) {
// const weakRef = array.pop();
// nativeView = weakRef.get();
// if (nativeView) {
// return nativeView;
// }
// }
// }
// return undefined;
// }
function putNativeView(context: Object, view: ViewBase): void {
const typeMap = contextMap.get(context);
const typeName = view.typeName;
let list = typeMap.get(typeName);
if (!list) {
list = [];
typeMap.set(typeName, list);
}
list.push(new WeakRef(view.nativeViewProtected));
}
// function putNativeView(context: Object, view: ViewBase): void {
// const typeMap = contextMap.get(context);
// const typeName = view.typeName;
// let list = typeMap.get(typeName);
// if (!list) {
// list = [];
// typeMap.set(typeName, list);
// }
// list.push(new WeakRef(view.nativeViewProtected));
// }
export abstract class ViewBase extends Observable implements ViewBaseDefinition {
public static loadedEvent = "loaded";
@ -141,7 +142,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
private _visualState: string;
private _inlineStyleSelector: SelectorCore;
private __nativeView: any;
private _disableNativeViewRecycling: boolean;
// private _disableNativeViewRecycling: boolean;
public domNode: DOMNode;
public recycleNativeView: "always" | "never" | "auto";
@ -204,7 +205,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
public _defaultPaddingRight: number;
public _defaultPaddingBottom: number;
public _defaultPaddingLeft: number;
private _isPaddingRelative: boolean;
public _isPaddingRelative: boolean;
constructor() {
super();
@ -213,7 +214,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
}
get nativeView(): any {
this._disableNativeViewRecycling = true;
// this._disableNativeViewRecycling = true;
return this.nativeViewProtected;
}
@ -230,7 +231,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
}
get android(): any {
this._disableNativeViewRecycling = true;
// this._disableNativeViewRecycling = true;
return this._androidView;
}
@ -670,25 +671,29 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
}
private resetNativeViewInternal(): void {
const nativeView = this.nativeViewProtected;
if (nativeView && isAndroid) {
const recycle = this.recycleNativeView;
if (recycle === "always" || (recycle === "auto" && !this._disableNativeViewRecycling)) {
resetNativeView(this);
if (this._isPaddingRelative) {
nativeView.setPaddingRelative(this._defaultPaddingLeft, this._defaultPaddingTop, this._defaultPaddingRight, this._defaultPaddingBottom);
} else {
nativeView.setPadding(this._defaultPaddingLeft, this._defaultPaddingTop, this._defaultPaddingRight, this._defaultPaddingBottom);
}
this.resetNativeView();
}
}
// const nativeView = this.nativeViewProtected;
// if (nativeView && isAndroid) {
// const recycle = this.recycleNativeView;
// if (recycle === "always" || (recycle === "auto" && !this._disableNativeViewRecycling)) {
// resetNativeView(this);
// if (this._isPaddingRelative) {
// nativeView.setPaddingRelative(this._defaultPaddingLeft, this._defaultPaddingTop, this._defaultPaddingRight, this._defaultPaddingBottom);
// } else {
// nativeView.setPadding(this._defaultPaddingLeft, this._defaultPaddingTop, this._defaultPaddingRight, this._defaultPaddingBottom);
// }
// this.resetNativeView();
// }
// }
if (this._cssState) {
this._cancelAllAnimations();
}
}
_setupAsRootView(context: any): void {
this._setupUI(context);
}
@profile
public _setupUI(context: android.content.Context, atIndex?: number, parentIsLoaded?: boolean) {
@ -706,10 +711,10 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
let nativeView;
if (isAndroid) {
const recycle = this.recycleNativeView;
if (recycle === "always" || (recycle === "auto" && !this._disableNativeViewRecycling)) {
nativeView = <android.view.View>getNativeView(context, this.typeName);
}
// const recycle = this.recycleNativeView;
// if (recycle === "always" || (recycle === "auto" && !this._disableNativeViewRecycling)) {
// nativeView = <android.view.View>getNativeView(context, this.typeName);
// }
if (!nativeView) {
nativeView = <android.view.View>this.createNativeView();
@ -790,15 +795,15 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
@profile
public _tearDownUI(force?: boolean) {
if (traceEnabled()) {
traceWrite(`${this}._tearDownUI(${force})`, traceCategories.VisualTreeEvents);
}
// No context means we are already teared down.
if (!this._context) {
return;
}
if (traceEnabled()) {
traceWrite(`${this}._tearDownUI(${force})`, traceCategories.VisualTreeEvents);
}
this.resetNativeViewInternal();
this.eachChild((child) => {
@ -810,18 +815,24 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
this.parent._removeViewFromNativeVisualTree(this);
}
const nativeView = this.nativeViewProtected;
if (nativeView && isAndroid) {
const recycle = this.recycleNativeView;
if (recycle === "always" || (recycle === "auto" && !this._disableNativeViewRecycling)) {
// const nativeParent = isAndroid ? (<android.view.View>nativeView).getParent() : (<UIView>nativeView).superview;
const nativeParent = (<android.view.View>nativeView).getParent();
const animation = (<android.view.View>nativeView).getAnimation();
if (!nativeParent && !animation) {
putNativeView(this._context, this);
}
}
}
// const nativeView = this.nativeViewProtected;
// if (nativeView && isAndroid) {
// const recycle = this.recycleNativeView;
// let shouldRecycle = false;
// if (recycle === "always") {
// shouldRecycle = true;
// } else if (recycle === "auto" && !this._disableNativeViewRecycling) {
// const propertiesSet = Object.getOwnPropertySymbols(this).length + Object.getOwnPropertySymbols(this.style).length / 2;
// shouldRecycle = propertiesSet <= this.recyclePropertyCounter;
// }
// // const nativeParent = isAndroid ? (<android.view.View>nativeView).getParent() : (<UIView>nativeView).superview;
// const nativeParent = (<android.view.View>nativeView).getParent();
// const animation = (<android.view.View>nativeView).getAnimation();
// if (shouldRecycle && !nativeParent && !animation) {
// putNativeView(this._context, this);
// }
// }
this.disposeNativeView();
@ -941,6 +952,21 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
}
}
}
public toString(): string {
let str = this.typeName;
if (this.id) {
str += `<${this.id}>`;
} else {
str += `(${this._domId})`;
}
let source = Source.get(this);
if (source) {
str += `@${source};`;
}
return str;
}
}
ViewBase.prototype.isCollapsed = false;

View File

@ -2,9 +2,6 @@
import { View as ViewDefinition, Point, Size, Color, dip } from ".";
import { HorizontalAlignment, VerticalAlignment, Visibility, Length, PercentLength } from "../../styling/style-properties";
// Types.
import { Source } from "../../../utils/debug";
import {
ViewBase, Property, booleanConverter, EventData, layout,
getEventOrGestureName, traceEnabled, traceWrite, traceCategories
@ -747,21 +744,6 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
super.resetNativeView();
}
public toString(): string {
let str = this.typeName;
if (this.id) {
str += `<${this.id}>`;
} else {
str += `(${this._domId})`;
}
let source = Source.get(this);
if (source) {
str += `@${source};`;
}
return str;
}
public _setNativeViewFrame(nativeView: any, frame: any) {
//
}

View File

@ -7,7 +7,8 @@ import { CacheLayerType } from "../../../utils/utils";
import { Background, ad as androidBackground } from "../../styling/background";
import {
ViewCommon, layout, isEnabledProperty, originXProperty, originYProperty, automationTextProperty, isUserInteractionEnabledProperty,
traceEnabled, traceWrite, traceCategories, traceNotifyEvent
traceEnabled, traceWrite, traceCategories, traceNotifyEvent,
paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty
} from "./view-common";
import {
@ -486,7 +487,18 @@ export class View extends ViewCommon {
} else {
const nativeView = this.nativeViewProtected;
org.nativescript.widgets.ViewHelper.setBackground(nativeView, value);
nativeView.setPadding(this._defaultPaddingLeft, this._defaultPaddingTop, this._defaultPaddingRight, this._defaultPaddingBottom);
const style = this.style;
const paddingTop = paddingTopProperty.isSet(style) ? this.effectivePaddingTop : this._defaultPaddingTop;
const paddingRight = paddingRightProperty.isSet(style) ? this.effectivePaddingRight : this._defaultPaddingRight;
const paddingBottom = paddingBottomProperty.isSet(style) ? this.effectivePaddingBottom : this._defaultPaddingBottom;
const paddingLeft = paddingLeftProperty.isSet(style) ? this.effectivePaddingLeft : this._defaultPaddingLeft;
if (this._isPaddingRelative) {
nativeView.setPaddingRelative(paddingLeft, paddingTop, paddingRight, paddingBottom);
} else {
nativeView.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
}
(<any>nativeView).background = undefined;
const cache = <CacheLayerType><any>nativeView;

View File

@ -334,6 +334,22 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
this._updateActionBar();
}
public _findEntryForTag(fragmentTag: string): BackstackEntry {
let entry: BackstackEntry;
if (this._currentEntry && this._currentEntry.fragmentTag === fragmentTag) {
entry = this._currentEntry;
} else {
entry = this._backStack.find((value) => value.fragmentTag === fragmentTag);
// on API 26 fragments are recreated lazily after activity is destroyed.
if (!entry) {
const navigationItem = this._navigationQueue.find((value) => value.entry.fragmentTag === fragmentTag);
entry = navigationItem ? navigationItem.entry : undefined;
}
}
return entry;
}
public navigationQueueIsEmpty(): boolean {
return this._navigationQueue.length === 0;
}

View File

@ -419,13 +419,7 @@ function findPageForFragment(fragment: android.app.Fragment, frame: Frame) {
return;
}
let entry: BackstackEntry;
if (frame._currentEntry && frame._currentEntry.fragmentTag === fragmentTag) {
entry = frame._currentEntry;
} else {
entry = frame.backStack.find((value) => value.fragmentTag === fragmentTag);
}
const entry = frame._findEntryForTag(fragmentTag);
const page = entry ? entry.resolvedPage : undefined;
if (page) {
const callbacks: FragmentCallbacksImplementation = fragment[CALLBACKS];
@ -668,7 +662,7 @@ class ActivityCallbacksImplementation implements AndroidActivityCallbacks {
this._rootView = rootView;
// Initialize native visual tree;
rootView._setupUI(activity);
rootView._setupAsRootView(activity);
activity.setContentView(rootView.nativeViewProtected, new org.nativescript.widgets.CommonLayoutParams());
// frameId is negative w
if (frame) {

View File

@ -132,6 +132,10 @@ export class Frame extends View {
* @private
*/
_getNavBarVisible(page: Page): boolean;
/**
* @private
*/
_findEntryForTag(fragmentTag: string): BackstackEntry;
//@endprivate
/**

View File

@ -20,8 +20,6 @@ const DELEGATE = "_delegate";
let navDepth = -1;
const FRAME_CONTEXT = {};
export class Frame extends FrameBase {
private _ios: iOSFrame;
private _paramToNavigate: any;
@ -35,13 +33,6 @@ export class Frame extends FrameBase {
public _bottom: number;
public _isInitialNavigation: boolean = true;
public get _context(): any {
return FRAME_CONTEXT;
}
public set _context(value: any) {
throw new Error("Frame _context is readonly");
}
constructor() {
super();
this._ios = new iOSFrame(this);
@ -255,10 +246,6 @@ export class Frame extends FrameBase {
return this._ios;
}
// get nativeView(): any {
// return this._ios.controller.view;
// }
public static get defaultAnimatedNavigation(): boolean {
return FrameBase.defaultAnimatedNavigation;
}
@ -495,10 +482,10 @@ class UINavigationControllerImpl extends UINavigationController {
}
@profile
public viewDidLoad(): void {
super.viewDidLoad();
public viewWillAppear(animated: boolean): void {
super.viewWillAppear(animated);
let owner = this._owner.get();
if (owner) {
if (owner && (!owner.isLoaded && !owner.parent)) {
owner.onLoaded();
}
}