diff --git a/CrossPlatformModules.csproj b/CrossPlatformModules.csproj
index 950037051..9688da4ba 100644
--- a/CrossPlatformModules.csproj
+++ b/CrossPlatformModules.csproj
@@ -134,6 +134,20 @@
page-title-icon.xml
+
+
+
+
+ animation.d.ts
+
+
+ animation.d.ts
+
+
+ animation.d.ts
+
+
+
list-picker.xml
@@ -791,6 +805,13 @@
+
+
+
+
+
+ Designer
+
@@ -798,7 +819,9 @@
-
+
+ Designer
+
Designer
@@ -1741,7 +1764,9 @@
PreserveNewest
-
+
+
+
PreserveNewest
diff --git a/application/Readme.md b/application/Readme.md
index e5badda4c..cc215353f 100644
--- a/application/Readme.md
+++ b/application/Readme.md
@@ -1,6 +1,4 @@
-Use the frame in the following way:
-
-### To navigate to the starting page of the application
+# Ani
```javascript
// put this in the bootstrap.js
var app = require("application");
diff --git a/apps/animations/app.css b/apps/animations/app.css
new file mode 100644
index 000000000..c59011f60
--- /dev/null
+++ b/apps/animations/app.css
@@ -0,0 +1,3 @@
+page {
+ /* CSS styles */
+}
\ No newline at end of file
diff --git a/apps/animations/app.ts b/apps/animations/app.ts
new file mode 100644
index 000000000..2baebd169
--- /dev/null
+++ b/apps/animations/app.ts
@@ -0,0 +1,8 @@
+import application = require("application");
+import trace = require("trace");
+
+trace.enable();
+trace.setCategories(trace.categories.concat(trace.categories.Animation));
+
+application.mainModule = "main-page";
+application.start();
diff --git a/apps/animations/main-page.ts b/apps/animations/main-page.ts
new file mode 100644
index 000000000..39c7d293f
--- /dev/null
+++ b/apps/animations/main-page.ts
@@ -0,0 +1,93 @@
+import observable = require("data/observable");
+import pages = require("ui/page");
+import buttonModule = require("ui/button");
+import abs = require("ui/layouts/absolute-layout");
+import animationModule = require("ui/animation");
+import colorModule = require("color");
+import model = require("./model");
+
+var vm = new model.ViewModel();
+
+var page: pages.Page;
+var panel: abs.AbsoluteLayout;
+var button1: buttonModule.Button;
+var button2: buttonModule.Button;
+var button3: buttonModule.Button;
+var buttonAnimation: animationModule.Animation;
+var panelAnimation: animationModule.Animation;
+
+export function pageLoaded(args: observable.EventData) {
+ page = args.object;
+ page.bindingContext = vm;
+ panel = page.getViewById("panel1");
+ button1 = page.getViewById("button1");
+ button2 = page.getViewById("button2");
+ button3 = page.getViewById("button3");
+}
+
+export function onSlideOut(args: observable.EventData) {
+ console.log("onSlideOut");
+ var curve = page.android ? new android.view.animation.AccelerateInterpolator(1) : UIViewAnimationCurve.UIViewAnimationCurveEaseIn;
+
+ var buttonAnimations = [
+ { target: button1, translate: { x: -240, y: 0 }, scale: { x: 0.5, y: 0.5 }, opacity: 0, duration: vm.duration, delay: 0, iterations: vm.iterations, curve: curve },
+ { target: button2, translate: { x: -240, y: 0 }, scale: { x: 0.5, y: 0.5 }, opacity: 0, duration: vm.duration, delay: vm.duration, iterations: vm.iterations, curve: curve },
+ { target: button3, translate: { x: -240, y: 0 }, scale: { x: 0.5, y: 0.5 }, opacity: 0, duration: vm.duration, delay: vm.duration * 2, iterations: vm.iterations, curve: curve },
+ ]
+ buttonAnimation = new animationModule.Animation(buttonAnimations, vm.playSequentially);
+
+ panelAnimation = panel.createAnimation({ opacity: 0, scale: { x: 0.5, y: 0.5 }, rotate: -360, backgroundColor: new colorModule.Color("red"), duration: vm.duration, iterations: vm.iterations });
+
+ buttonAnimation.play().finished
+ .then(() => panelAnimation.play().finished)
+ .catch((e) => console.log(e.message));
+}
+
+export function onSlideIn(args: observable.EventData) {
+ console.log("onSlideIn");
+ var curve = page.android ? new android.view.animation.DecelerateInterpolator(1) : UIViewAnimationCurve.UIViewAnimationCurveEaseOut;
+
+ panelAnimation = panel.createAnimation({ opacity: 1, scale: { x: 1, y: 1 }, rotate: 0, backgroundColor: new colorModule.Color("yellow"), duration: vm.duration, iterations: vm.iterations });
+
+ var buttonAnimations = [
+ { target: button3, translate: { x: 0, y: 0 }, scale: { x: 1, y: 1 }, opacity: 1, duration: vm.duration, delay: 0, iterations: vm.iterations, curve: curve },
+ { target: button2, translate: { x: 0, y: 0 }, scale: { x: 1, y: 1 }, opacity: 1, duration: vm.duration, delay: vm.duration, iterations: vm.iterations, curve: curve },
+ { target: button1, translate: { x: 0, y: 0 }, scale: { x: 1, y: 1 }, opacity: 1, duration: vm.duration, delay: vm.duration * 2, iterations: vm.iterations, curve: curve },
+ ]
+ buttonAnimation = new animationModule.Animation(buttonAnimations, vm.playSequentially);
+
+ panelAnimation.play().finished
+ .then(() => buttonAnimation.play().finished)
+ .catch((e) => console.log(e.message));
+}
+
+export function onCancel(args: observable.EventData) {
+ console.log("onCancel");
+ if (panelAnimation.isPlaying) {
+ panelAnimation.cancel();
+ }
+ if (buttonAnimation.isPlaying) {
+ buttonAnimation.cancel();
+ }
+}
+
+export function onTap(args: observable.EventData) {
+ console.log((args.object).text);
+}
+
+export function onSingle(args: observable.EventData) {
+ console.log("onSingle");
+ button1.animate({
+ opacity: 0.75,
+ backgroundColor: new colorModule.Color("Red"),
+ translate: { x: 100, y: 100 },
+ scale: { x: 2, y: 2 },
+ rotate: 180,
+ duration: vm.duration,
+ delay: 0,
+ iterations: vm.iterations,
+ curve: button1.ios ? UIViewAnimationCurve.UIViewAnimationCurveEaseIn : new android.view.animation.AccelerateInterpolator(1),
+ })
+ .then(() => console.log("Animation finished"))
+ .catch((e) => console.log(e.message));
+}
\ No newline at end of file
diff --git a/apps/animations/main-page.xml b/apps/animations/main-page.xml
new file mode 100644
index 000000000..8a132ce87
--- /dev/null
+++ b/apps/animations/main-page.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/animations/model.ts b/apps/animations/model.ts
new file mode 100644
index 000000000..8123a1c26
--- /dev/null
+++ b/apps/animations/model.ts
@@ -0,0 +1,37 @@
+import observable = require("data/observable");
+
+export class ViewModel extends observable.Observable {
+ constructor() {
+ super();
+
+ this._duration = 3000;
+ this._iterations = 1;
+ }
+
+ private _playSequentially: boolean;
+ get playSequentially(): boolean {
+ return this._playSequentially;
+ }
+ set playSequentially(value: boolean) {
+ this._playSequentially = value;
+ this.notify({ object: this, eventName: observable.Observable.propertyChangeEvent, propertyName: "playSequentially", value: value });
+ }
+
+ private _duration: number;
+ get duration(): number {
+ return this._duration;
+ }
+ set duration(value: number) {
+ this._duration = value;
+ this.notify({ object: this, eventName: observable.Observable.propertyChangeEvent, propertyName: "duration", value: value });
+ }
+
+ private _iterations: number;
+ get iterations(): number {
+ return this._iterations;
+ }
+ set iterations(value: number) {
+ this._iterations = value;
+ this.notify({ object: this, eventName: observable.Observable.propertyChangeEvent, propertyName: "iterations", value: value });
+ }
+}
\ No newline at end of file
diff --git a/apps/animations/package.json b/apps/animations/package.json
new file mode 100644
index 000000000..be8391bcb
--- /dev/null
+++ b/apps/animations/package.json
@@ -0,0 +1,2 @@
+{ "name" : "animations",
+ "main" : "app.js" }
\ No newline at end of file
diff --git a/apps/tests/testRunner.ts b/apps/tests/testRunner.ts
index 59680937e..25399b0b2 100644
--- a/apps/tests/testRunner.ts
+++ b/apps/tests/testRunner.ts
@@ -78,6 +78,7 @@ allTests["WEAK-EVENTS"] = require("./weak-event-listener-tests");
allTests["REPEATER"] = require("./ui/repeater/repeater-tests");
allTests["SEARCH-BAR"] = require('./ui/search-bar/search-bar-tests');
allTests["CONNECTIVITY"] = require("./connectivity-tests");
+allTests["ANIMATION"] = require("./ui/animation/animation-tests");
if (!isRunningOnEmulator()) {
allTests["LOCATION"] = require("./location-tests");
diff --git a/apps/tests/ui/animation/animation-tests.ts b/apps/tests/ui/animation/animation-tests.ts
new file mode 100644
index 000000000..48544bcc7
--- /dev/null
+++ b/apps/tests/ui/animation/animation-tests.ts
@@ -0,0 +1,243 @@
+import TKUnit = require("../../TKUnit");
+import helper = require("../helper");
+import pageModule = require("ui/page");
+import labelModule = require("ui/label");
+import stackLayoutModule = require("ui/layouts/stack-layout");
+import colorModule = require("color");
+
+//
+// # Animation
+// Animating view properties requires the "ui/animation" module.
+// ``` JavaScript
+import animation = require("ui/animation");
+// ```
+//
+
+export var test_AnimatingProperties = function (done) {
+ var mainPage: pageModule.Page;
+ var label: labelModule.Label;
+ var pageFactory = function (): pageModule.Page {
+ label = new labelModule.Label();
+ label.text = "label";
+ var stackLayout = new stackLayoutModule.StackLayout();
+ stackLayout.addChild(label);
+ mainPage = new pageModule.Page();
+ mainPage.content = stackLayout;
+ return mainPage;
+ };
+
+ helper.navigate(pageFactory);
+ TKUnit.waitUntilReady(() => { return label.isLoaded });
+
+ //
+ // # Animating properties
+ // ``` JavaScript
+ label.animate({
+ opacity: 0.75,
+ backgroundColor: new colorModule.Color("Red"),
+ translate: { x: 100, y: 100 },
+ scale: { x: 2, y: 2 },
+ rotate: 180,
+ duration: 1000,
+ delay: 100,
+ iterations: 3,
+ curve: label.ios ? UIViewAnimationCurve.UIViewAnimationCurveEaseIn : new android.view.animation.AccelerateInterpolator(1),
+ })
+ .then(() => {
+ console.log("Animation finished.");
+ //
+ helper.goBack();
+ done();
+ //
+ })
+ .catch((e) => {
+ console.log(e.message);
+ //
+ helper.goBack();
+ done(e);
+ //
+ });
+ // ```
+ //
+}
+
+export var test_CancellingAnimation = function (done) {
+ var mainPage: pageModule.Page;
+ var label: labelModule.Label;
+ var pageFactory = function (): pageModule.Page {
+ label = new labelModule.Label();
+ label.text = "label";
+ var stackLayout = new stackLayoutModule.StackLayout();
+ stackLayout.addChild(label);
+ mainPage = new pageModule.Page();
+ mainPage.content = stackLayout;
+ return mainPage;
+ };
+
+ helper.navigate(pageFactory);
+ TKUnit.waitUntilReady(() => { return label.isLoaded });
+
+ //
+ // # Cancelling animation
+ // ``` JavaScript
+ var animation1 = label.createAnimation({ translate: { x: 100, y: 100 } });
+ animation1.play().finished
+ .then(() => {
+ console.log("Animation finished");
+ //
+ helper.goBack();
+ done();
+ //
+ })
+ .catch((e) => {
+ console.log("Animation cancelled");
+ //
+ helper.goBack();
+ done(e);
+ //
+ });
+ animation1.cancel();
+ // ```
+ //
+}
+
+export var test_ChainingAnimations = function (done) {
+ var mainPage: pageModule.Page;
+ var label: labelModule.Label;
+ var pageFactory = function (): pageModule.Page {
+ label = new labelModule.Label();
+ label.text = "label";
+ var stackLayout = new stackLayoutModule.StackLayout();
+ stackLayout.addChild(label);
+ mainPage = new pageModule.Page();
+ mainPage.content = stackLayout;
+ return mainPage;
+ };
+ helper.navigate(pageFactory);
+ TKUnit.waitUntilReady(() => { return label.isLoaded });
+
+ //
+ // # Chaining animations
+ // ``` JavaScript
+ label.animate({ opacity: 0 })
+ .then(() => label.animate({ opacity: 1 }))
+ .then(() => label.animate({ translate: { x: 200, y: 200 } }))
+ .then(() => label.animate({ translate: { x: 0, y: 0 } }))
+ .then(() => label.animate({ scale: { x: 5, y: 5 } }))
+ .then(() => label.animate({ scale: { x: 1, y: 1 } }))
+ .then(() => label.animate({ rotate: 180 }))
+ .then(() => label.animate({ rotate: 0 }))
+ .then(() => {
+ console.log("Animation finished");
+ //
+ helper.goBack();
+ done();
+ //
+ })
+ .catch((e) => {
+ console.log(e.message);
+ //
+ helper.goBack();
+ done(e);
+ //
+ });
+ // ```
+ //
+}
+
+export var test_ReusingAnimations = function (done) {
+ var mainPage: pageModule.Page;
+ var label: labelModule.Label;
+ var pageFactory = function (): pageModule.Page {
+ label = new labelModule.Label();
+ label.text = "label";
+ var stackLayout = new stackLayoutModule.StackLayout();
+ stackLayout.addChild(label);
+ mainPage = new pageModule.Page();
+ mainPage.content = stackLayout;
+ return mainPage;
+ };
+
+ helper.navigate(pageFactory);
+ TKUnit.waitUntilReady(() => { return label.isLoaded });
+
+ //
+ // # Reusing animations
+ // ``` JavaScript
+ var animation1 = label.createAnimation({ translate: { x: 100, y: 100 } });
+ var animation2 = label.createAnimation({ translate: { x: 0, y: 0 } });
+
+ animation1.play().finished
+ .then(() => animation2.play().finished)
+ .then(() => animation1.play().finished)
+ .then(() => animation2.play().finished)
+ .then(() => animation1.play().finished)
+ .then(() => animation2.play().finished)
+ .then(() => {
+ console.log("Animation finished");
+ //
+ helper.goBack();
+ done();
+ //
+ })
+ .catch((e) => {
+ console.log(e.message);
+ //
+ helper.goBack();
+ done(e);
+ //
+ });
+ // ```
+ //
+}
+
+export var test_AnimatingMultipleViews = function (done) {
+ var mainPage: pageModule.Page;
+ var label1: labelModule.Label;
+ var label2: labelModule.Label;
+ var label3: labelModule.Label;
+ var pageFactory = function (): pageModule.Page {
+ label1 = new labelModule.Label();
+ label1.text = "label1";
+ label2 = new labelModule.Label();
+ label2.text = "label2";
+ label3 = new labelModule.Label();
+ label3.text = "label3";
+ var stackLayout = new stackLayoutModule.StackLayout();
+ stackLayout.addChild(label1);
+ stackLayout.addChild(label2);
+ stackLayout.addChild(label3);
+ mainPage = new pageModule.Page();
+ mainPage.content = stackLayout;
+ return mainPage;
+ };
+ helper.navigate(pageFactory);
+ TKUnit.waitUntilReady(() => { return label1.isLoaded && label2.isLoaded });
+
+ //
+ // # Animating multiple views simultaneously
+ // ``` JavaScript
+ var animations: Array = [
+ { target: label1, translate: { x: 200, y: 200 }, duration: 1000, delay: 0 },
+ { target: label2, translate: { x: 200, y: 200 }, duration: 1000, delay: 333 },
+ { target: label3, translate: { x: 200, y: 200 }, duration: 1000, delay: 666 },
+ ];
+ var animation = new animation.Animation(animations);
+ animation.play().finished
+ .then(() => {
+ console.log("Animations finished");
+ //
+ helper.goBack();
+ done();
+ //
+ })
+ .catch((e) => {
+ console.log(e.message);
+ //
+ helper.goBack();
+ done(e);
+ //
+ });
+ // ```
+ //
+}
\ No newline at end of file
diff --git a/declarations.d.ts b/declarations.d.ts
index 61295bc03..80659a90b 100644
--- a/declarations.d.ts
+++ b/declarations.d.ts
@@ -93,6 +93,7 @@ interface Console {
log(message: any, ...formatParams: any[]): void;
trace(): void;
dump(obj: any): void;
+ createDump(obj: any): string;
dir(obj: any): void;
}
diff --git a/trace/trace.d.ts b/trace/trace.d.ts
index 52197f126..2dff00a98 100644
--- a/trace/trace.d.ts
+++ b/trace/trace.d.ts
@@ -75,6 +75,7 @@ declare module "trace" {
export var Test: string;
export var Binding: string;
export var Error: string;
+ export var Animation: string;
export var All: string;
diff --git a/trace/trace.ts b/trace/trace.ts
index 5a7e4cc1a..4398c4e36 100644
--- a/trace/trace.ts
+++ b/trace/trace.ts
@@ -113,7 +113,8 @@ export module categories {
export var Test = "Test";
export var Binding = "Binding";
export var Error = "Error";
- export var All = VisualTreeEvents + "," + Layout + "," + Style + "," + ViewHierarchy + "," + NativeLifecycle + "," + Debug + "," + Navigation + "," + Test + "," + Binding + "," + Error;
+ export var Animation = "Animation";
+ export var All = VisualTreeEvents + "," + Layout + "," + Style + "," + ViewHierarchy + "," + NativeLifecycle + "," + Debug + "," + Navigation + "," + Test + "," + Binding + "," + Error + "," + Animation;
export var separator = ",";
diff --git a/ui/animation/animation-common.ts b/ui/animation/animation-common.ts
new file mode 100644
index 000000000..906aac1c3
--- /dev/null
+++ b/ui/animation/animation-common.ts
@@ -0,0 +1,180 @@
+import definition = require("ui/animation");
+import viewModule = require("ui/core/view");
+import trace = require("trace");
+
+export module Properties {
+ export var opacity = "opacity";
+ export var backgroundColor = "backgroundColor";
+ export var translate = "translate";
+ export var rotate = "rotate";
+ export var scale = "scale";
+}
+
+export interface PropertyAnimation {
+ target: viewModule.View;
+ property: string;
+ value: any;
+ duration?: number;
+ delay?: number;
+ iterations?: number;
+ curve?: any;
+}
+
+export class Animation implements definition.Animation {
+ public _propertyAnimations: Array;
+ public _playSequentially: boolean;
+ private _isPlaying: boolean;
+ private _resolve;
+ private _reject;
+ private _animationFinishedPromise: Promise;
+
+ public play(): Animation {
+ if (this.isPlaying) {
+ throw new Error("Animation is already playing.");
+ }
+
+ this._isPlaying = true;
+ return this;
+ }
+
+ public cancel(): void {
+ if (!this.isPlaying) {
+ throw new Error("Animation is not currently playing.");
+ }
+ }
+
+ public get finished(): Promise {
+ return this._animationFinishedPromise;
+ }
+
+ public get isPlaying(): boolean {
+ return this._isPlaying;
+ }
+
+ constructor(animationDefinitions: Array, playSequentially?: boolean) {
+ if (!animationDefinitions || animationDefinitions.length === 0) {
+ throw new Error("No animation definitions specified");
+ }
+
+ trace.write("Analyzing " + animationDefinitions.length + " animation definitions...", trace.categories.Animation);
+ this._propertyAnimations = new Array();
+ var i = 0;
+ var length = animationDefinitions.length;
+ for (; i < length; i++) {
+ this._propertyAnimations = this._propertyAnimations.concat(Animation._createPropertyAnimations(animationDefinitions[i]));
+ }
+
+ if (this._propertyAnimations.length === 0) {
+ throw new Error("Nothing to animate.");
+ }
+ trace.write("Created " + this._propertyAnimations.length + " individual property animations.", trace.categories.Animation);
+
+ this._playSequentially = playSequentially;
+ var that = this;
+ this._animationFinishedPromise = new Promise((resolve, reject) => {
+ that._resolve = resolve;
+ that._reject = reject;
+ });
+ }
+
+ public _resolveAnimationFinishedPromise() {
+ this._isPlaying = false;
+ this._resolve();
+ }
+
+ public _rejectAnimationFinishedPromise() {
+ this._isPlaying = false;
+ this._reject(new Error("Animation cancelled."));
+ }
+
+ private static _createPropertyAnimations(animationDefinition: definition.AnimationDefinition): Array {
+ if (!animationDefinition.target) {
+ throw new Error("No animation target specified.");
+ }
+
+ var propertyAnimations = new Array();
+
+ // opacity
+ if (animationDefinition.opacity !== undefined) {
+ propertyAnimations.push({
+ target: animationDefinition.target,
+ property: Properties.opacity,
+ value: animationDefinition.opacity,
+ duration: animationDefinition.duration,
+ delay: animationDefinition.delay,
+ iterations: animationDefinition.iterations,
+ curve: animationDefinition.curve
+ });
+ }
+
+ // backgroundColor
+ if (animationDefinition.backgroundColor !== undefined) {
+ propertyAnimations.push({
+ target: animationDefinition.target,
+ property: Properties.backgroundColor,
+ value: animationDefinition.backgroundColor,
+ duration: animationDefinition.duration,
+ delay: animationDefinition.delay,
+ iterations: animationDefinition.iterations,
+ curve: animationDefinition.curve
+ });
+ }
+
+ // translate
+ if (animationDefinition.translate !== undefined) {
+ propertyAnimations.push({
+ target: animationDefinition.target,
+ property: Properties.translate,
+ value: animationDefinition.translate,
+ duration: animationDefinition.duration,
+ delay: animationDefinition.delay,
+ iterations: animationDefinition.iterations,
+ curve: animationDefinition.curve
+ });
+ }
+
+ // scale
+ if (animationDefinition.scale !== undefined) {
+ propertyAnimations.push({
+ target: animationDefinition.target,
+ property: Properties.scale,
+ value: animationDefinition.scale,
+ duration: animationDefinition.duration,
+ delay: animationDefinition.delay,
+ iterations: animationDefinition.iterations,
+ curve: animationDefinition.curve
+ });
+ }
+
+ // rotate
+ if (animationDefinition.rotate !== undefined) {
+ propertyAnimations.push({
+ target: animationDefinition.target,
+ property: Properties.rotate,
+ value: animationDefinition.rotate,
+ duration: animationDefinition.duration,
+ delay: animationDefinition.delay,
+ iterations: animationDefinition.iterations,
+ curve: animationDefinition.curve
+ });
+ }
+
+ if (propertyAnimations.length === 0) {
+ throw new Error("No animation property specified.");
+ }
+
+ return propertyAnimations;
+ }
+
+ public static _getAnimationInfo(animation: PropertyAnimation): string {
+ return JSON.stringify({
+ target: animation.target.id,
+ property: animation.property,
+ value: animation.value,
+ duration: animation.duration,
+ delay: animation.delay,
+ iterations: animation.iterations,
+ curve: animation.curve
+ });
+ }
+}
diff --git a/ui/animation/animation.android.ts b/ui/animation/animation.android.ts
new file mode 100644
index 000000000..023810cb1
--- /dev/null
+++ b/ui/animation/animation.android.ts
@@ -0,0 +1,256 @@
+import definition = require("ui/animation");
+import common = require("ui/animation/animation-common");
+import utils = require("utils/utils");
+import color = require("color");
+import trace = require("trace");
+import types = require("utils/types");
+
+// merge the exports of the common file with the exports of this file
+declare var exports;
+require("utils/module-merge").merge(common, exports);
+
+var intType = java.lang.Integer.class.getField("TYPE").get(null);
+var floatType = java.lang.Float.class.getField("TYPE").get(null);
+var argbEvaluator: android.animation.ArgbEvaluator = new android.animation.ArgbEvaluator();
+
+export class Animation extends common.Animation implements definition.Animation {
+ private _animatorListener: android.animation.Animator.AnimatorListener;
+ private _nativeAnimatorsArray: any;
+ private _animatorSet: android.animation.AnimatorSet;
+ private _animators: Array;
+ private _propertyUpdateCallbacks: Array;
+ private _propertyResetCallbacks: Array;
+
+ public play(): Animation {
+ super.play();
+
+ var i: number;
+ var length: number;
+
+ this._animators = new Array();
+ this._propertyUpdateCallbacks = new Array();
+ this._propertyResetCallbacks = new Array();
+
+ i = 0;
+ length = this._propertyAnimations.length;
+ for (; i < length; i++) {
+ this._createAnimators(this._propertyAnimations[i]);
+ }
+
+ if (this._animators.length === 0) {
+ trace.write("Nothing to animate.", trace.categories.Animation);
+ this._resolveAnimationFinishedPromise();
+ return this;
+ }
+
+ this._nativeAnimatorsArray = java.lang.reflect.Array.newInstance(android.animation.ObjectAnimator.class, this._animators.length);
+ i = 0;
+ length = this._animators.length;
+ for (; i < length; i++) {
+ this._nativeAnimatorsArray[i] = this._animators[i];
+ }
+
+ this._animatorSet = new android.animation.AnimatorSet();
+ this._animatorSet.addListener(this._animatorListener);
+ if (this._playSequentially) {
+ this._animatorSet.playSequentially(this._nativeAnimatorsArray);
+ }
+ else {
+ this._animatorSet.playTogether(this._nativeAnimatorsArray);
+ }
+
+ trace.write("Starting " + this._nativeAnimatorsArray.length + " animations " + (this._playSequentially ? "sequentially." : "together."), trace.categories.Animation);
+ this._animatorSet.start();
+ return this;
+ }
+
+ public cancel(): void {
+ super.cancel();
+ trace.write("Cancelling AnimatorSet.", trace.categories.Animation);
+ this._animatorSet.cancel();
+ }
+
+ constructor(animationDefinitions: Array, playSequentially?: boolean) {
+ super(animationDefinitions, playSequentially);
+
+ var that = this;
+ this._animatorListener = new android.animation.Animator.AnimatorListener({
+ onAnimationStart: function (animator: android.animation.Animator): void {
+ that._onAndroidAnimationStart();
+ },
+ onAnimationRepeat: function (animator: android.animation.Animator): void {
+ that._onAndroidAnimationRepeat();
+ },
+ onAnimationEnd: function (animator: android.animation.Animator): void {
+ that._onAndroidAnimationEnd();
+ },
+ onAnimationCancel: function (animator: android.animation.Animator): void {
+ that._onAndroidAnimationCancel();
+ }
+ });
+ }
+
+ private _onAndroidAnimationStart() {
+ trace.write("AndroidAnimation._onAndroidAnimationStart.", trace.categories.Animation);
+ }
+
+ private _onAndroidAnimationRepeat() {
+ trace.write("AndroidAnimation._onAndroidAnimationRepeat.", trace.categories.Animation);
+ }
+
+ private _onAndroidAnimationEnd() {
+ trace.write("AndroidAnimation._onAndroidAnimationEnd.", trace.categories.Animation);
+
+ if (!this.isPlaying) {
+ // It has been cancelled
+ return;
+ }
+
+ var i = 0;
+ var length = this._propertyUpdateCallbacks.length;
+ for (; i < length; i++) {
+ this._propertyUpdateCallbacks[i]();
+ }
+ this._resolveAnimationFinishedPromise();
+ }
+
+ private _onAndroidAnimationCancel() {
+ trace.write("AndroidAnimation._onAndroidAnimationCancel.", trace.categories.Animation);
+ var i = 0;
+ var length = this._propertyResetCallbacks.length;
+ for (; i < length; i++) {
+ this._propertyResetCallbacks[i]();
+ }
+ this._rejectAnimationFinishedPromise();
+ }
+
+ private _createAnimators(propertyAnimation: common.PropertyAnimation): void {
+ trace.write("Creating ObjectAnimator(s) for animation: " + common.Animation._getAnimationInfo(propertyAnimation) + "...", trace.categories.Animation);
+
+ if (types.isNullOrUndefined(propertyAnimation.target)) {
+ throw new Error("Animation target cannot be null or undefined!");
+ }
+
+ if (types.isNullOrUndefined(propertyAnimation.property)) {
+ throw new Error("Animation property cannot be null or undefined!");
+ }
+
+ if (types.isNullOrUndefined(propertyAnimation.value)) {
+ throw new Error("Animation value cannot be null or undefined!");
+ }
+
+ var nativeArray;
+ var nativeView: android.view.View = (propertyAnimation.target._nativeView);
+ var animators = new Array();
+ var propertyUpdateCallbacks = new Array();
+ var propertyResetCallbacks = new Array();
+ var animator: android.animation.ObjectAnimator;
+ var originalValue;
+ var density = utils.layout.getDisplayDensity();
+ switch (propertyAnimation.property) {
+
+ case common.Properties.opacity:
+ originalValue = nativeView.getAlpha();
+ if (propertyAnimation.value !== propertyAnimation.target.opacity) {
+ nativeArray = java.lang.reflect.Array.newInstance(floatType, 1);
+ nativeArray[0] = propertyAnimation.value;
+ animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "alpha", nativeArray));
+ propertyUpdateCallbacks.push(() => { propertyAnimation.target.opacity = propertyAnimation.value });
+ propertyResetCallbacks.push(() => { nativeView.setAlpha(originalValue); });
+ }
+ break;
+
+ case common.Properties.backgroundColor:
+ originalValue = nativeView.getBackground();
+ if (!color.Color.equals(propertyAnimation.value, propertyAnimation.target.backgroundColor)) {
+ nativeArray = java.lang.reflect.Array.newInstance(intType, 1);
+ nativeArray[0] = (propertyAnimation.value).argb;
+ //https://github.com/NativeScript/android-runtime/issues/168
+ //animator = android.animation.ObjectAnimator.ofObject(nativeView, "backgroundColor", argbEvaluator, nativeArray);
+ animator = android.animation.ObjectAnimator.ofInt(nativeView, "backgroundColor", nativeArray);
+ animator.setEvaluator(argbEvaluator);
+ animators.push(animator);
+ propertyUpdateCallbacks.push(() => { propertyAnimation.target.backgroundColor = propertyAnimation.value; });
+ propertyResetCallbacks.push(() => { nativeView.setBackground(originalValue); });
+ }
+ break;
+
+ case common.Properties.translate:
+ originalValue = nativeView.getTranslationX();
+ if (propertyAnimation.value.x * density !== originalValue) {
+ nativeArray = java.lang.reflect.Array.newInstance(floatType, 1);
+ nativeArray[0] = propertyAnimation.value.x * density;
+ animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "translationX", nativeArray));
+ propertyResetCallbacks.push(() => { nativeView.setTranslationX(originalValue); });
+ }
+
+ originalValue = nativeView.getTranslationY();
+ if (propertyAnimation.value.y * density !== originalValue) {
+ nativeArray = java.lang.reflect.Array.newInstance(floatType, 1);
+ nativeArray[0] = propertyAnimation.value.y * density;
+ animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "translationY", nativeArray));
+ propertyResetCallbacks.push(() => { nativeView.setTranslationY(originalValue); });
+ }
+ break;
+
+ case common.Properties.rotate:
+ originalValue = nativeView.getRotation();
+ if (propertyAnimation.value !== originalValue) {
+ nativeArray = java.lang.reflect.Array.newInstance(floatType, 1);
+ nativeArray[0] = propertyAnimation.value;
+ animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "rotation", nativeArray));
+ propertyResetCallbacks.push(() => { nativeView.setRotation(originalValue); });
+ }
+ break;
+
+ case common.Properties.scale:
+ originalValue = nativeView.getScaleX();
+ if (propertyAnimation.value.x !== originalValue) {
+ nativeArray = java.lang.reflect.Array.newInstance(floatType, 1);
+ nativeArray[0] = propertyAnimation.value.x;
+ animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "scaleX", nativeArray));
+ propertyResetCallbacks.push(() => { nativeView.setScaleX(originalValue); });
+ }
+
+ originalValue = nativeView.getScaleY();
+ if (propertyAnimation.value.y !== originalValue) {
+ nativeArray = java.lang.reflect.Array.newInstance(floatType, 1);
+ nativeArray[0] = propertyAnimation.value.y;
+ animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "scaleY", nativeArray));
+ propertyResetCallbacks.push(() => { nativeView.setScaleY(originalValue); });
+ }
+ break;
+
+ default:
+ throw new Error("Cannot animate " + propertyAnimation.property);
+ break;
+ }
+
+ var i = 0;
+ var length = animators.length;
+ for (; i < length; i++) {
+ if (propertyAnimation.duration !== undefined) {
+ animators[i].setDuration(propertyAnimation.duration);
+ }
+ if (propertyAnimation.delay !== undefined) {
+ animators[i].setStartDelay(propertyAnimation.delay);
+ }
+ if (propertyAnimation.iterations !== undefined) {
+ if (propertyAnimation.iterations === Number.POSITIVE_INFINITY) {
+ animators[i].setRepeatCount(android.view.animation.Animation.INFINITE);
+ }
+ else {
+ animators[i].setRepeatCount(propertyAnimation.iterations - 1);
+ }
+ }
+ if (propertyAnimation.curve !== undefined) {
+ animators[i].setInterpolator(propertyAnimation.curve);
+ }
+ trace.write("ObjectAnimator created: " + animators[i], trace.categories.Animation);
+ }
+
+ this._animators = this._animators.concat(animators);
+ this._propertyUpdateCallbacks = this._propertyUpdateCallbacks.concat(propertyUpdateCallbacks);
+ this._propertyResetCallbacks = this._propertyResetCallbacks.concat(propertyResetCallbacks);
+ }
+}
\ No newline at end of file
diff --git a/ui/animation/animation.d.ts b/ui/animation/animation.d.ts
new file mode 100644
index 000000000..f1a0ac6f0
--- /dev/null
+++ b/ui/animation/animation.d.ts
@@ -0,0 +1,80 @@
+declare module "ui/animation" {
+ import viewModule = require("ui/core/view");
+ import colorModule = require("color");
+
+ /**
+ * Defines animation options for the View.animate method.
+ */
+ export interface AnimationDefinition {
+ /**
+ * The view whose property is to be animated.
+ */
+ target?: viewModule.View;
+
+ /**
+ * Animates the opacity of the view. Value should be a number between 0.0 and 1.0
+ */
+ opacity?: number;
+
+ /**
+ * Animates the backgroundColor of the view.
+ */
+ backgroundColor?: colorModule.Color;
+
+ /**
+ * Animates the translate affine transform of the view.
+ */
+ translate?: Pair;
+
+ /**
+ * Animates the scale affine transform of the view.
+ */
+ scale?: Pair;
+
+ /**
+ * Animates the rotate affine transform of the view. Value should be a number specifying the rotation amount in degrees.
+ */
+ rotate?: number;
+
+ /**
+ * The length of the animation in milliseconds. The default duration is 300 milliseconds.
+ */
+ duration?: number;
+
+ /**
+ * The amount of time, in milliseconds, to delay starting the animation.
+ */
+ delay?: number;
+
+ /**
+ * Specifies how many times the animation should be played. Default is 1.
+ * iOS animations support fractional iterations, i.e. 1.5.
+ * To repeat an animation infinitely, use Number.POSITIVE_INFINITY
+ */
+ iterations?: number;
+
+ /**
+ * An optional animation curve of type UIViewAnimationCurve for iOS or android.animation.TimeInterpolator for Android.
+ */
+ curve?: any;
+ }
+
+ /**
+ * Defines a pair of values (horizontal and vertical) for translate and scale animations.
+ */
+ export interface Pair {
+ x: number;
+ y: number;
+ }
+
+ /**
+ * Defines a animation set.
+ */
+ export class Animation {
+ constructor(animationDefinitions: Array, playSequentially?: boolean);
+ public play: () => Animation;
+ public cancel: () => void;
+ public finished: Promise;
+ public isPlaying: boolean;
+ }
+}
\ No newline at end of file
diff --git a/ui/animation/animation.ios.ts b/ui/animation/animation.ios.ts
new file mode 100644
index 000000000..0e278513a
--- /dev/null
+++ b/ui/animation/animation.ios.ts
@@ -0,0 +1,282 @@
+import definition = require("ui/animation");
+import common = require("ui/animation/animation-common");
+import trace = require("trace");
+
+// merge the exports of the common file with the exports of this file
+declare var exports;
+require("utils/module-merge").merge(common, exports);
+
+var _transform = "_transform";
+var _skip = "_skip";
+
+var FLT_MAX = 340282346638528859811704183484516925440.000000;
+
+class AnimationDelegateImpl extends NSObject {
+ static new(): AnimationDelegateImpl {
+ return super.new();
+ }
+
+ private _finishedCallback: Function;
+
+ public initWithFinishedCallback(finishedCallback: Function): AnimationDelegateImpl {
+ this._finishedCallback = finishedCallback;
+ return this;
+ }
+
+ public animationWillStart(animationID: string, context: any): void {
+ trace.write("AnimationDelegateImpl.animationWillStart, animationID: " + animationID, trace.categories.Animation);
+ }
+
+ public animationDidStop(animationID: string, finished: boolean, context: any): void {
+ trace.write("AnimationDelegateImpl.animationDidStop, animationID: " + animationID + ", finished: " + finished, trace.categories.Animation);
+ if (this._finishedCallback) {
+ var cancelled = !finished;
+ // This could either be the master finishedCallback or an nextAnimationCallback depending on the playSequentially argument values.
+ this._finishedCallback(cancelled);
+ }
+ }
+
+ public static ObjCExposedMethods = {
+ "animationWillStart": { returns: interop.types.void, params: [NSString, NSObject] },
+ "animationDidStop": { returns: interop.types.void, params: [NSString, NSNumber, NSObject] }
+ };
+}
+
+export class Animation extends common.Animation implements definition.Animation {
+ private _iOSAnimationFunction: Function;
+ private _finishedAnimations: number;
+ private _cancelledAnimations: number;
+ private _mergedPropertyAnimations: Array;
+
+ public play(): Animation {
+ super.play();
+
+ this._finishedAnimations = 0;
+ this._cancelledAnimations = 0;
+ this._iOSAnimationFunction();
+
+ return this;
+ }
+
+ public cancel(): void {
+ super.cancel();
+
+ var i = 0;
+ var length = this._mergedPropertyAnimations.length;
+ for (; i < length; i++) {
+ (this._mergedPropertyAnimations[i].target._nativeView).layer.removeAllAnimations();
+ if ((this._mergedPropertyAnimations[i])._propertyResetCallback) {
+ (this._mergedPropertyAnimations[i])._propertyResetCallback();
+ }
+ }
+ }
+
+ constructor(animationDefinitions: Array, playSequentially?: boolean) {
+ super(animationDefinitions, playSequentially);
+
+ trace.write("Non-merged Property Animations: " + this._propertyAnimations.length, trace.categories.Animation);
+ this._mergedPropertyAnimations = Animation._mergeAffineTransformAnimations(this._propertyAnimations);
+ trace.write("Merged Property Animations: " + this._mergedPropertyAnimations.length, trace.categories.Animation);
+
+ var that = this;
+ var animationFinishedCallback = (cancelled: boolean) => {
+ if (that._playSequentially) {
+ // This function will be called by the last animation when done or by another animation if the user cancels them halfway through.
+ if (cancelled) {
+ that._rejectAnimationFinishedPromise();
+ }
+ else {
+ that._resolveAnimationFinishedPromise();
+ }
+ }
+ else {
+ // This callback will be called by each INDIVIDUAL animation when it finishes or is cancelled.
+ if (cancelled) {
+ that._cancelledAnimations++;
+ }
+ else {
+ that._finishedAnimations++;
+ }
+
+ if (that._cancelledAnimations === that._mergedPropertyAnimations.length) {
+ trace.write(that._cancelledAnimations + " animations cancelled.", trace.categories.Animation);
+ that._rejectAnimationFinishedPromise();
+ }
+ else if (that._finishedAnimations === that._mergedPropertyAnimations.length) {
+ trace.write(that._finishedAnimations + " animations finished.", trace.categories.Animation);
+ that._resolveAnimationFinishedPromise();
+ }
+ }
+ };
+
+ this._iOSAnimationFunction = Animation._createiOSAnimationFunction(this._mergedPropertyAnimations, 0, this._playSequentially, animationFinishedCallback);
+ }
+
+ private static _createiOSAnimationFunction(propertyAnimations: Array, index: number, playSequentially: boolean, finishedCallback: (cancelled?: boolean) => void): Function {
+ return (cancelled?: boolean) => {
+ if (cancelled && finishedCallback) {
+ trace.write("Animation " + (index - 1).toString() + " was cancelled. Will skip the rest of animations and call finishedCallback(true).", trace.categories.Animation);
+ finishedCallback(cancelled);
+ return;
+ }
+
+ var animation = propertyAnimations[index];
+ var nativeView = (animation.target._nativeView);
+
+ var nextAnimationCallback: Function;
+ var animationDelegate: AnimationDelegateImpl;
+ if (index === propertyAnimations.length - 1) {
+ // This is the last animation, so tell it to call the master finishedCallback when done.
+ animationDelegate = AnimationDelegateImpl.new().initWithFinishedCallback(finishedCallback);
+ }
+ else {
+ nextAnimationCallback = Animation._createiOSAnimationFunction(propertyAnimations, index + 1, playSequentially, finishedCallback);
+ // If animations are to be played sequentially, tell it to start the next animation when done.
+ // If played together, all individual animations will call the master finishedCallback, which increments a counter every time it is called.
+ animationDelegate = AnimationDelegateImpl.new().initWithFinishedCallback(playSequentially ? nextAnimationCallback : finishedCallback);
+ }
+
+ trace.write("UIView.beginAnimationsContext(" + index + "): " + common.Animation._getAnimationInfo(animation), trace.categories.Animation);
+ UIView.beginAnimationsContext(index.toString(), null);
+
+ if (animationDelegate) {
+ UIView.setAnimationDelegate(animationDelegate);
+ UIView.setAnimationWillStartSelector("animationWillStart");
+ UIView.setAnimationDidStopSelector("animationDidStop");
+ }
+
+ if (animation.duration !== undefined) {
+ UIView.setAnimationDuration(animation.duration / 1000.0);
+ }
+ else {
+ UIView.setAnimationDuration(0.3); //Default duration.
+ }
+ if (animation.delay !== undefined) {
+ UIView.setAnimationDelay(animation.delay / 1000.0);
+ }
+ if (animation.iterations !== undefined) {
+ if (animation.iterations === Number.POSITIVE_INFINITY) {
+ UIView.setAnimationRepeatCount(FLT_MAX);
+ }
+ else {
+ UIView.setAnimationRepeatCount(animation.iterations - 1);
+ }
+ }
+ if (animation.curve !== undefined) {
+ UIView.setAnimationCurve(animation.curve);
+ }
+
+ var originalValue;
+ switch (animation.property) {
+ case common.Properties.opacity:
+ originalValue = animation.target.opacity;
+ (animation)._propertyResetCallback = () => { animation.target.opacity = originalValue };
+ animation.target.opacity = animation.value;
+ break;
+ case common.Properties.backgroundColor:
+ originalValue = animation.target.backgroundColor;
+ (animation)._propertyResetCallback = () => { animation.target.backgroundColor = originalValue };
+ animation.target.backgroundColor = animation.value;
+ break;
+ case _transform:
+ originalValue = nativeView.transform;
+ (animation)._propertyResetCallback = () => { nativeView.transform = originalValue };
+ nativeView.transform = animation.value;
+ break;
+ default:
+ throw new Error("Cannot animate " + animation.property);
+ break;
+ }
+
+ trace.write("UIView.commitAnimations " + index, trace.categories.Animation);
+ UIView.commitAnimations();
+
+ if (!playSequentially && nextAnimationCallback) {
+ nextAnimationCallback();
+ }
+ }
+ }
+
+ private static _isAffineTransform(property: string): boolean {
+ return property === _transform
+ || property === common.Properties.translate
+ || property === common.Properties.rotate
+ || property === common.Properties.scale;
+ }
+
+ private static _canBeMerged(animation1: common.PropertyAnimation, animation2: common.PropertyAnimation) {
+ var result =
+ Animation._isAffineTransform(animation1.property) &&
+ Animation._isAffineTransform(animation2.property) &&
+ animation1.target === animation2.target &&
+ animation1.duration === animation2.duration &&
+ animation1.delay === animation2.delay &&
+ animation1.iterations === animation2.iterations &&
+ animation1.curve === animation2.curve;
+ return result;
+ }
+
+ private static _affineTransform(matrix: CGAffineTransform, property: string, value: any): CGAffineTransform {
+ switch (property) {
+ case common.Properties.translate:
+ return CGAffineTransformTranslate(matrix, value.x, value.y);
+ case common.Properties.rotate:
+ return CGAffineTransformRotate(matrix, value * Math.PI / 180);
+ case common.Properties.scale:
+ return CGAffineTransformScale(matrix, value.x, value.y);
+ default:
+ throw new Error("Cannot create transform for" + property);
+ break;
+ }
+ }
+
+ private static _mergeAffineTransformAnimations(propertyAnimations: Array): Array {
+ var result = new Array();
+
+ var i = 0;
+ var j;
+ var length = propertyAnimations.length;
+ for (; i < length; i++) {
+ if (propertyAnimations[i].property !== _skip) {
+
+ if (!Animation._isAffineTransform(propertyAnimations[i].property)) {
+ // This is not an affine transform animation, so there is nothing to merge.
+ result.push(propertyAnimations[i]);
+ }
+ else {
+
+ // This animation has not been merged anywhere. Create a new transform animation.
+ var newTransformAnimation: common.PropertyAnimation = {
+ target: propertyAnimations[i].target,
+ property: _transform,
+ value: Animation._affineTransform(CGAffineTransformIdentity, propertyAnimations[i].property, propertyAnimations[i].value),
+ duration: propertyAnimations[i].duration,
+ delay: propertyAnimations[i].delay,
+ iterations: propertyAnimations[i].iterations,
+ iosUIViewAnimationCurve: propertyAnimations[i].curve
+ };
+ trace.write("Created new transform animation: " + common.Animation._getAnimationInfo(newTransformAnimation), trace.categories.Animation);
+
+ j = i + 1;
+ if (j < length) {
+ // Merge all compatible affine transform animations to the right into this new animation.
+ for (; j < length; j++) {
+ if (Animation._canBeMerged(propertyAnimations[i], propertyAnimations[j])) {
+ trace.write("Merging animations: " + common.Animation._getAnimationInfo(newTransformAnimation) + " + " + common.Animation._getAnimationInfo(propertyAnimations[j]) + " = ", trace.categories.Animation);
+ trace.write("New native transform is: " + NSStringFromCGAffineTransform(newTransformAnimation.value), trace.categories.Animation);
+ newTransformAnimation.value = Animation._affineTransform(newTransformAnimation.value, propertyAnimations[j].property, propertyAnimations[j].value);
+
+ // Mark that it has been merged so we can skip it on our outer loop.
+ propertyAnimations[j].property = _skip;
+ }
+ }
+ }
+
+ result.push(newTransformAnimation);
+ }
+ }
+ }
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/ui/animation/package.json b/ui/animation/package.json
new file mode 100644
index 000000000..5a168b892
--- /dev/null
+++ b/ui/animation/package.json
@@ -0,0 +1,2 @@
+{ "name" : "animation",
+ "main" : "animation.js" }
\ No newline at end of file
diff --git a/ui/core/view-common.ts b/ui/core/view-common.ts
index 32d02702b..e79f02477 100644
--- a/ui/core/view-common.ts
+++ b/ui/core/view-common.ts
@@ -12,6 +12,7 @@ import styleScope = require("ui/styling/style-scope");
import enums = require("ui/enums");
import utils = require("utils/utils");
import color = require("color");
+import animationModule = require("ui/animation");
export function getViewById(view: View, id: string): View {
if (!view) {
@@ -967,4 +968,14 @@ export class View extends proxy.ProxyObject implements definition.View {
public focus(): boolean {
return undefined;
}
+
+ public animate(animation: animationModule.AnimationDefinition): Promise {
+ return this.createAnimation(animation).play().finished;
+ }
+
+ public createAnimation(animation: animationModule.AnimationDefinition): animationModule.Animation {
+ var that = this;
+ animation.target = that;
+ return new animationModule.Animation([animation]);
+ }
}
\ No newline at end of file
diff --git a/ui/core/view.d.ts b/ui/core/view.d.ts
index d5c62d64c..94ca5efce 100644
--- a/ui/core/view.d.ts
+++ b/ui/core/view.d.ts
@@ -5,6 +5,7 @@ declare module "ui/core/view" {
import gestures = require("ui/gestures");
import color = require("color");
import observable = require("data/observable");
+ import animation = require("ui/animation");
/**
* Gets a child view by id.
@@ -403,6 +404,9 @@ declare module "ui/core/view" {
*/
on(event: "unloaded", callback: (args: observable.EventData) => void, thisArg?: any);
+ public animate(options: animation.AnimationDefinition): Promise;
+ public createAnimation(options: animation.AnimationDefinition): animation.Animation;
+
// Lifecycle events
onLoaded(): void;
onUnloaded(): void;
@@ -521,4 +525,5 @@ declare module "ui/core/view" {
*/
_applyXmlAttribute(attributeName: string, attrValue: any): boolean;
}
+
}
diff --git a/ui/layouts/layout.ts b/ui/layouts/layout.ts
index 6cabfba88..c22ee5e01 100644
--- a/ui/layouts/layout.ts
+++ b/ui/layouts/layout.ts
@@ -1,11 +1,31 @@
import definition = require("ui/layouts/layout");
import view = require("ui/core/view");
import dependencyObservable = require("ui/core/dependency-observable");
+import proxy = require("ui/core/proxy");
+
+function onClipToBoundsPropertyChanged(data: dependencyObservable.PropertyChangeData) {
+ var nativeView = (data.object)._nativeView;
+ if (!nativeView) {
+ return;
+ }
+ var value = data.newValue;
+
+ if (nativeView instanceof UIView) {
+ (nativeView).clipsToBounds = value;
+ }
+ else if (nativeView instanceof android.view.ViewGroup) {
+ (nativeView).setClipChildren(value);
+ }
+}
+
+var clipToBoundsProperty = new dependencyObservable.Property(
+ "clipToBounds",
+ "Layout",
+ new proxy.PropertyMetadata(undefined, dependencyObservable.PropertyMetadataSettings.None, onClipToBoundsPropertyChanged)
+ );
export class Layout extends view.CustomLayoutView implements definition.Layout, view.AddChildFromBuilder {
-
- public static clipToBoundsProperty = new dependencyObservable.Property("clipToBounds", "Layout",
- new dependencyObservable.PropertyMetadata(true, dependencyObservable.PropertyMetadataSettings.None, Layout.onClipToBoundsPropertyChanged));
+ public static clipToBoundsProperty = clipToBoundsProperty;
private _subViews: Array = new Array();
@@ -96,15 +116,10 @@ export class Layout extends view.CustomLayoutView implements definition.Layout,
this.style.paddingLeft = value;
}
- private static onClipToBoundsPropertyChanged(data: dependencyObservable.PropertyChangeData) {
- var layout = data.object;
- var nativeView: Object = layout._nativeView;
- var value = data.newValue;
- if (nativeView instanceof android.view.ViewGroup) {
- (nativeView).setClipChildren(value);
- }
- else if (nativeView instanceof UIView) {
- (nativeView).clipsToBounds = value;
- }
+ get clipToBounds(): boolean {
+ return this._getValue(Layout.clipToBoundsProperty);
+ }
+ set clipToBounds(value: boolean) {
+ this._setValue(Layout.clipToBoundsProperty, value);
}
}