Skip to content

Commit 4b70a7e

Browse files
author
Tsvetan Raikov
committed
Added animations support in Angular 2 RC3
1 parent b9de97a commit 4b70a7e

16 files changed

+461
-20
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { AnimationKeyframe } from '@angular/core/src/animation/animation_keyframe';
2+
import { AnimationPlayer } from '@angular/core/src/animation/animation_player';
3+
import { AnimationStyles } from '@angular/core/src/animation/animation_styles';
4+
import { AnimationDriver } from '@angular/core/src/animation/animation_driver';
5+
import { NativeScriptAnimationPlayer } from './animation-player';
6+
import {View} from "ui/core/view";
7+
import styleProperty = require('ui/styling/style-property');
8+
9+
export class NativeScriptAnimationDriver implements AnimationDriver {
10+
11+
computeStyle(element: any, prop: string): string {
12+
return (<View>element).style._getValue(styleProperty.getPropertyByCssName(prop));
13+
}
14+
15+
animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number, easing: string): AnimationPlayer {
16+
return new NativeScriptAnimationPlayer(element, keyframes, duration, delay, easing);
17+
}
18+
}
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import { AnimationKeyframe } from '@angular/core/src/animation/animation_keyframe';
2+
import { AnimationPlayer } from '@angular/core/src/animation/animation_player';
3+
import { AnimationStyles } from '@angular/core/src/animation/animation_styles';
4+
import { AnimationDriver } from '@angular/core/src/animation/animation_driver';
5+
import { KeyframeAnimation, KeyframeAnimationInfo, KeyframeInfo, KeyframeDeclaration } from 'ui/animation/keyframe-animation';
6+
import { View } from "ui/core/view";
7+
import enums = require("ui/enums");
8+
import styleProperty = require('ui/styling/style-property');
9+
import observable = require('ui/core/dependency-observable');
10+
import types = require("utils/types");
11+
12+
export class NativeScriptAnimationPlayer implements AnimationPlayer {
13+
14+
public parentPlayer: AnimationPlayer;
15+
16+
private _subscriptions: Function[] = [];
17+
private _finished = false;
18+
private animation: KeyframeAnimation;
19+
private target: View;
20+
21+
constructor(element: Node, keyframes: AnimationKeyframe[], duration: number, delay: number, easing: string) {
22+
23+
this.parentPlayer = null;
24+
25+
if (duration === 0) {
26+
duration = 0.01;
27+
}
28+
29+
if (!(element instanceof View)) {
30+
throw new Error("NativeScript: Can animate only Views!");
31+
}
32+
33+
this.target = <any>element;
34+
35+
let keyframeAnimationInfo = new KeyframeAnimationInfo();
36+
keyframeAnimationInfo.duration = duration;
37+
keyframeAnimationInfo.delay = delay;
38+
keyframeAnimationInfo.iterations = 1;
39+
keyframeAnimationInfo.curve = easing ? NativeScriptAnimationPlayer.animationTimingFunctionConverter(easing) : enums.AnimationCurve.ease;
40+
keyframeAnimationInfo.keyframes = new Array<KeyframeInfo>();
41+
keyframeAnimationInfo.isForwards = true;
42+
43+
for (let keyframe of keyframes) {
44+
let keyframeInfo = <KeyframeInfo>{};
45+
keyframeInfo.duration = keyframe.offset;
46+
keyframeInfo.declarations = new Array<KeyframeDeclaration>();
47+
for (let style of keyframe.styles.styles) {
48+
for (let substyle in style) {
49+
let value = style[substyle];
50+
let property = styleProperty.getPropertyByCssName(substyle);
51+
if (property) {
52+
if (typeof value === "string" && property.valueConverter) {
53+
value = property.valueConverter(<string>value);
54+
}
55+
keyframeInfo.declarations.push({ property: property.name, value: value })
56+
}
57+
else if (typeof value === "string" && substyle === "transform") {
58+
NativeScriptAnimationPlayer.parseTransform(<string>value, keyframeInfo);
59+
}
60+
}
61+
}
62+
keyframeAnimationInfo.keyframes.push(keyframeInfo)
63+
}
64+
65+
this.animation = KeyframeAnimation.keyframeAnimationFromInfo(keyframeAnimationInfo, observable.ValueSource.VisualState);
66+
}
67+
68+
onDone(fn: Function): void { this._subscriptions.push(fn); }
69+
70+
private _onFinish() {
71+
if (!this._finished) {
72+
this._finished = true;
73+
this._subscriptions.forEach(fn => fn());
74+
this._subscriptions = [];
75+
}
76+
}
77+
78+
play(): void {
79+
if (this.animation) {
80+
this.animation.play(this.target)
81+
.then(() => { this._onFinish(); })
82+
.catch((e) => {});
83+
}
84+
}
85+
86+
pause(): void {
87+
throw new Error("AnimationPlayer.pause method is not supported!");
88+
}
89+
90+
finish(): void {
91+
throw new Error("AnimationPlayer.finish method is not supported!");
92+
}
93+
94+
reset(): void {
95+
if (this.animation && this.animation.isPlaying) {
96+
this.animation.cancel();
97+
}
98+
}
99+
100+
restart(): void {
101+
this.reset();
102+
this.play();
103+
}
104+
105+
destroy(): void {
106+
this.reset();
107+
this._onFinish();
108+
}
109+
110+
setPosition(p: any): void {
111+
throw new Error("AnimationPlayer.setPosition method is not supported!");
112+
}
113+
114+
getPosition(): number {
115+
return 0;
116+
}
117+
118+
static animationTimingFunctionConverter(value): any {
119+
switch (value) {
120+
case "ease":
121+
return enums.AnimationCurve.ease;
122+
case "linear":
123+
return enums.AnimationCurve.linear;
124+
case "ease-in":
125+
return enums.AnimationCurve.easeIn;
126+
case "ease-out":
127+
return enums.AnimationCurve.easeOut;
128+
case "ease-in-out":
129+
return enums.AnimationCurve.easeInOut;
130+
case "spring":
131+
return enums.AnimationCurve.spring;
132+
default:
133+
if (value.indexOf("cubic-bezier(") === 0) {
134+
let bezierArr = value.substring(13).split(/[,]+/);
135+
if (bezierArr.length !== 4) {
136+
throw new Error("Invalid value for animation: " + value);
137+
}
138+
return enums.AnimationCurve.cubicBezier(
139+
NativeScriptAnimationPlayer.bezieArgumentConverter(bezierArr[0]),
140+
NativeScriptAnimationPlayer.bezieArgumentConverter(bezierArr[1]),
141+
NativeScriptAnimationPlayer.bezieArgumentConverter(bezierArr[2]),
142+
NativeScriptAnimationPlayer.bezieArgumentConverter(bezierArr[3]));
143+
}
144+
else {
145+
throw new Error("Invalid value for animation: " + value);
146+
}
147+
}
148+
}
149+
150+
static bezieArgumentConverter(value): number {
151+
let result = parseFloat(value);
152+
result = Math.max(0.0, result);
153+
result = Math.min(1.0, result);
154+
return result;
155+
}
156+
157+
static transformConverter(value: any): Object {
158+
if (value === "none") {
159+
let operations = {};
160+
operations[value] = value;
161+
return operations;
162+
}
163+
else if (types.isString(value)) {
164+
let operations = {};
165+
let operator = "";
166+
let pos = 0;
167+
while (pos < value.length) {
168+
if (value[pos] === " " || value[pos] === ",") {
169+
pos ++;
170+
}
171+
else if (value[pos] === "(") {
172+
let start = pos + 1;
173+
while (pos < value.length && value[pos] !== ")") {
174+
pos ++;
175+
}
176+
let operand = value.substring(start, pos);
177+
operations[operator] = operand.trim();
178+
operator = "";
179+
pos ++;
180+
}
181+
else {
182+
operator += value[pos ++];
183+
}
184+
}
185+
return operations;
186+
}
187+
else {
188+
return undefined;
189+
}
190+
}
191+
192+
static parseTransform(value: string, animationInfo: KeyframeInfo) {
193+
let newTransform = NativeScriptAnimationPlayer.transformConverter(value);
194+
let array = new Array<styleProperty.KeyValuePair<styleProperty.Property, any>>();
195+
let values = undefined;
196+
for (let transform in newTransform) {
197+
switch (transform) {
198+
case "scaleX":
199+
animationInfo.declarations.push({ property: "scale", value: { x: parseFloat(newTransform[transform]), y: 1 } });
200+
break;
201+
case "scaleY":
202+
animationInfo.declarations.push({ property: "scale", value: { x: 1, y: parseFloat(newTransform[transform]) } });
203+
break;
204+
case "scale":
205+
case "scale3d":
206+
values = newTransform[transform].split(",");
207+
if (values.length === 2 || values.length === 3) {
208+
animationInfo.declarations.push({ property: "scale", value: { x: parseFloat(values[0]), y: parseFloat(values[1]) } });
209+
}
210+
break;
211+
case "translateX":
212+
animationInfo.declarations.push({ property: "translate", value: { x: parseFloat(newTransform[transform]), y: 0 } });
213+
break;
214+
case "translateY":
215+
animationInfo.declarations.push({ property: "translate", value: { x: 0, y: parseFloat(newTransform[transform]) } });
216+
break;
217+
case "translate":
218+
case "translate3d":
219+
values = newTransform[transform].split(",");
220+
if (values.length === 2 || values.length === 3) {
221+
animationInfo.declarations.push({ property: "translate", value: { x: parseFloat(values[0]), y: parseFloat(values[1]) } });
222+
}
223+
break;
224+
case "rotate":
225+
let text = newTransform[transform];
226+
let val = parseFloat(text);
227+
if (text.slice(-3) === "rad") {
228+
val = val * (180.0 / Math.PI);
229+
}
230+
animationInfo.declarations.push({ property: "rotate", value: val });
231+
case "none":
232+
animationInfo.declarations.push({ property: "scale", value: { x: 1, y: 1 } });
233+
animationInfo.declarations.push({ property: "translate", value: { x: 0, y: 0 } });
234+
animationInfo.declarations.push({ property: "rotate", value: 0 });
235+
break;
236+
}
237+
}
238+
return array;
239+
}
240+
}

nativescript-angular/application.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import {Observable} from "rxjs";
3535

3636
export type ProviderArray = Array<Type | Provider | any[]>;
3737

38-
import {defaultPageProvider, defaultDeviceProvider} from "./platform-providers";
38+
import {defaultPageProvider, defaultDeviceProvider, defaultAnimationDriverProvider} from "./platform-providers";
3939

4040
import * as nativescriptIntl from "nativescript-intl";
4141
global.Intl = nativescriptIntl;
@@ -97,6 +97,7 @@ export function bootstrap(appComponentType: any,
9797

9898
defaultPageProvider,
9999
defaultDeviceProvider,
100+
defaultAnimationDriverProvider,
100101
NativeScriptRootRenderer,
101102
provide(RootRenderer, { useClass: NativeScriptRootRenderer }),
102103
NativeScriptRenderer,

nativescript-angular/platform-providers.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import {topmost} from 'ui/frame';
22
import {Page} from 'ui/page';
33
import {provide, Provider, OpaqueToken} from '@angular/core/src/di';
44
import {Device, ScreenMetrics, device, screen} from "platform";
5+
import {NativeScriptAnimationDriver} from './animation-driver';
56

67
export const APP_ROOT_VIEW = new OpaqueToken('App Root View');
78
export const DEVICE = new OpaqueToken('platfrom device');
9+
export const ANIMATION_DRIVER = new OpaqueToken('animation driver');
810

911
export const defaultPageProvider = provide(Page, { useFactory: getDefaultPage });
1012

@@ -19,3 +21,4 @@ export function getDefaultPage(): Page {
1921

2022
export const defaultDeviceProvider = provide(DEVICE, { useValue: device });
2123

24+
export const defaultAnimationDriverProvider = provide(ANIMATION_DRIVER, { useClass: NativeScriptAnimationDriver });

nativescript-angular/renderer.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import {
88
import { AnimationKeyframe } from '@angular/core/src/animation/animation_keyframe';
99
import { AnimationPlayer } from '@angular/core/src/animation/animation_player';
1010
import { AnimationStyles } from '@angular/core/src/animation/animation_styles';
11-
import {APP_ROOT_VIEW, DEVICE} from "./platform-providers";
11+
import { AnimationDriver } from '@angular/core/src/animation/animation_driver';
12+
import {APP_ROOT_VIEW, DEVICE, ANIMATION_DRIVER} from "./platform-providers";
1213
import {isBlank} from '@angular/core/src/facade/lang';
1314
import {CONTENT_ATTR} from '@angular/platform-browser/src/dom/dom_renderer';
1415
import {View} from "ui/core/view";
@@ -24,10 +25,12 @@ import { Device } from "platform";
2425
export class NativeScriptRootRenderer implements RootRenderer {
2526
private _rootView: View = null;
2627
private _viewUtil: ViewUtil;
28+
private _animationDriver: AnimationDriver;
2729

28-
constructor( @Optional() @Inject(APP_ROOT_VIEW) rootView: View, @Inject(DEVICE) device: Device) {
30+
constructor( @Optional() @Inject(APP_ROOT_VIEW) rootView: View, @Inject(DEVICE) device: Device, @Inject(ANIMATION_DRIVER) animationDriver) {
2931
this._rootView = rootView;
3032
this._viewUtil = new ViewUtil(device);
33+
this._animationDriver = animationDriver;
3134
}
3235

3336
private _registeredComponents: Map<string, NativeScriptRenderer> = new Map<string, NativeScriptRenderer>();
@@ -50,7 +53,7 @@ export class NativeScriptRootRenderer implements RootRenderer {
5053
renderComponent(componentProto: RenderComponentType): Renderer {
5154
var renderer = this._registeredComponents.get(componentProto.id);
5255
if (isBlank(renderer)) {
53-
renderer = new NativeScriptRenderer(this, componentProto);
56+
renderer = new NativeScriptRenderer(this, componentProto, this._animationDriver);
5457
this._registeredComponents.set(componentProto.id, renderer);
5558
}
5659
return renderer;
@@ -67,7 +70,7 @@ export class NativeScriptRenderer extends Renderer {
6770
return this.rootRenderer.viewUtil;
6871
}
6972

70-
constructor(private _rootRenderer: NativeScriptRootRenderer, private componentProto: RenderComponentType) {
73+
constructor(private _rootRenderer: NativeScriptRootRenderer, private componentProto: RenderComponentType, private animationDriver: AnimationDriver) {
7174
super();
7275
this.rootRenderer = _rootRenderer;
7376
let page = this.rootRenderer.page;
@@ -121,7 +124,6 @@ export class NativeScriptRenderer extends Renderer {
121124
viewRootNodes.forEach((node, index) => {
122125
const childIndex = insertPosition + index + 1;
123126
this.viewUtil.insertChild(parent, node, childIndex);
124-
this.animateNodeEnter(node);
125127
});
126128
}
127129

@@ -130,16 +132,9 @@ export class NativeScriptRenderer extends Renderer {
130132
for (var i = 0; i < viewRootNodes.length; i++) {
131133
var node = viewRootNodes[i];
132134
this.viewUtil.removeChild(<NgView>node.parent, node);
133-
this.animateNodeLeave(node);
134135
}
135136
}
136137

137-
animateNodeEnter(node: NgView) {
138-
}
139-
140-
animateNodeLeave(node: NgView) {
141-
}
142-
143138
public destroyView(hostElement: NgView, viewAllNodes: NgView[]) {
144139
traceLog("NativeScriptRenderer.destroyView");
145140
// Seems to be called on component dispose only (router outlet)
@@ -231,6 +226,7 @@ export class NativeScriptRenderer extends Renderer {
231226
}
232227

233228
public animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number, easing: string): AnimationPlayer {
234-
throw new Error("NativeScriptRenderer.animate() - Not implemented");
229+
let player = this.animationDriver.animate(element, startingStyles, keyframes, duration, delay, easing);
230+
return player;
235231
}
236232
}

0 commit comments

Comments
 (0)