Skip to content

Commit 2107ffc

Browse files
committed
feat(animations): introduce NativeScriptAnimationsModule
same as #704 but using modules 2.5 style properties API's
1 parent c4a48e7 commit 2107ffc

File tree

9 files changed

+590
-5
lines changed

9 files changed

+590
-5
lines changed

Diff for: nativescript-angular/animations.ts

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { NgModule, Injectable, NgZone, Provider, RendererFactory2 } from "@angular/core";
2+
3+
import {
4+
AnimationDriver,
5+
ɵAnimationEngine as AnimationEngine,
6+
ɵAnimationStyleNormalizer as AnimationStyleNormalizer,
7+
ɵWebAnimationsStyleNormalizer as WebAnimationsStyleNormalizer
8+
} from "@angular/animations/browser";
9+
10+
import { ɵAnimationRendererFactory as AnimationRendererFactory } from "@angular/platform-browser/animations";
11+
12+
import { NativeScriptAnimationEngine } from "./animations/animation-engine";
13+
import { NativeScriptAnimationDriver } from "./animations/animation-driver";
14+
import { NativeScriptModule } from "./nativescript.module";
15+
import { NativeScriptRendererFactory } from "./renderer";
16+
17+
@Injectable()
18+
export class InjectableAnimationEngine extends NativeScriptAnimationEngine {
19+
constructor(driver: AnimationDriver, normalizer: AnimationStyleNormalizer) {
20+
super(driver, normalizer);
21+
}
22+
}
23+
24+
export function instantiateSupportedAnimationDriver() {
25+
return new NativeScriptAnimationDriver();
26+
}
27+
28+
export function instantiateRendererFactory(
29+
renderer: NativeScriptRendererFactory, engine: AnimationEngine, zone: NgZone) {
30+
return new AnimationRendererFactory(renderer, engine, zone);
31+
}
32+
33+
export function instanciateDefaultStyleNormalizer() {
34+
return new WebAnimationsStyleNormalizer();
35+
}
36+
37+
export const NATIVESCRIPT_ANIMATIONS_PROVIDERS: Provider[] = [
38+
{provide: AnimationDriver, useFactory: instantiateSupportedAnimationDriver},
39+
{provide: AnimationStyleNormalizer, useFactory: instanciateDefaultStyleNormalizer},
40+
{provide: AnimationEngine, useClass: InjectableAnimationEngine}, {
41+
provide: RendererFactory2,
42+
useFactory: instantiateRendererFactory,
43+
deps: [NativeScriptRendererFactory, AnimationEngine, NgZone]
44+
}
45+
];
46+
47+
@NgModule({
48+
imports: [NativeScriptModule],
49+
providers: NATIVESCRIPT_ANIMATIONS_PROVIDERS,
50+
})
51+
export class NativeScriptAnimationsModule {
52+
}

Diff for: nativescript-angular/animations/animation-driver.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { AnimationPlayer } from "@angular/animations";
2+
import { getPropertyByCssName } from "ui/styling/style-property";
3+
4+
import { NgView } from "../element-registry";
5+
import { NativeScriptAnimationPlayer } from "./animation-player";
6+
import { Keyframe } from "./utils";
7+
8+
export abstract class AnimationDriver {
9+
abstract animate(
10+
element: any,
11+
keyframes: Keyframe[],
12+
duration: number,
13+
delay: number,
14+
easing: string
15+
): AnimationPlayer;
16+
}
17+
18+
export class NativeScriptAnimationDriver implements AnimationDriver {
19+
computeStyle(element: NgView, prop: string): string {
20+
return element.style._getValue(getPropertyByCssName(prop));
21+
}
22+
23+
animate(
24+
element: NgView,
25+
keyframes: Keyframe[],
26+
duration: number,
27+
delay: number,
28+
easing: string
29+
): AnimationPlayer {
30+
return new NativeScriptAnimationPlayer(
31+
element, keyframes, duration, delay, easing);
32+
}
33+
}

Diff for: nativescript-angular/animations/animation-engine.ts

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { ɵDomAnimationEngine as DomAnimationEngine } from "@angular/animations/browser";
2+
import { AnimationEvent, AnimationPlayer } from "@angular/animations";
3+
4+
import { NgView } from "../element-registry";
5+
import {
6+
copyArray,
7+
cssClasses,
8+
deleteFromArrayMap,
9+
eraseStylesOverride,
10+
getOrSetAsInMap,
11+
makeAnimationEvent,
12+
optimizeGroupPlayer,
13+
setStyles,
14+
} from "./dom-utils";
15+
16+
const MARKED_FOR_ANIMATION = "ng-animate";
17+
18+
interface QueuedAnimationTransitionTuple {
19+
element: NgView;
20+
player: AnimationPlayer;
21+
triggerName: string;
22+
event: AnimationEvent;
23+
}
24+
25+
// we are extending Angular's animation engine and
26+
// overriding a few methods that work on the DOM
27+
export class NativeScriptAnimationEngine extends DomAnimationEngine {
28+
// this method is almost completely copied from
29+
// the original animation engine, just replaced
30+
// a few method invocations with overriden ones
31+
animateTransition(element: NgView, instruction: any): AnimationPlayer {
32+
const triggerName = instruction.triggerName;
33+
34+
let previousPlayers: AnimationPlayer[];
35+
if (instruction.isRemovalTransition) {
36+
previousPlayers = this._onRemovalTransitionOverride(element);
37+
} else {
38+
previousPlayers = [];
39+
const existingTransitions = this._getTransitionAnimation(element);
40+
const existingPlayer = existingTransitions ? existingTransitions[triggerName] : null;
41+
if (existingPlayer) {
42+
previousPlayers.push(existingPlayer);
43+
}
44+
}
45+
46+
// it's important to do this step before destroying the players
47+
// so that the onDone callback below won"t fire before this
48+
eraseStylesOverride(element, instruction.fromStyles);
49+
50+
// we first run this so that the previous animation player
51+
// data can be passed into the successive animation players
52+
let totalTime = 0;
53+
const players = instruction.timelines.map(timelineInstruction => {
54+
totalTime = Math.max(totalTime, timelineInstruction.totalTime);
55+
return (<any>this)._buildPlayer(element, timelineInstruction, previousPlayers);
56+
});
57+
58+
previousPlayers.forEach(previousPlayer => previousPlayer.destroy());
59+
const player = optimizeGroupPlayer(players);
60+
player.onDone(() => {
61+
player.destroy();
62+
const elmTransitionMap = this._getTransitionAnimation(element);
63+
if (elmTransitionMap) {
64+
delete elmTransitionMap[triggerName];
65+
if (Object.keys(elmTransitionMap).length === 0) {
66+
(<any>this)._activeTransitionAnimations.delete(element);
67+
}
68+
}
69+
deleteFromArrayMap((<any>this)._activeElementAnimations, element, player);
70+
setStyles(element, instruction.toStyles);
71+
});
72+
73+
const elmTransitionMap = getOrSetAsInMap((<any>this)._activeTransitionAnimations, element, {});
74+
elmTransitionMap[triggerName] = player;
75+
76+
this._queuePlayerOverride(
77+
element, triggerName, player,
78+
makeAnimationEvent(
79+
element, triggerName, instruction.fromState, instruction.toState,
80+
null, // this will be filled in during event creation
81+
totalTime));
82+
83+
return player;
84+
}
85+
86+
// overriden to use eachChild method of View
87+
// instead of DOM querySelectorAll
88+
private _onRemovalTransitionOverride(element: NgView): AnimationPlayer[] {
89+
// when a parent animation is set to trigger a removal we want to
90+
// find all of the children that are currently animating and clear
91+
// them out by destroying each of them.
92+
let elms = [];
93+
element._eachLayoutView(child => {
94+
if (cssClasses(<NgView>child).get(MARKED_FOR_ANIMATION)) {
95+
elms.push(child);
96+
}
97+
98+
return true;
99+
});
100+
101+
for (let i = 0; i < elms.length; i++) {
102+
const elm = elms[i];
103+
const activePlayers = this._getElementAnimation(elm);
104+
if (activePlayers) {
105+
activePlayers.forEach(player => player.destroy());
106+
}
107+
108+
const activeTransitions = this._getTransitionAnimation(elm);
109+
if (activeTransitions) {
110+
Object.keys(activeTransitions).forEach(triggerName => {
111+
const player = activeTransitions[triggerName];
112+
if (player) {
113+
player.destroy();
114+
}
115+
});
116+
}
117+
}
118+
119+
// we make a copy of the array because the actual source array is modified
120+
// each time a player is finished/destroyed (the forEach loop would fail otherwise)
121+
return copyArray(this._getElementAnimation(element));
122+
}
123+
124+
// overriden to use cssClasses method to access native element's styles
125+
// instead of DOM element's classList
126+
private _queuePlayerOverride(
127+
element: NgView, triggerName: string, player: AnimationPlayer, event: AnimationEvent) {
128+
const tuple = <QueuedAnimationTransitionTuple>{ element, player, triggerName, event };
129+
(<any>this)._queuedTransitionAnimations.push(tuple);
130+
player.init();
131+
132+
cssClasses(element).set(MARKED_FOR_ANIMATION, true);
133+
player.onDone(() => cssClasses(element).set(MARKED_FOR_ANIMATION, false));
134+
}
135+
136+
private _getElementAnimation(element: NgView) {
137+
return (<any>this)._activeElementAnimations.get(element);
138+
}
139+
140+
private _getTransitionAnimation(element: NgView) {
141+
return (<any>this)._activeTransitionAnimations.get(element);
142+
}
143+
}

Diff for: nativescript-angular/animations/animation-player.ts

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { AnimationPlayer } from "@angular/animations";
2+
import { KeyframeAnimation, KeyframeAnimationInfo } from "ui/animation/keyframe-animation";
3+
import { ValueSource } from "ui/core/dependency-observable";
4+
import { Keyframe, getAnimationCurve, parseAnimationKeyframe } from "./utils";
5+
6+
import { NgView } from "../element-registry";
7+
8+
export class NativeScriptAnimationPlayer implements AnimationPlayer {
9+
public parentPlayer: AnimationPlayer = null;
10+
11+
private _startSubscriptions: Function[] = [];
12+
private _doneSubscriptions: Function[] = [];
13+
private _finished = false;
14+
private _started = false;
15+
private animation: KeyframeAnimation;
16+
17+
constructor(
18+
private target: NgView,
19+
keyframes: Keyframe[],
20+
duration: number,
21+
delay: number,
22+
easing: string
23+
) {
24+
this.initKeyframeAnimation(keyframes, duration, delay, easing);
25+
}
26+
27+
init(): void {
28+
}
29+
30+
hasStarted(): boolean {
31+
return this._started;
32+
}
33+
34+
onStart(fn: Function): void { this._startSubscriptions.push(fn); }
35+
onDone(fn: Function): void { this._doneSubscriptions.push(fn); }
36+
onDestroy(fn: Function): void { this._doneSubscriptions.push(fn); }
37+
38+
play(): void {
39+
if (!this.animation) {
40+
return;
41+
}
42+
43+
if (!this._started) {
44+
this._started = true;
45+
this._startSubscriptions.forEach(fn => fn());
46+
this._startSubscriptions = [];
47+
}
48+
49+
this.animation.play(this.target)
50+
.then(() => this.onFinish())
51+
.catch((_e) => { });
52+
}
53+
54+
pause(): void {
55+
throw new Error("AnimationPlayer.pause method is not supported!");
56+
}
57+
58+
finish(): void {
59+
throw new Error("AnimationPlayer.finish method is not supported!");
60+
}
61+
62+
reset(): void {
63+
if (this.animation && this.animation.isPlaying) {
64+
this.animation.cancel();
65+
}
66+
}
67+
68+
restart(): void {
69+
this.reset();
70+
this.play();
71+
}
72+
73+
destroy(): void {
74+
this.reset();
75+
this.onFinish();
76+
}
77+
78+
setPosition(_p: any): void {
79+
throw new Error("AnimationPlayer.setPosition method is not supported!");
80+
}
81+
82+
getPosition(): number {
83+
return 0;
84+
}
85+
86+
private initKeyframeAnimation(keyframes: Keyframe[], duration: number, delay: number, easing: string) {
87+
let info = new KeyframeAnimationInfo();
88+
info.isForwards = true;
89+
info.iterations = 1;
90+
info.duration = duration === 0 ? 0.01 : duration;
91+
info.delay = delay;
92+
info.curve = getAnimationCurve(easing);
93+
info.keyframes = keyframes.map(parseAnimationKeyframe);
94+
95+
this.animation = KeyframeAnimation.keyframeAnimationFromInfo(info, ValueSource.VisualState);
96+
}
97+
98+
private onFinish() {
99+
if (!this._finished) {
100+
this._finished = true;
101+
this._started = false;
102+
this._doneSubscriptions.forEach(fn => fn());
103+
this._doneSubscriptions = [];
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)