mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 19:26:42 +08:00
feat: Add 3D rotation to view - takeover of PR# 5950 (#8136)
* feat: add 3d rotation * chore: fix build errors * chore: fix tslint errors * chore: add @types/chai dev dep * chore: unused import cleanup * chore: update tests for x,y rotation * chore: rebase upstream/master * fix: iOS Affine Transform test verification * feat(css): Added optional css-tree parser (#8076) * feat(css): Added optional css-tree parser * test: css-tree parser compat tests * test: more css-tree compat tests * feat(dialogs): Setting the size of popup dialog thru dialog options (#8041) * Added iOS specific height and width attributes to ShowModalOptions * Set the height and width of the popup dialog to the presenting controller * dialog options ios attributes presentationStyle, height & width are made optional * Updated NativeScript.api.md for public API changes * Update with git properties * Public API * CLA update * fix: use iOS native-helper for 3d-rotate * test: Fix tests using _getTransformMismatchError * fix: view.__hasTransfrom not set updating properly * test: fix css-animations test page Co-authored-by: Alexander Vakrilov <alexander.vakrilov@gmail.com> Co-authored-by: Darin Dimitrov <darin.dimitrov@gmail.com> Co-authored-by: Shailesh Lolam <slolam@live.com> Co-authored-by: Dimitar Topuzov <dtopuzov@gmail.com>
This commit is contained in:

committed by
Alexander Vakrilov

parent
8550c3293d
commit
e8f5ac8522
@ -243,7 +243,8 @@ export interface AnimationDefinition {
|
||||
|
||||
opacity?: number;
|
||||
|
||||
rotate?: number;
|
||||
// Warning: (ae-forgotten-export) The symbol "Point3D" needs to be exported by the entry point index.d.ts
|
||||
rotate?: number | Point3D;
|
||||
|
||||
scale?: Pair;
|
||||
|
||||
@ -2086,6 +2087,8 @@ export class Style extends Observable {
|
||||
// (undocumented)
|
||||
public paddingTop: Length;
|
||||
// (undocumented)
|
||||
public perspective: number;
|
||||
// (undocumented)
|
||||
public placeholderColor: Color;
|
||||
// Warning: (ae-forgotten-export) The symbol "PropertyBagClass" needs to be exported by the entry point index.d.ts
|
||||
public readonly PropertyBag: PropertyBagClass;
|
||||
@ -2094,6 +2097,10 @@ export class Style extends Observable {
|
||||
// (undocumented)
|
||||
public rotate: number;
|
||||
// (undocumented)
|
||||
public rotateX: number;
|
||||
// (undocumented)
|
||||
public rotateY: number;
|
||||
// (undocumented)
|
||||
public scaleX: number;
|
||||
// (undocumented)
|
||||
public scaleY: number;
|
||||
@ -2702,12 +2709,15 @@ export abstract class View extends ViewBase {
|
||||
opacity: number;
|
||||
originX: number;
|
||||
originY: number;
|
||||
perspective: number;
|
||||
// (undocumented)
|
||||
_redrawNativeBackground(value: any): void;
|
||||
// (undocumented)
|
||||
_removeAnimation(animation: Animation): boolean;
|
||||
public static resolveSizeAndState(size: number, specSize: number, specMode: number, childMeasuredState: number): number;
|
||||
rotate: number;
|
||||
rotateX: number;
|
||||
rotateY: number;
|
||||
scaleX: number;
|
||||
scaleY: number;
|
||||
_setCurrentLayoutBounds(left: number, top: number, right: number, bottom: number): { boundsChanged: boolean, sizeChanged: boolean };
|
||||
|
40
e2e/animation/app/3d-rotate/page.ts
Normal file
40
e2e/animation/app/3d-rotate/page.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { EventData, Page } from "tns-core-modules/ui/page";
|
||||
import { View } from "tns-core-modules/ui/core/view";
|
||||
import { Point3D } from "tns-core-modules/ui/animation/animation";
|
||||
|
||||
let view: View;
|
||||
|
||||
export function pageLoaded(args: EventData) {
|
||||
const page = <Page>args.object;
|
||||
view = page.getViewById<View>("view");
|
||||
}
|
||||
|
||||
export function onAnimateX(args: EventData) {
|
||||
rotate({ x: 360, y: 0, z: 0 });
|
||||
}
|
||||
|
||||
export function onAnimateY(args: EventData) {
|
||||
rotate({ x: 0, y: 360, z: 0 });
|
||||
}
|
||||
|
||||
export function onAnimateZ(args: EventData) {
|
||||
rotate({ x: 0, y: 0, z: 360 });
|
||||
}
|
||||
|
||||
export function onAnimateXYZ(args: EventData) {
|
||||
rotate({ x: 360, y: 360, z: 360 });
|
||||
}
|
||||
|
||||
async function rotate(rotate: Point3D) {
|
||||
await view.animate({
|
||||
rotate,
|
||||
duration: 1000
|
||||
});
|
||||
reset();
|
||||
}
|
||||
|
||||
function reset() {
|
||||
view.rotate = 0;
|
||||
view.rotateX = 0;
|
||||
view.rotateY = 0;
|
||||
}
|
24
e2e/animation/app/3d-rotate/page.xml
Normal file
24
e2e/animation/app/3d-rotate/page.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="pageLoaded">
|
||||
<ActionBar title="Rotate" />
|
||||
|
||||
<GridLayout rows="auto auto auto auto *" columns="* * *">
|
||||
<Image src="~/res/icon_100x100.png" width="30" height="30" col="0" row="0" rotateX="60"/>
|
||||
<Image src="~/res/icon_100x100.png" width="30" height="30" col="1" row="0" rotateY="60"/>
|
||||
<Image src="~/res/icon_100x100.png" width="30" height="30" col="2" row="0" rotate="60"/>
|
||||
|
||||
<Button text="X" tap="onAnimateX" col="0" row="1"/>
|
||||
<Button text="Y" tap="onAnimateY" col="1" row="1"/>
|
||||
<Button text="Z" tap="onAnimateZ" col="2" row="1"/>
|
||||
|
||||
<Image src="~/res/icon_100x100.png" width="60" height="60" horizontalAlignment="center"
|
||||
colSpan="3" row="2" rotate="60" rotateX="60" rotateY="60"/>
|
||||
|
||||
<Button text="XYZ" tap="onAnimateXYZ" row="3" colSpan="3"/>
|
||||
|
||||
<AbsoluteLayout width="300" height="300" clipToBounds="true" backgroundColor="LightGray" row="4" colSpan="3">
|
||||
<Image id="view" src="~/res/icon_100x100.png"
|
||||
width="100" height="100"
|
||||
left="100" top="100"/>
|
||||
</AbsoluteLayout>
|
||||
</GridLayout>
|
||||
</Page>
|
75
e2e/animation/app/css-animations/3d-rotate/page.css
Normal file
75
e2e/animation/app/css-animations/3d-rotate/page.css
Normal file
@ -0,0 +1,75 @@
|
||||
.rotate-x {
|
||||
rotateX: 60;
|
||||
}
|
||||
|
||||
.rotate-y {
|
||||
rotateY: 60;
|
||||
}
|
||||
|
||||
.rotate-z {
|
||||
rotate: 60;
|
||||
}
|
||||
|
||||
.original {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.animate-x {
|
||||
animation-name: rotateX;
|
||||
animation-duration: 2s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
.animate-y {
|
||||
animation-name: rotateY;
|
||||
animation-duration: 2s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
.animate-z {
|
||||
animation-name: rotateZ;
|
||||
animation-duration: 2s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
.animate-xyz-3d {
|
||||
animation-name: rotateXYZ3D;
|
||||
animation-duration: 2s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
.animate-xyz {
|
||||
animation-name: rotateXYZ;
|
||||
animation-duration: 2s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
@keyframes rotateX {
|
||||
from { transform: none; }
|
||||
50% { transform: rotateX(60) }
|
||||
to { transform: none; }
|
||||
}
|
||||
|
||||
@keyframes rotateY {
|
||||
from { transform: none; }
|
||||
50% { transform: rotateY(60) }
|
||||
to { transform: none; }
|
||||
}
|
||||
|
||||
@keyframes rotateZ {
|
||||
from { transform: none; }
|
||||
50% { transform: rotate(60) }
|
||||
to { transform: none; }
|
||||
}
|
||||
|
||||
@keyframes rotateXYZ3D {
|
||||
from { transform: none; }
|
||||
50% { transform: rotate3d(60, 60, 60) }
|
||||
to { transform: none; }
|
||||
}
|
||||
|
||||
@keyframes rotateXYZ {
|
||||
from { transform: none; }
|
||||
50% { transform: rotateX(60) rotateY(60) rotate(60) }
|
||||
to { transform: none; }
|
||||
}
|
35
e2e/animation/app/css-animations/3d-rotate/page.ts
Normal file
35
e2e/animation/app/css-animations/3d-rotate/page.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { EventData, Page } from "tns-core-modules/ui/page";
|
||||
import { View } from "tns-core-modules/ui/core/view";
|
||||
import { Point3D } from "tns-core-modules/ui/animation/animation";
|
||||
|
||||
let view: View;
|
||||
|
||||
export function pageLoaded(args: EventData) {
|
||||
const page = <Page>args.object;
|
||||
view = page.getViewById<View>("view");
|
||||
}
|
||||
|
||||
export function onAnimateX(args: EventData) {
|
||||
view.className = "original";
|
||||
view.className = "animate-x";
|
||||
}
|
||||
|
||||
export function onAnimateY(args: EventData) {
|
||||
view.className = "original";
|
||||
view.className = "animate-y";
|
||||
}
|
||||
|
||||
export function onAnimateZ(args: EventData) {
|
||||
view.className = "original";
|
||||
view.className = "animate-z";
|
||||
}
|
||||
|
||||
export function onAnimateXYZ3D(args: EventData) {
|
||||
view.className = "original";
|
||||
view.className = "animate-xyz-3d";
|
||||
}
|
||||
|
||||
export function onAnimateXYZ(args: EventData) {
|
||||
view.className = "original";
|
||||
view.className = "animate-xyz";
|
||||
}
|
22
e2e/animation/app/css-animations/3d-rotate/page.xml
Normal file
22
e2e/animation/app/css-animations/3d-rotate/page.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="pageLoaded">
|
||||
<ActionBar title="Rotate" />
|
||||
|
||||
<GridLayout rows="auto auto auto *" columns="* * *">
|
||||
<Image src="~/res/icon_100x100.png" width="30" height="30" col="0" row="0" class="rotate-x"/>
|
||||
<Image src="~/res/icon_100x100.png" width="30" height="30" col="1" row="0" class="rotate-y"/>
|
||||
<Image src="~/res/icon_100x100.png" width="30" height="30" col="2" row="0" class="rotate-z"/>
|
||||
|
||||
<Button text="X" tap="onAnimateX" col="0" row="1"/>
|
||||
<Button text="Y" tap="onAnimateY" col="1" row="1"/>
|
||||
<Button text="Z" tap="onAnimateZ" col="2" row="1"/>
|
||||
|
||||
<Button text="XYZ" tap="onAnimateXYZ" row="2" col="0"/>
|
||||
<Button text="XYZ-3D" tap="onAnimateXYZ3D" row="2" col="1"/>
|
||||
|
||||
<AbsoluteLayout width="300" height="300" clipToBounds="true" backgroundColor="LightGray" row="3" colSpan="3">
|
||||
<Image id="view" src="~/res/icon_100x100.png"
|
||||
width="100" height="100"
|
||||
left="100" top="100" />
|
||||
</AbsoluteLayout>
|
||||
</GridLayout>
|
||||
</Page>
|
@ -11,6 +11,6 @@ export function pageLoaded(args: EventData) {
|
||||
export function onButtonTap(args: EventData) {
|
||||
const clickedButton = <Button>args.object;
|
||||
|
||||
const destination = clickedButton.text + "/page";
|
||||
const destination = "css-animations/" + clickedButton.text + "/page";
|
||||
currentFrame.navigate(destination);
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
<Button text="settings" tap="onButtonTap"/>
|
||||
<Button text="visual-states" tap="onButtonTap"/>
|
||||
<Button text="initial-animation" tap="onButtonTap"/>
|
||||
<Button text="3d-rotate" tap="onButtonTap"/>
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</Page>
|
||||
|
@ -19,6 +19,7 @@
|
||||
<Button text="infinite" tap="onButtonTap" />
|
||||
<Button text="animation-curves" tap="onButtonTap" />
|
||||
<Button text="css-animations" tap="onButtonTap" />
|
||||
<Button text="3d-rotate" tap="onButtonTap" />
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
|
||||
|
@ -16,12 +16,15 @@ const TRANSFORM_MATRIXES = {
|
||||
0, 1, y,
|
||||
0, 0, 1,
|
||||
],
|
||||
"rotate": angleInDeg => {
|
||||
const angleInRad = degreesToRadians(angleInDeg);
|
||||
"rotate": ({ x, y, z }) => {
|
||||
// TODO: Handle rotations over X and Y axis
|
||||
const radZ = degreesToRadians(z);
|
||||
const cosZ = Math.cos(radZ);
|
||||
const sinZ = Math.sin(radZ);
|
||||
|
||||
return [
|
||||
Math.cos(angleInRad), -Math.sin(angleInRad), 0,
|
||||
Math.sin(angleInRad), Math.cos(angleInRad), 0,
|
||||
cosZ, -sinZ, 0,
|
||||
sinZ, cosZ, 0,
|
||||
0, 0, 1,
|
||||
];
|
||||
},
|
||||
@ -43,6 +46,7 @@ export function multiplyAffine2d(m1: number[], m2: number[]): number[] {
|
||||
];
|
||||
}
|
||||
|
||||
// TODO: Decompose rotations over X and Y axis
|
||||
export function decompose2DTransformMatrix(matrix: number[])
|
||||
: TransformFunctionsInfo {
|
||||
|
||||
@ -52,7 +56,7 @@ export function decompose2DTransformMatrix(matrix: number[])
|
||||
const determinant = A * D - B * C;
|
||||
const translate = { x: E || 0, y: F || 0 };
|
||||
|
||||
// rewrite with obj desctructuring using the identity matrix
|
||||
// rewrite with obj destructuring using the identity matrix
|
||||
let rotate = 0;
|
||||
let scale = { x: 1, y: 1 };
|
||||
if (A || B) {
|
||||
@ -67,7 +71,7 @@ export function decompose2DTransformMatrix(matrix: number[])
|
||||
|
||||
rotate = radiansToDegrees(rotate);
|
||||
|
||||
return { translate, rotate, scale };
|
||||
return { translate, rotate: { x: 0, y: 0, z: rotate }, scale };
|
||||
}
|
||||
|
||||
function verifyTransformMatrix(matrix: number[]) {
|
||||
|
@ -26,6 +26,7 @@
|
||||
"tslib": "1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "~4.2.5",
|
||||
"@types/node": "~10.12.18",
|
||||
"tns-platform-declarations": "next"
|
||||
},
|
||||
|
@ -1,7 +1,8 @@
|
||||
// Types.
|
||||
import {
|
||||
CubicBezierAnimationCurve as CubicBezierAnimationCurveDefinition,
|
||||
Animation as AnimationBaseDefinition
|
||||
Animation as AnimationBaseDefinition,
|
||||
Point3D
|
||||
} from ".";
|
||||
import {
|
||||
AnimationDefinition, AnimationPromise as AnimationPromiseDefinition,
|
||||
@ -150,24 +151,30 @@ export abstract class AnimationBase implements AnimationBaseDefinition {
|
||||
}
|
||||
|
||||
for (let item in animationDefinition) {
|
||||
if (animationDefinition[item] === undefined) {
|
||||
const value = animationDefinition[item];
|
||||
if (value === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((item === Properties.opacity ||
|
||||
item === Properties.rotate ||
|
||||
item === "duration" ||
|
||||
item === "delay" ||
|
||||
item === "iterations") && typeof animationDefinition[item] !== "number") {
|
||||
throw new Error(`Property ${item} must be valid number. Value: ${animationDefinition[item]}`);
|
||||
item === "iterations") && typeof value !== "number") {
|
||||
throw new Error(`Property ${item} must be valid number. Value: ${value}`);
|
||||
} else if ((item === Properties.scale || item === Properties.translate) &&
|
||||
(typeof (<Pair>animationDefinition[item]).x !== "number" || typeof (<Pair>animationDefinition[item]).y !== "number")) {
|
||||
throw new Error(`Property ${item} must be valid Pair. Value: ${animationDefinition[item]}`);
|
||||
(typeof (<Pair>value).x !== "number" || typeof (<Pair>value).y !== "number")) {
|
||||
throw new Error(`Property ${item} must be valid Pair. Value: ${value}`);
|
||||
} else if (item === Properties.backgroundColor && !Color.isValid(animationDefinition.backgroundColor)) {
|
||||
throw new Error(`Property ${item} must be valid color. Value: ${animationDefinition[item]}`);
|
||||
throw new Error(`Property ${item} must be valid color. Value: ${value}`);
|
||||
} else if (item === Properties.width || item === Properties.height) {
|
||||
// Coerce input into a PercentLength object in case it's a string.
|
||||
animationDefinition[item] = PercentLength.parse(<any>animationDefinition[item]);
|
||||
animationDefinition[item] = PercentLength.parse(<any>value);
|
||||
} else if (item === Properties.rotate) {
|
||||
const rotate: number | Point3D = value;
|
||||
if ((typeof rotate !== "number") &&
|
||||
!(typeof rotate.x === "number" && typeof rotate.y === "number" && typeof rotate.z === "number")) {
|
||||
throw new Error(`Property ${rotate} must be valid number or Point3D. Value: ${value}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -228,10 +235,18 @@ export abstract class AnimationBase implements AnimationBaseDefinition {
|
||||
|
||||
// rotate
|
||||
if (animationDefinition.rotate !== undefined) {
|
||||
// Make sure the value of the rotation property is always Point3D
|
||||
let rotationValue: Point3D;
|
||||
if (typeof animationDefinition.rotate === "number") {
|
||||
rotationValue = { x: 0, y: 0, z: animationDefinition.rotate };
|
||||
} else {
|
||||
rotationValue = animationDefinition.rotate;
|
||||
}
|
||||
|
||||
propertyAnimations.push({
|
||||
target: animationDefinition.target,
|
||||
property: Properties.rotate,
|
||||
value: animationDefinition.rotate,
|
||||
value: rotationValue,
|
||||
duration: animationDefinition.duration,
|
||||
delay: animationDefinition.delay,
|
||||
iterations: animationDefinition.iterations,
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
traceEnabled, traceCategories, traceType
|
||||
} from "./animation-common";
|
||||
import {
|
||||
opacityProperty, backgroundColorProperty, rotateProperty,
|
||||
opacityProperty, backgroundColorProperty, rotateProperty, rotateXProperty, rotateYProperty,
|
||||
translateXProperty, translateYProperty, scaleXProperty, scaleYProperty,
|
||||
heightProperty, widthProperty, PercentLength
|
||||
} from "../styling/style-properties";
|
||||
@ -91,6 +91,36 @@ export function _resolveAnimationCurve(curve: string | CubicBezierAnimationCurve
|
||||
}
|
||||
}
|
||||
|
||||
function getAndroidRepeatCount(iterations: number): number {
|
||||
return (iterations === Number.POSITIVE_INFINITY) ? android.view.animation.Animation.INFINITE : iterations - 1;
|
||||
}
|
||||
|
||||
function createObjectAnimator(nativeView: android.view.View, propertyName: string, value: number): android.animation.ObjectAnimator {
|
||||
let arr = Array.create("float", 1);
|
||||
arr[0] = value;
|
||||
|
||||
return android.animation.ObjectAnimator.ofFloat(nativeView, propertyName, arr);
|
||||
}
|
||||
|
||||
function createAnimationSet(animators: android.animation.ObjectAnimator[], iterations: number): android.animation.AnimatorSet {
|
||||
iterations = getAndroidRepeatCount(iterations);
|
||||
|
||||
const animatorSet = new android.animation.AnimatorSet();
|
||||
const animatorsArray = Array.create(android.animation.Animator, animators.length);
|
||||
|
||||
animators.forEach((animator, index) => {
|
||||
animatorsArray[index] = animator;
|
||||
|
||||
//TODO: not sure if we have to do that for each animator
|
||||
animatorsArray[index].setRepeatCount(iterations);
|
||||
});
|
||||
|
||||
animatorSet.playTogether(animatorsArray);
|
||||
animatorSet.setupStartValues();
|
||||
|
||||
return animatorSet;
|
||||
}
|
||||
|
||||
export class Animation extends AnimationBase {
|
||||
private _animatorListener: android.animation.Animator.AnimatorListener;
|
||||
private _nativeAnimatorsArray: any;
|
||||
@ -264,16 +294,14 @@ export class Animation extends AnimationBase {
|
||||
|
||||
this._target = propertyAnimation.target;
|
||||
|
||||
let nativeArray;
|
||||
const nativeView = <android.view.View>propertyAnimation.target.nativeViewProtected;
|
||||
const animators = new Array<android.animation.Animator>();
|
||||
const propertyUpdateCallbacks = new Array<Function>();
|
||||
const propertyResetCallbacks = new Array<Function>();
|
||||
let originalValue1;
|
||||
let originalValue2;
|
||||
let originalValue3;
|
||||
const density = layout.getDisplayDensity();
|
||||
let xyObjectAnimators: any;
|
||||
let animatorSet: android.animation.AnimatorSet;
|
||||
|
||||
let key = propertyKeys[propertyAnimation.property];
|
||||
if (key) {
|
||||
@ -295,8 +323,6 @@ export class Animation extends AnimationBase {
|
||||
opacityProperty._initDefaultNativeValue(style);
|
||||
|
||||
originalValue1 = nativeView.getAlpha();
|
||||
nativeArray = Array.create("float", 1);
|
||||
nativeArray[0] = propertyAnimation.value;
|
||||
propertyUpdateCallbacks.push(checkAnimation(() => {
|
||||
propertyAnimation.target.style[setLocal ? opacityProperty.name : opacityProperty.keyframe] = propertyAnimation.value;
|
||||
}));
|
||||
@ -310,7 +336,7 @@ export class Animation extends AnimationBase {
|
||||
propertyAnimation.target[opacityProperty.setNative](propertyAnimation.target.style.opacity);
|
||||
}
|
||||
}));
|
||||
animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "alpha", nativeArray));
|
||||
animators.push(createObjectAnimator(nativeView, "alpha", propertyAnimation.value));
|
||||
break;
|
||||
|
||||
case Properties.backgroundColor:
|
||||
@ -318,7 +344,7 @@ export class Animation extends AnimationBase {
|
||||
|
||||
ensureArgbEvaluator();
|
||||
originalValue1 = propertyAnimation.target.backgroundColor;
|
||||
nativeArray = Array.create(java.lang.Object, 2);
|
||||
const nativeArray = Array.create(java.lang.Object, 2);
|
||||
nativeArray[0] = propertyAnimation.target.backgroundColor ? java.lang.Integer.valueOf((<Color>propertyAnimation.target.backgroundColor).argb) : java.lang.Integer.valueOf(-1);
|
||||
nativeArray[1] = java.lang.Integer.valueOf((<Color>propertyAnimation.value).argb);
|
||||
let animator = android.animation.ValueAnimator.ofObject(argbEvaluator, nativeArray);
|
||||
@ -350,18 +376,6 @@ export class Animation extends AnimationBase {
|
||||
translateXProperty._initDefaultNativeValue(style);
|
||||
translateYProperty._initDefaultNativeValue(style);
|
||||
|
||||
xyObjectAnimators = Array.create(android.animation.Animator, 2);
|
||||
|
||||
nativeArray = Array.create("float", 1);
|
||||
nativeArray[0] = propertyAnimation.value.x * density;
|
||||
xyObjectAnimators[0] = android.animation.ObjectAnimator.ofFloat(nativeView, "translationX", nativeArray);
|
||||
xyObjectAnimators[0].setRepeatCount(Animation._getAndroidRepeatCount(propertyAnimation.iterations));
|
||||
|
||||
nativeArray = Array.create("float", 1);
|
||||
nativeArray[0] = propertyAnimation.value.y * density;
|
||||
xyObjectAnimators[1] = android.animation.ObjectAnimator.ofFloat(nativeView, "translationY", nativeArray);
|
||||
xyObjectAnimators[1].setRepeatCount(Animation._getAndroidRepeatCount(propertyAnimation.iterations));
|
||||
|
||||
originalValue1 = nativeView.getTranslationX() / density;
|
||||
originalValue2 = nativeView.getTranslationY() / density;
|
||||
|
||||
@ -385,28 +399,17 @@ export class Animation extends AnimationBase {
|
||||
}
|
||||
}));
|
||||
|
||||
animatorSet = new android.animation.AnimatorSet();
|
||||
animatorSet.playTogether(xyObjectAnimators);
|
||||
animatorSet.setupStartValues();
|
||||
animators.push(animatorSet);
|
||||
animators.push(
|
||||
createAnimationSet([
|
||||
createObjectAnimator(nativeView, "translationX", propertyAnimation.value.x * density),
|
||||
createObjectAnimator(nativeView, "translationY", propertyAnimation.value.y * density)
|
||||
], propertyAnimation.iterations));
|
||||
break;
|
||||
|
||||
case Properties.scale:
|
||||
scaleXProperty._initDefaultNativeValue(style);
|
||||
scaleYProperty._initDefaultNativeValue(style);
|
||||
|
||||
xyObjectAnimators = Array.create(android.animation.Animator, 2);
|
||||
|
||||
nativeArray = Array.create("float", 1);
|
||||
nativeArray[0] = propertyAnimation.value.x;
|
||||
xyObjectAnimators[0] = android.animation.ObjectAnimator.ofFloat(nativeView, "scaleX", nativeArray);
|
||||
xyObjectAnimators[0].setRepeatCount(Animation._getAndroidRepeatCount(propertyAnimation.iterations));
|
||||
|
||||
nativeArray = Array.create("float", 1);
|
||||
nativeArray[0] = propertyAnimation.value.y;
|
||||
xyObjectAnimators[1] = android.animation.ObjectAnimator.ofFloat(nativeView, "scaleY", nativeArray);
|
||||
xyObjectAnimators[1].setRepeatCount(Animation._getAndroidRepeatCount(propertyAnimation.iterations));
|
||||
|
||||
originalValue1 = nativeView.getScaleX();
|
||||
originalValue2 = nativeView.getScaleY();
|
||||
|
||||
@ -430,34 +433,53 @@ export class Animation extends AnimationBase {
|
||||
}
|
||||
}));
|
||||
|
||||
animatorSet = new android.animation.AnimatorSet();
|
||||
animatorSet.playTogether(xyObjectAnimators);
|
||||
animatorSet.setupStartValues();
|
||||
animators.push(animatorSet);
|
||||
animators.push(
|
||||
createAnimationSet([
|
||||
createObjectAnimator(nativeView, "scaleX", propertyAnimation.value.x),
|
||||
createObjectAnimator(nativeView, "scaleY", propertyAnimation.value.y)
|
||||
], propertyAnimation.iterations));
|
||||
break;
|
||||
|
||||
case Properties.rotate:
|
||||
rotateProperty._initDefaultNativeValue(style);
|
||||
rotateXProperty._initDefaultNativeValue(style);
|
||||
rotateYProperty._initDefaultNativeValue(style);
|
||||
|
||||
originalValue1 = nativeView.getRotationX();
|
||||
originalValue2 = nativeView.getRotationY();
|
||||
originalValue3 = nativeView.getRotation();
|
||||
|
||||
originalValue1 = nativeView.getRotation();
|
||||
nativeArray = Array.create("float", 1);
|
||||
nativeArray[0] = propertyAnimation.value;
|
||||
propertyUpdateCallbacks.push(checkAnimation(() => {
|
||||
propertyAnimation.target.style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = propertyAnimation.value;
|
||||
propertyAnimation.target.style[setLocal ? rotateXProperty.name : rotateXProperty.keyframe] = propertyAnimation.value.x;
|
||||
propertyAnimation.target.style[setLocal ? rotateYProperty.name : rotateYProperty.keyframe] = propertyAnimation.value.y;
|
||||
propertyAnimation.target.style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = propertyAnimation.value.z;
|
||||
}));
|
||||
propertyResetCallbacks.push(checkAnimation(() => {
|
||||
if (setLocal) {
|
||||
propertyAnimation.target.style[rotateProperty.name] = originalValue1;
|
||||
propertyAnimation.target.style[rotateXProperty.name] = originalValue1;
|
||||
propertyAnimation.target.style[rotateYProperty.name] = originalValue2;
|
||||
propertyAnimation.target.style[rotateProperty.name] = originalValue3;
|
||||
} else {
|
||||
propertyAnimation.target.style[rotateProperty.keyframe] = originalValue1;
|
||||
propertyAnimation.target.style[rotateXProperty.keyframe] = originalValue1;
|
||||
propertyAnimation.target.style[rotateYProperty.keyframe] = originalValue2;
|
||||
propertyAnimation.target.style[rotateProperty.keyframe] = originalValue3;
|
||||
}
|
||||
|
||||
if (propertyAnimation.target.nativeViewProtected) {
|
||||
propertyAnimation.target[rotateProperty.setNative](propertyAnimation.target.style.rotate);
|
||||
propertyAnimation.target[rotateXProperty.setNative](propertyAnimation.target.style.rotateX);
|
||||
propertyAnimation.target[rotateYProperty.setNative](propertyAnimation.target.style.rotateY);
|
||||
}
|
||||
}));
|
||||
animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "rotation", nativeArray));
|
||||
|
||||
animators.push(
|
||||
createAnimationSet([
|
||||
createObjectAnimator(nativeView, "rotationX", propertyAnimation.value.x),
|
||||
createObjectAnimator(nativeView, "rotationY", propertyAnimation.value.y),
|
||||
createObjectAnimator(nativeView, "rotation", propertyAnimation.value.z)
|
||||
], propertyAnimation.iterations));
|
||||
break;
|
||||
|
||||
case Properties.width:
|
||||
case Properties.height: {
|
||||
|
||||
@ -465,7 +487,7 @@ export class Animation extends AnimationBase {
|
||||
const extentProperty = isVertical ? heightProperty : widthProperty;
|
||||
|
||||
extentProperty._initDefaultNativeValue(style);
|
||||
nativeArray = Array.create("float", 2);
|
||||
const nativeArray = Array.create("float", 2);
|
||||
let toValue = propertyAnimation.value;
|
||||
let parent = propertyAnimation.target.parent as View;
|
||||
if (!parent) {
|
||||
@ -473,7 +495,7 @@ export class Animation extends AnimationBase {
|
||||
}
|
||||
const parentExtent: number = isVertical ? parent.getMeasuredHeight() : parent.getMeasuredWidth();
|
||||
toValue = PercentLength.toDevicePixels(toValue, parentExtent, parentExtent) / screen.mainScreen.scale;
|
||||
const nativeHeight: number = isVertical ? nativeView.getHeight() : nativeView.getWidth();
|
||||
let nativeHeight: number = isVertical ? nativeView.getHeight() : nativeView.getWidth();
|
||||
const targetStyle: string = setLocal ? extentProperty.name : extentProperty.keyframe;
|
||||
originalValue1 = nativeHeight / screen.mainScreen.scale;
|
||||
nativeArray[0] = originalValue1;
|
||||
@ -516,7 +538,7 @@ export class Animation extends AnimationBase {
|
||||
|
||||
// Repeat Count
|
||||
if (propertyAnimation.iterations !== undefined && animators[i] instanceof android.animation.ValueAnimator) {
|
||||
(<android.animation.ValueAnimator>animators[i]).setRepeatCount(Animation._getAndroidRepeatCount(propertyAnimation.iterations));
|
||||
(<android.animation.ValueAnimator>animators[i]).setRepeatCount(getAndroidRepeatCount(propertyAnimation.iterations));
|
||||
}
|
||||
|
||||
// Interpolator
|
||||
@ -533,8 +555,4 @@ export class Animation extends AnimationBase {
|
||||
this._propertyUpdateCallbacks = this._propertyUpdateCallbacks.concat(propertyUpdateCallbacks);
|
||||
this._propertyResetCallbacks = this._propertyResetCallbacks.concat(propertyResetCallbacks);
|
||||
}
|
||||
|
||||
private static _getAndroidRepeatCount(iterations: number): number {
|
||||
return (iterations === Number.POSITIVE_INFINITY) ? android.view.animation.Animation.INFINITE : iterations - 1;
|
||||
}
|
||||
}
|
||||
|
18
nativescript-core/ui/animation/animation.d.ts
vendored
18
nativescript-core/ui/animation/animation.d.ts
vendored
@ -47,7 +47,7 @@ export interface AnimationDefinition {
|
||||
/**
|
||||
* Animates the rotate affine transform of the view. Value should be a number specifying the rotation amount in degrees.
|
||||
*/
|
||||
rotate?: number;
|
||||
rotate?: number | Point3D;
|
||||
|
||||
/**
|
||||
* The length of the animation in milliseconds. The default duration is 300 milliseconds.
|
||||
@ -97,14 +97,24 @@ export type Transformation = {
|
||||
/**
|
||||
* Defines possible css transformations
|
||||
*/
|
||||
export type TransformationType = "rotate" |
|
||||
export type TransformationType =
|
||||
"rotate" | "rotateX" | "rotateY" |
|
||||
"translate" | "translateX" | "translateY" |
|
||||
"scale" | "scaleX" | "scaleY";
|
||||
|
||||
/**
|
||||
* Defines possible css transformation values
|
||||
*/
|
||||
export type TransformationValue = Pair | number;
|
||||
export type TransformationValue = Point3D | Pair | number;
|
||||
|
||||
/**
|
||||
* Defines a point in 3d space (x, y and z) for rotation in 3d animations.
|
||||
*/
|
||||
export interface Point3D {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a pair of values (horizontal and vertical) for translate and scale animations.
|
||||
@ -119,7 +129,7 @@ export interface Pair {
|
||||
*/
|
||||
export type TransformFunctionsInfo = {
|
||||
translate: Pair,
|
||||
rotate: number,
|
||||
rotate: Point3D,
|
||||
scale: Pair,
|
||||
}
|
||||
|
||||
|
@ -11,11 +11,13 @@ import {
|
||||
traceWrite, traceEnabled, traceCategories, traceType
|
||||
} from "./animation-common";
|
||||
import {
|
||||
opacityProperty, backgroundColorProperty, rotateProperty,
|
||||
opacityProperty, backgroundColorProperty, rotateProperty, rotateXProperty, rotateYProperty,
|
||||
translateXProperty, translateYProperty, scaleXProperty, scaleYProperty,
|
||||
heightProperty, widthProperty, PercentLength
|
||||
} from "../styling/style-properties";
|
||||
|
||||
import { ios as iosNativeHelper } from "../../utils/native-helper";
|
||||
|
||||
import { screen } from "../../platform";
|
||||
|
||||
export * from "./animation-common";
|
||||
@ -27,6 +29,7 @@ let FLT_MAX = 340282346638528859811704183484516925440.000000;
|
||||
|
||||
class AnimationInfo {
|
||||
public propertyNameToAnimate: string;
|
||||
public subPropertiesToAnimate?: string[];
|
||||
public fromValue: any;
|
||||
public toValue: any;
|
||||
public duration: number;
|
||||
@ -69,7 +72,9 @@ class AnimationDelegateImpl extends NSObject implements CAAnimationDelegate {
|
||||
targetStyle[setLocal ? opacityProperty.name : opacityProperty.keyframe] = value;
|
||||
break;
|
||||
case Properties.rotate:
|
||||
targetStyle[setLocal ? rotateProperty.name : rotateProperty.keyframe] = value;
|
||||
targetStyle[setLocal ? rotateXProperty.name : rotateXProperty.keyframe] = value.x;
|
||||
targetStyle[setLocal ? rotateYProperty.name : rotateYProperty.keyframe] = value.y;
|
||||
targetStyle[setLocal ? rotateProperty.name : rotateProperty.keyframe] = value.z;
|
||||
break;
|
||||
case Properties.translate:
|
||||
targetStyle[setLocal ? translateXProperty.name : translateXProperty.keyframe] = value.x;
|
||||
@ -90,6 +95,11 @@ class AnimationDelegateImpl extends NSObject implements CAAnimationDelegate {
|
||||
targetStyle[setLocal ? translateXProperty.name : translateXProperty.keyframe] = value[Properties.translate].x;
|
||||
targetStyle[setLocal ? translateYProperty.name : translateYProperty.keyframe] = value[Properties.translate].y;
|
||||
}
|
||||
if (value[Properties.rotate] !== undefined) {
|
||||
targetStyle[setLocal ? rotateXProperty.name : rotateXProperty.keyframe] = value[Properties.rotate].x;
|
||||
targetStyle[setLocal ? rotateYProperty.name : rotateYProperty.keyframe] = value[Properties.rotate].y;
|
||||
targetStyle[setLocal ? rotateProperty.name : rotateProperty.keyframe] = value[Properties.rotate].z;
|
||||
}
|
||||
if (value[Properties.scale] !== undefined) {
|
||||
let x = value[Properties.scale].x;
|
||||
let y = value[Properties.scale].y;
|
||||
@ -267,24 +277,23 @@ export class Animation extends AnimationBase {
|
||||
}
|
||||
|
||||
private static _getNativeAnimationArguments(animation: PropertyAnimationInfo, valueSource: "animation" | "keyframe"): AnimationInfo {
|
||||
|
||||
let nativeView = <UIView>animation.target.nativeViewProtected;
|
||||
let propertyNameToAnimate = animation.property;
|
||||
let toValue = animation.value;
|
||||
let fromValue;
|
||||
const parent = animation.target.parent as View;
|
||||
const view = animation.target;
|
||||
const style = view.style;
|
||||
const nativeView = <UIView>view.nativeViewProtected;
|
||||
const parent = view.parent as View;
|
||||
const screenScale: number = screen.mainScreen.scale;
|
||||
|
||||
let tempRotate = (animation.target.rotate || 0) * Math.PI / 180;
|
||||
let abs;
|
||||
|
||||
let propertyNameToAnimate = animation.property;
|
||||
let subPropertyNameToAnimate;
|
||||
let toValue = animation.value;
|
||||
let fromValue;
|
||||
let setLocal = valueSource === "animation";
|
||||
|
||||
switch (animation.property) {
|
||||
case Properties.backgroundColor:
|
||||
animation._originalValue = animation.target.backgroundColor;
|
||||
animation._originalValue = view.backgroundColor;
|
||||
animation._propertyResetCallback = (value, valueSource) => {
|
||||
animation.target.style[setLocal ? backgroundColorProperty.name : backgroundColorProperty.keyframe] = value;
|
||||
style[setLocal ? backgroundColorProperty.name : backgroundColorProperty.keyframe] = value;
|
||||
};
|
||||
fromValue = nativeView.layer.backgroundColor;
|
||||
if (nativeView instanceof UILabel) {
|
||||
@ -293,33 +302,50 @@ export class Animation extends AnimationBase {
|
||||
toValue = toValue.CGColor;
|
||||
break;
|
||||
case Properties.opacity:
|
||||
animation._originalValue = animation.target.opacity;
|
||||
animation._originalValue = view.opacity;
|
||||
animation._propertyResetCallback = (value, valueSource) => {
|
||||
animation.target.style[setLocal ? opacityProperty.name : opacityProperty.keyframe] = value;
|
||||
style[setLocal ? opacityProperty.name : opacityProperty.keyframe] = value;
|
||||
};
|
||||
fromValue = nativeView.layer.opacity;
|
||||
break;
|
||||
case Properties.rotate:
|
||||
animation._originalValue = animation.target.rotate !== undefined ? animation.target.rotate : 0;
|
||||
animation._originalValue = { x: view.rotateX, y: view.rotateY, z: view.rotate };
|
||||
animation._propertyResetCallback = (value, valueSource) => {
|
||||
animation.target.style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = value;
|
||||
style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = value.z;
|
||||
style[setLocal ? rotateXProperty.name : rotateXProperty.keyframe] = value.x;
|
||||
style[setLocal ? rotateYProperty.name : rotateYProperty.keyframe] = value.y;
|
||||
};
|
||||
|
||||
propertyNameToAnimate = "transform.rotation";
|
||||
fromValue = nativeView.layer.valueForKeyPath("transform.rotation");
|
||||
subPropertyNameToAnimate = ["x", "y", "z"];
|
||||
fromValue = {
|
||||
x: nativeView.layer.valueForKeyPath("transform.rotation.x"),
|
||||
y: nativeView.layer.valueForKeyPath("transform.rotation.y"),
|
||||
z: nativeView.layer.valueForKeyPath("transform.rotation.z")
|
||||
};
|
||||
|
||||
if (animation.target.rotateX !== undefined && animation.target.rotateX !== 0 && Math.floor(toValue / 360) - toValue / 360 === 0) {
|
||||
fromValue.x = animation.target.rotateX * Math.PI / 180;
|
||||
}
|
||||
if (animation.target.rotateY !== undefined && animation.target.rotateY !== 0 && Math.floor(toValue / 360) - toValue / 360 === 0) {
|
||||
fromValue.y = animation.target.rotateY * Math.PI / 180;
|
||||
}
|
||||
if (animation.target.rotate !== undefined && animation.target.rotate !== 0 && Math.floor(toValue / 360) - toValue / 360 === 0) {
|
||||
fromValue = animation.target.rotate * Math.PI / 180;
|
||||
}
|
||||
toValue = toValue * Math.PI / 180;
|
||||
abs = fabs(fromValue - toValue);
|
||||
if (abs < 0.001 && fromValue !== tempRotate) {
|
||||
fromValue = tempRotate;
|
||||
fromValue.z = animation.target.rotate * Math.PI / 180;
|
||||
}
|
||||
|
||||
// Respect only value.z for back-compat until 3D rotations are implemented
|
||||
toValue = {
|
||||
x: toValue.x * Math.PI / 180,
|
||||
y: toValue.y * Math.PI / 180,
|
||||
z: toValue.z * Math.PI / 180
|
||||
};
|
||||
break;
|
||||
case Properties.translate:
|
||||
animation._originalValue = { x: animation.target.translateX, y: animation.target.translateY };
|
||||
animation._originalValue = { x: view.translateX, y: view.translateY };
|
||||
animation._propertyResetCallback = (value, valueSource) => {
|
||||
animation.target.style[setLocal ? translateXProperty.name : translateXProperty.keyframe] = value.x;
|
||||
animation.target.style[setLocal ? translateYProperty.name : translateYProperty.keyframe] = value.y;
|
||||
style[setLocal ? translateXProperty.name : translateXProperty.keyframe] = value.x;
|
||||
style[setLocal ? translateYProperty.name : translateYProperty.keyframe] = value.y;
|
||||
};
|
||||
propertyNameToAnimate = "transform";
|
||||
fromValue = NSValue.valueWithCATransform3D(nativeView.layer.transform);
|
||||
@ -332,10 +358,10 @@ export class Animation extends AnimationBase {
|
||||
if (toValue.y === 0) {
|
||||
toValue.y = 0.001;
|
||||
}
|
||||
animation._originalValue = { x: animation.target.scaleX, y: animation.target.scaleY };
|
||||
animation._originalValue = { x: view.scaleX, y: view.scaleY };
|
||||
animation._propertyResetCallback = (value, valueSource) => {
|
||||
animation.target.style[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = value.x;
|
||||
animation.target.style[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = value.y;
|
||||
style[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = value.x;
|
||||
style[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = value.y;
|
||||
};
|
||||
propertyNameToAnimate = "transform";
|
||||
fromValue = NSValue.valueWithCATransform3D(nativeView.layer.transform);
|
||||
@ -344,14 +370,18 @@ export class Animation extends AnimationBase {
|
||||
case _transform:
|
||||
fromValue = NSValue.valueWithCATransform3D(nativeView.layer.transform);
|
||||
animation._originalValue = {
|
||||
xs: animation.target.scaleX, ys: animation.target.scaleY,
|
||||
xt: animation.target.translateX, yt: animation.target.translateY
|
||||
xs: view.scaleX, ys: view.scaleY,
|
||||
xt: view.translateX, yt: view.translateY,
|
||||
rx: view.rotateX, ry: view.rotateY, rz: view.rotate
|
||||
};
|
||||
animation._propertyResetCallback = (value, valueSource) => {
|
||||
animation.target.style[setLocal ? translateXProperty.name : translateXProperty.keyframe] = value.xt;
|
||||
animation.target.style[setLocal ? translateYProperty.name : translateYProperty.keyframe] = value.yt;
|
||||
animation.target.style[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = value.xs;
|
||||
animation.target.style[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = value.ys;
|
||||
style[setLocal ? translateXProperty.name : translateXProperty.keyframe] = value.xt;
|
||||
style[setLocal ? translateYProperty.name : translateYProperty.keyframe] = value.yt;
|
||||
style[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = value.xs;
|
||||
style[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = value.ys;
|
||||
style[setLocal ? rotateXProperty.name : rotateXProperty.keyframe] = value.rx;
|
||||
style[setLocal ? rotateYProperty.name : rotateYProperty.keyframe] = value.ry;
|
||||
style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = value.rz;
|
||||
};
|
||||
propertyNameToAnimate = "transform";
|
||||
toValue = NSValue.valueWithCATransform3D(Animation._createNativeAffineTransform(animation));
|
||||
@ -373,10 +403,10 @@ export class Animation extends AnimationBase {
|
||||
toValue = NSValue.valueWithCGRect(
|
||||
CGRectMake(currentBounds.origin.x, currentBounds.origin.y, extentX, extentY)
|
||||
);
|
||||
animation._originalValue = animation.target.height;
|
||||
animation._originalValue = view.height;
|
||||
animation._propertyResetCallback = (value, valueSource) => {
|
||||
const prop = isHeight ? heightProperty : widthProperty;
|
||||
animation.target.style[setLocal ? prop.name : prop.keyframe] = value;
|
||||
style[setLocal ? prop.name : prop.keyframe] = value;
|
||||
};
|
||||
break;
|
||||
default:
|
||||
@ -406,6 +436,7 @@ export class Animation extends AnimationBase {
|
||||
return {
|
||||
propertyNameToAnimate: propertyNameToAnimate,
|
||||
fromValue: fromValue,
|
||||
subPropertiesToAnimate: subPropertyNameToAnimate,
|
||||
toValue: toValue,
|
||||
duration: duration,
|
||||
repeatCount: repeatCount,
|
||||
@ -416,18 +447,12 @@ export class Animation extends AnimationBase {
|
||||
private static _createNativeAnimation(propertyAnimations: Array<PropertyAnimation>, index: number, playSequentially: boolean, args: AnimationInfo, animation: PropertyAnimation, valueSource: "animation" | "keyframe", finishedCallback: (cancelled?: boolean) => void) {
|
||||
|
||||
let nativeView = <UIView>animation.target.nativeViewProtected;
|
||||
let nativeAnimation = CABasicAnimation.animationWithKeyPath(args.propertyNameToAnimate);
|
||||
nativeAnimation.fromValue = args.fromValue;
|
||||
nativeAnimation.toValue = args.toValue;
|
||||
nativeAnimation.duration = args.duration;
|
||||
if (args.repeatCount !== undefined) {
|
||||
nativeAnimation.repeatCount = args.repeatCount;
|
||||
}
|
||||
if (args.delay !== undefined) {
|
||||
nativeAnimation.beginTime = CACurrentMediaTime() + args.delay;
|
||||
}
|
||||
if (animation.curve !== undefined) {
|
||||
nativeAnimation.timingFunction = animation.curve;
|
||||
let nativeAnimation;
|
||||
|
||||
if (args.subPropertiesToAnimate) {
|
||||
nativeAnimation = this._createGroupAnimation(args, animation);
|
||||
} else {
|
||||
nativeAnimation = this._createBasicAnimation(args, animation);
|
||||
}
|
||||
|
||||
let animationDelegate = AnimationDelegateImpl.initWithFinishedCallback(finishedCallback, animation, valueSource);
|
||||
@ -447,6 +472,44 @@ export class Animation extends AnimationBase {
|
||||
}
|
||||
}
|
||||
|
||||
private static _createGroupAnimation(args: AnimationInfo, animation: PropertyAnimation) {
|
||||
let groupAnimation = CAAnimationGroup.new();
|
||||
groupAnimation.duration = args.duration;
|
||||
const animations = NSMutableArray.alloc<CAAnimation>().initWithCapacity(3);
|
||||
|
||||
args.subPropertiesToAnimate.forEach(property => {
|
||||
const basicAnimationArgs = { ...args };
|
||||
basicAnimationArgs.propertyNameToAnimate = `${args.propertyNameToAnimate}.${property}`;
|
||||
basicAnimationArgs.fromValue = args.fromValue[property];
|
||||
basicAnimationArgs.toValue = args.toValue[property];
|
||||
|
||||
const basicAnimation = this._createBasicAnimation(basicAnimationArgs, animation);
|
||||
animations.addObject(basicAnimation);
|
||||
});
|
||||
|
||||
groupAnimation.animations = animations;
|
||||
|
||||
return groupAnimation;
|
||||
}
|
||||
|
||||
private static _createBasicAnimation(args: AnimationInfo, animation: PropertyAnimation) {
|
||||
let basicAnimation = CABasicAnimation.animationWithKeyPath(args.propertyNameToAnimate);
|
||||
basicAnimation.fromValue = args.fromValue;
|
||||
basicAnimation.toValue = args.toValue;
|
||||
basicAnimation.duration = args.duration;
|
||||
if (args.repeatCount !== undefined) {
|
||||
basicAnimation.repeatCount = args.repeatCount;
|
||||
}
|
||||
if (args.delay !== undefined) {
|
||||
basicAnimation.beginTime = CACurrentMediaTime() + args.delay;
|
||||
}
|
||||
if (animation.curve !== undefined) {
|
||||
basicAnimation.timingFunction = animation.curve;
|
||||
}
|
||||
|
||||
return basicAnimation;
|
||||
}
|
||||
|
||||
private static _createNativeSpringAnimation(propertyAnimations: Array<PropertyAnimationInfo>, index: number, playSequentially: boolean, args: AnimationInfo, animation: PropertyAnimationInfo, valueSource: "animation" | "keyframe", finishedCallback: (cancelled?: boolean) => void) {
|
||||
|
||||
let nativeView = <UIView>animation.target.nativeViewProtected;
|
||||
@ -490,9 +553,6 @@ export class Animation extends AnimationBase {
|
||||
animation.target[animation.property] = value;
|
||||
};
|
||||
break;
|
||||
case Properties.rotate:
|
||||
nativeView.layer.setValueForKey(args.toValue, args.propertyNameToAnimate);
|
||||
break;
|
||||
case _transform:
|
||||
animation._originalValue = nativeView.layer.transform;
|
||||
nativeView.layer.setValueForKey(args.toValue, args.propertyNameToAnimate);
|
||||
@ -508,6 +568,11 @@ export class Animation extends AnimationBase {
|
||||
animation.target.translateX = animation.value[Properties.translate].x;
|
||||
animation.target.translateY = animation.value[Properties.translate].y;
|
||||
}
|
||||
if (animation.value[Properties.rotate] !== undefined) {
|
||||
animation.target.rotateX = animation.value[Properties.rotate].x;
|
||||
animation.target.rotateY = animation.value[Properties.rotate].y;
|
||||
animation.target.rotate = animation.value[Properties.rotate].z;
|
||||
}
|
||||
if (animation.value[Properties.scale] !== undefined) {
|
||||
animation.target.scaleX = animation.value[Properties.scale].x;
|
||||
animation.target.scaleY = animation.value[Properties.scale].y;
|
||||
@ -631,19 +696,41 @@ export class Animation extends AnimationBase {
|
||||
}
|
||||
|
||||
export function _getTransformMismatchErrorMessage(view: View): string {
|
||||
// Order is important: translate, rotate, scale
|
||||
let result: CGAffineTransform = CGAffineTransformIdentity;
|
||||
const tx = view.translateX;
|
||||
const ty = view.translateY;
|
||||
result = CGAffineTransformTranslate(result, tx, ty);
|
||||
result = CGAffineTransformRotate(result, (view.rotate || 0) * Math.PI / 180);
|
||||
result = CGAffineTransformScale(result, view.scaleX || 1, view.scaleY || 1);
|
||||
let viewTransform = NSStringFromCGAffineTransform(result);
|
||||
let nativeTransform = NSStringFromCGAffineTransform(view.nativeViewProtected.transform);
|
||||
const expectedTransform = calculateTransform(view);
|
||||
const expectedTransformString = getCATransform3DString(expectedTransform);
|
||||
const actualTransformString = getCATransform3DString(view.nativeViewProtected.layer.transform);
|
||||
|
||||
if (viewTransform !== nativeTransform) {
|
||||
return "View and Native transforms do not match. View: " + viewTransform + "; Native: " + nativeTransform;
|
||||
if (actualTransformString !== expectedTransformString) {
|
||||
return "View and Native transforms do not match.\nActual: " + actualTransformString + ";\nExpected: " + expectedTransformString;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function calculateTransform(view: View): CATransform3D {
|
||||
const scaleX = view.scaleX || 1e-6;
|
||||
const scaleY = view.scaleY || 1e-6;
|
||||
const perspective = view.perspective || 300;
|
||||
|
||||
// Order is important: translate, rotate, scale
|
||||
let expectedTransform = new CATransform3D(CATransform3DIdentity);
|
||||
|
||||
// Only set perspective if there is 3D rotation
|
||||
if (view.rotateX || view.rotateY) {
|
||||
expectedTransform.m34 = -1 / perspective;
|
||||
}
|
||||
|
||||
expectedTransform = CATransform3DTranslate(expectedTransform, view.translateX, view.translateY, 0);
|
||||
expectedTransform = iosNativeHelper.applyRotateTransform(expectedTransform, view.rotateX, view.rotateY, view.rotate);
|
||||
expectedTransform = CATransform3DScale(expectedTransform, scaleX, scaleY, 1);
|
||||
|
||||
return expectedTransform;
|
||||
}
|
||||
|
||||
function getCATransform3DString(t: CATransform3D) {
|
||||
return `[
|
||||
${t.m11}, ${t.m12}, ${t.m13}, ${t.m14},
|
||||
${t.m21}, ${t.m22}, ${t.m23}, ${t.m24},
|
||||
${t.m31}, ${t.m32}, ${t.m33}, ${t.m34},
|
||||
${t.m41}, ${t.m42}, ${t.m43}, ${t.m44}]`;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import {
|
||||
backgroundColorProperty,
|
||||
scaleXProperty, scaleYProperty,
|
||||
translateXProperty, translateYProperty,
|
||||
rotateProperty, opacityProperty,
|
||||
rotateProperty, opacityProperty, rotateXProperty, rotateYProperty,
|
||||
widthProperty, heightProperty, PercentLength
|
||||
} from "../styling/style-properties";
|
||||
|
||||
@ -61,7 +61,7 @@ interface Keyframe {
|
||||
backgroundColor?: Color;
|
||||
scale?: { x: number, y: number };
|
||||
translate?: { x: number, y: number };
|
||||
rotate?: number;
|
||||
rotate?: { x: number, y: number, z: number };
|
||||
opacity?: number;
|
||||
width?: PercentLength;
|
||||
height?: PercentLength;
|
||||
@ -213,7 +213,9 @@ export class KeyframeAnimation implements KeyframeAnimationDefinition {
|
||||
view.style[translateYProperty.keyframe] = animation.translate.y;
|
||||
}
|
||||
if ("rotate" in animation) {
|
||||
view.style[rotateProperty.keyframe] = animation.rotate;
|
||||
view.style[rotateXProperty.keyframe] = animation.rotate.x;
|
||||
view.style[rotateYProperty.keyframe] = animation.rotate.y;
|
||||
view.style[rotateProperty.keyframe] = animation.rotate.z;
|
||||
}
|
||||
if ("opacity" in animation) {
|
||||
view.style[opacityProperty.keyframe] = animation.opacity;
|
||||
|
@ -697,6 +697,27 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
|
||||
this.style.rotate = value;
|
||||
}
|
||||
|
||||
get rotateX(): number {
|
||||
return this.style.rotateX;
|
||||
}
|
||||
set rotateX(value: number) {
|
||||
this.style.rotateX = value;
|
||||
}
|
||||
|
||||
get rotateY(): number {
|
||||
return this.style.rotateY;
|
||||
}
|
||||
set rotateY(value: number) {
|
||||
this.style.rotateY = value;
|
||||
}
|
||||
|
||||
get perspective(): number {
|
||||
return this.style.perspective;
|
||||
}
|
||||
set perspective(value: number) {
|
||||
this.style.perspective = value;
|
||||
}
|
||||
|
||||
get translateX(): dip {
|
||||
return this.style.translateX;
|
||||
}
|
||||
|
@ -10,17 +10,18 @@ import {
|
||||
} from "./view-common";
|
||||
|
||||
import {
|
||||
Length, PercentLength, Visibility, HorizontalAlignment, VerticalAlignment,
|
||||
perspectiveProperty, Length, PercentLength, Visibility, HorizontalAlignment, VerticalAlignment,
|
||||
visibilityProperty, opacityProperty, horizontalAlignmentProperty, verticalAlignmentProperty,
|
||||
minWidthProperty, minHeightProperty, widthProperty, heightProperty,
|
||||
marginLeftProperty, marginTopProperty, marginRightProperty, marginBottomProperty,
|
||||
rotateProperty, scaleXProperty, scaleYProperty, translateXProperty, translateYProperty,
|
||||
rotateProperty, rotateXProperty, rotateYProperty, scaleXProperty, scaleYProperty, translateXProperty, translateYProperty,
|
||||
zIndexProperty, backgroundInternalProperty, androidElevationProperty, androidDynamicElevationOffsetProperty
|
||||
} from "../../styling/style-properties";
|
||||
|
||||
import { Background, ad as androidBackground } from "../../styling/background";
|
||||
import { profile } from "../../../profiling";
|
||||
import { topmost } from "../../frame/frame-stack";
|
||||
import { screen } from "../../../platform";
|
||||
import { AndroidActivityBackPressedEventData, android as androidApp } from "../../../application";
|
||||
import { device } from "../../../platform";
|
||||
import lazy from "../../../utils/lazy";
|
||||
@ -911,6 +912,20 @@ export class View extends ViewCommon {
|
||||
org.nativescript.widgets.ViewHelper.setRotate(this.nativeViewProtected, float(value));
|
||||
}
|
||||
|
||||
[rotateXProperty.setNative](value: number) {
|
||||
org.nativescript.widgets.ViewHelper.setRotateX(this.nativeViewProtected, float(value));
|
||||
}
|
||||
|
||||
[rotateYProperty.setNative](value: number) {
|
||||
org.nativescript.widgets.ViewHelper.setRotateY(this.nativeViewProtected, float(value));
|
||||
}
|
||||
|
||||
[perspectiveProperty.setNative](value: number) {
|
||||
const scale = screen.mainScreen.scale;
|
||||
const distance = value * scale;
|
||||
org.nativescript.widgets.ViewHelper.setPerspective(this.nativeViewProtected, float(distance));
|
||||
}
|
||||
|
||||
[scaleXProperty.setNative](value: number) {
|
||||
org.nativescript.widgets.ViewHelper.setScaleX(this.nativeViewProtected, float(value));
|
||||
}
|
||||
|
23
nativescript-core/ui/core/view/view.d.ts
vendored
23
nativescript-core/ui/core/view/view.d.ts
vendored
@ -75,6 +75,11 @@ export interface Point {
|
||||
* Represents the y coordinate of the location.
|
||||
*/
|
||||
y: number;
|
||||
|
||||
/**
|
||||
* Represents the z coordinate of the location.
|
||||
*/
|
||||
z?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -315,10 +320,26 @@ export abstract class View extends ViewBase {
|
||||
opacity: number;
|
||||
|
||||
/**
|
||||
* Gets or sets the rotate affine transform of the view.
|
||||
* Gets or sets the rotate affine transform of the view along the Z axis.
|
||||
*/
|
||||
rotate: number;
|
||||
|
||||
/**
|
||||
* Gets or sets the rotate affine transform of the view along the X axis.
|
||||
*/
|
||||
rotateX: number;
|
||||
|
||||
/**
|
||||
* Gets or sets the rotate affine transform of the view along the Y axis.
|
||||
*/
|
||||
rotateY: number;
|
||||
|
||||
/**
|
||||
* Gets or sets the distance of the camera form the view perspective.
|
||||
* Usually needed when rotating the view over the X or Y axis.
|
||||
*/
|
||||
perspective: number;
|
||||
|
||||
/**
|
||||
* Gets or sets the translateX affine transform of the view in device independent pixels.
|
||||
*/
|
||||
|
@ -9,10 +9,12 @@ import {
|
||||
import { ios } from "./view-helper";
|
||||
import { ios as iosBackground, Background } from "../../styling/background";
|
||||
import { ios as iosUtils } from "../../../utils/utils";
|
||||
import { ios as iosNativeHelper } from "../../../utils/native-helper";
|
||||
import {
|
||||
Visibility,
|
||||
perspectiveProperty, Visibility,
|
||||
visibilityProperty, opacityProperty,
|
||||
rotateProperty, scaleXProperty, scaleYProperty,
|
||||
rotateProperty, rotateXProperty, rotateYProperty,
|
||||
scaleXProperty, scaleYProperty,
|
||||
translateXProperty, translateYProperty, zIndexProperty,
|
||||
backgroundInternalProperty, clipPathProperty
|
||||
} from "../../styling/style-properties";
|
||||
@ -156,6 +158,7 @@ export class View extends ViewCommon implements ViewDefinition {
|
||||
}
|
||||
|
||||
public _setNativeViewFrame(nativeView: UIView, frame: CGRect): void {
|
||||
|
||||
let oldFrame = this._cachedFrame || nativeView.frame;
|
||||
if (!CGRectEqualToRect(oldFrame, frame)) {
|
||||
if (traceEnabled()) {
|
||||
@ -166,8 +169,8 @@ export class View extends ViewCommon implements ViewDefinition {
|
||||
let transform = null;
|
||||
if (this._hasTransfrom) {
|
||||
// Always set identity transform before setting frame;
|
||||
transform = nativeView.transform;
|
||||
nativeView.transform = CGAffineTransformIdentity;
|
||||
transform = nativeView.layer.transform;
|
||||
nativeView.layer.transform = CATransform3DIdentity;
|
||||
nativeView.frame = frame;
|
||||
} else {
|
||||
nativeView.frame = frame;
|
||||
@ -180,7 +183,7 @@ export class View extends ViewCommon implements ViewDefinition {
|
||||
|
||||
if (this._hasTransfrom) {
|
||||
// re-apply the transform after the frame is adjusted
|
||||
nativeView.transform = transform;
|
||||
nativeView.layer.transform = transform;
|
||||
}
|
||||
|
||||
const boundsOrigin = nativeView.bounds.origin;
|
||||
@ -348,18 +351,25 @@ export class View extends ViewCommon implements ViewDefinition {
|
||||
public updateNativeTransform() {
|
||||
const scaleX = this.scaleX || 1e-6;
|
||||
const scaleY = this.scaleY || 1e-6;
|
||||
const rotate = this.rotate || 0;
|
||||
let newTransform = CGAffineTransformIdentity;
|
||||
newTransform = CGAffineTransformTranslate(newTransform, this.translateX, this.translateY);
|
||||
newTransform = CGAffineTransformRotate(newTransform, rotate * Math.PI / 180);
|
||||
newTransform = CGAffineTransformScale(newTransform, scaleX, scaleY);
|
||||
if (!CGAffineTransformEqualToTransform(this.nativeViewProtected.transform, newTransform)) {
|
||||
const perspective = this.perspective || 300;
|
||||
|
||||
let transform = new CATransform3D(CATransform3DIdentity);
|
||||
|
||||
// Only set perspective if there is 3D rotation
|
||||
if (this.rotateX || this.rotateY) {
|
||||
transform.m34 = -1 / perspective;
|
||||
}
|
||||
|
||||
transform = CATransform3DTranslate(transform, this.translateX, this.translateY, 0);
|
||||
transform = iosNativeHelper.applyRotateTransform(transform, this.rotateX, this.rotateY, this.rotate);
|
||||
transform = CATransform3DScale(transform, scaleX, scaleY, 1);
|
||||
if (!CATransform3DEqualToTransform(this.nativeViewProtected.layer.transform, transform)) {
|
||||
const updateSuspended = this._isPresentationLayerUpdateSuspeneded();
|
||||
if (!updateSuspended) {
|
||||
CATransaction.begin();
|
||||
}
|
||||
this.nativeViewProtected.transform = newTransform;
|
||||
this._hasTransfrom = this.nativeViewProtected && !CGAffineTransformEqualToTransform(this.nativeViewProtected.transform, CGAffineTransformIdentity);
|
||||
this.nativeViewProtected.layer.transform = transform;
|
||||
this._hasTransfrom = this.nativeViewProtected && !CATransform3DEqualToTransform(this.nativeViewProtected.transform3D, CATransform3DIdentity);
|
||||
if (!updateSuspended) {
|
||||
CATransaction.commit();
|
||||
}
|
||||
@ -579,6 +589,27 @@ export class View extends ViewCommon implements ViewDefinition {
|
||||
this.updateNativeTransform();
|
||||
}
|
||||
|
||||
[rotateXProperty.getDefault](): number {
|
||||
return 0;
|
||||
}
|
||||
[rotateXProperty.setNative](value: number) {
|
||||
this.updateNativeTransform();
|
||||
}
|
||||
|
||||
[rotateYProperty.getDefault](): number {
|
||||
return 0;
|
||||
}
|
||||
[rotateYProperty.setNative](value: number) {
|
||||
this.updateNativeTransform();
|
||||
}
|
||||
|
||||
[perspectiveProperty.getDefault](): number {
|
||||
return 300;
|
||||
}
|
||||
[perspectiveProperty.setNative](value: number) {
|
||||
this.updateNativeTransform();
|
||||
}
|
||||
|
||||
[scaleXProperty.getDefault](): number {
|
||||
return 1;
|
||||
}
|
||||
|
@ -472,6 +472,15 @@ function convertToPaddings(this: void, value: string | Length): [CssProperty<any
|
||||
export const rotateProperty = new CssAnimationProperty<Style, number>({ name: "rotate", cssName: "rotate", defaultValue: 0, valueConverter: parseFloat });
|
||||
rotateProperty.register(Style);
|
||||
|
||||
export const rotateXProperty = new CssAnimationProperty<Style, number>({ name: "rotateX", cssName: "rotatex", defaultValue: 0, valueConverter: parseFloat });
|
||||
rotateXProperty.register(Style);
|
||||
|
||||
export const rotateYProperty = new CssAnimationProperty<Style, number>({ name: "rotateY", cssName: "rotatey", defaultValue: 0, valueConverter: parseFloat });
|
||||
rotateYProperty.register(Style);
|
||||
|
||||
export const perspectiveProperty = new CssAnimationProperty<Style, number>({ name: "perspective", cssName: "perspective", defaultValue: 1000, valueConverter: parseFloat });
|
||||
perspectiveProperty.register(Style);
|
||||
|
||||
export const scaleXProperty = new CssAnimationProperty<Style, number>({ name: "scaleX", cssName: "scaleX", defaultValue: 1, valueConverter: parseFloat });
|
||||
scaleXProperty.register(Style);
|
||||
|
||||
@ -500,6 +509,8 @@ const transformProperty = new ShorthandProperty<Style, string>({
|
||||
let translateX = this.translateX;
|
||||
let translateY = this.translateY;
|
||||
let rotate = this.rotate;
|
||||
let rotateX = this.rotateX;
|
||||
let rotateY = this.rotateY;
|
||||
let result = "";
|
||||
if (translateX !== 0 || translateY !== 0) {
|
||||
result += `translate(${translateX}, ${translateY}) `;
|
||||
@ -507,7 +518,8 @@ const transformProperty = new ShorthandProperty<Style, string>({
|
||||
if (scaleX !== 1 || scaleY !== 1) {
|
||||
result += `scale(${scaleX}, ${scaleY}) `;
|
||||
}
|
||||
if (rotate !== 0) {
|
||||
if (rotateX !== 0 || rotateY !== 0 || rotate !== 0) {
|
||||
result += `rotate(${rotateX}, ${rotateY}, ${rotate}) `;
|
||||
result += `rotate (${rotate})`;
|
||||
}
|
||||
|
||||
@ -519,13 +531,16 @@ transformProperty.register(Style);
|
||||
|
||||
const IDENTITY_TRANSFORMATION = {
|
||||
translate: { x: 0, y: 0 },
|
||||
rotate: 0,
|
||||
rotate: { x: 0, y: 0, z: 0 },
|
||||
scale: { x: 1, y: 1 },
|
||||
};
|
||||
|
||||
const TRANSFORM_SPLITTER = new RegExp(/\s*(.+?)\((.*?)\)/g);
|
||||
const TRANSFORMATIONS = Object.freeze([
|
||||
"rotate",
|
||||
"rotateX",
|
||||
"rotateY",
|
||||
"rotate3d",
|
||||
"translate",
|
||||
"translate3d",
|
||||
"translateX",
|
||||
@ -547,7 +562,28 @@ const STYLE_TRANSFORMATION_MAP = Object.freeze({
|
||||
"translateX": ({ x }) => ({ property: "translate", value: { x, y: IDENTITY_TRANSFORMATION.translate.y } }),
|
||||
"translateY": ({ y }) => ({ property: "translate", value: { y, x: IDENTITY_TRANSFORMATION.translate.x } }),
|
||||
|
||||
"rotate": value => ({ property: "rotate", value }),
|
||||
"rotate3d": value => ({ property: "rotate", value }),
|
||||
"rotateX": (x) => ({
|
||||
property: "rotate", value: {
|
||||
x,
|
||||
y: IDENTITY_TRANSFORMATION.rotate.y,
|
||||
z: IDENTITY_TRANSFORMATION.rotate.z
|
||||
}
|
||||
}),
|
||||
"rotateY": (y) => ({
|
||||
property: "rotate", value: {
|
||||
x: IDENTITY_TRANSFORMATION.rotate.x,
|
||||
y,
|
||||
z: IDENTITY_TRANSFORMATION.rotate.z
|
||||
}
|
||||
}),
|
||||
"rotate": (z) => ({
|
||||
property: "rotate", value: {
|
||||
x: IDENTITY_TRANSFORMATION.rotate.x,
|
||||
y: IDENTITY_TRANSFORMATION.rotate.y,
|
||||
z
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
function convertToTransform(value: string): [CssProperty<any, any>, any][] {
|
||||
@ -564,7 +600,9 @@ function convertToTransform(value: string): [CssProperty<any, any>, any][] {
|
||||
[scaleXProperty, scale.x],
|
||||
[scaleYProperty, scale.y],
|
||||
|
||||
[rotateProperty, rotate],
|
||||
[rotateProperty, rotate.z],
|
||||
[rotateXProperty, rotate.x],
|
||||
[rotateYProperty, rotate.y],
|
||||
];
|
||||
}
|
||||
|
||||
@ -619,13 +657,13 @@ function normalizeTransformation({ property, value }: Transformation): Transform
|
||||
function convertTransformValue(property: string, stringValue: string)
|
||||
: TransformationValue {
|
||||
|
||||
const [x, y = x] = stringValue.split(",").map(parseFloat);
|
||||
const [x, y = x, z = y] = stringValue.split(",").map(parseFloat);
|
||||
|
||||
if (property === "rotate") {
|
||||
if (property === "rotate" || property === "rotateX" || property === "rotateY") {
|
||||
return stringValue.slice(-3) === "rad" ? radiansToDegrees(x) : x;
|
||||
}
|
||||
|
||||
return { x, y };
|
||||
return { x, y, z };
|
||||
}
|
||||
|
||||
// Background properties.
|
||||
|
@ -525,6 +525,8 @@ export class CssState {
|
||||
const view = this.viewRef.get();
|
||||
if (view) {
|
||||
view.style["keyframe:rotate"] = unsetValue;
|
||||
view.style["keyframe:rotateX"] = unsetValue;
|
||||
view.style["keyframe:rotateY"] = unsetValue;
|
||||
view.style["keyframe:scaleX"] = unsetValue;
|
||||
view.style["keyframe:scaleY"] = unsetValue;
|
||||
view.style["keyframe:translateX"] = unsetValue;
|
||||
|
@ -53,6 +53,9 @@ export class Style extends Observable {
|
||||
public backgroundInternal: Background;
|
||||
|
||||
public rotate: number;
|
||||
public rotateX: number;
|
||||
public rotateY: number;
|
||||
public perspective: number;
|
||||
public scaleX: number;
|
||||
public scaleY: number;
|
||||
public translateX: dip;
|
||||
|
@ -86,6 +86,10 @@ export class Style extends Observable implements StyleDefinition {
|
||||
public backgroundInternal: Background;
|
||||
|
||||
public rotate: number;
|
||||
public rotateX: number;
|
||||
public rotateY: number;
|
||||
public perspective: number;
|
||||
|
||||
public scaleX: number;
|
||||
public scaleY: number;
|
||||
public translateX: dip;
|
||||
|
9
nativescript-core/utils/native-helper.d.ts
vendored
9
nativescript-core/utils/native-helper.d.ts
vendored
@ -155,5 +155,14 @@ export module ios {
|
||||
*/
|
||||
export function getVisibleViewController(rootViewController: any/* UIViewController*/): any/* UIViewController*/;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param transform Applies a rotation transform over X,Y and Z axis
|
||||
* @param x Rotation over X axis in degrees
|
||||
* @param y Rotation over Y axis in degrees
|
||||
* @param z Rotation over Z axis in degrees
|
||||
*/
|
||||
export function applyRotateTransform(transform: any /* CATransform3D*/, x: number, y: number, z: number): any /* CATransform3D*/;
|
||||
|
||||
export class UIDocumentInteractionControllerDelegateImpl { }
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ import {
|
||||
write as traceWrite
|
||||
} from "../trace";
|
||||
|
||||
const radToDeg = Math.PI / 180;
|
||||
|
||||
function isOrientationLandscape(orientation: number) {
|
||||
return orientation === UIDeviceOrientation.LandscapeLeft /* 3 */ ||
|
||||
orientation === UIDeviceOrientation.LandscapeRight /* 4 */;
|
||||
@ -114,6 +116,22 @@ export module ios {
|
||||
|
||||
}
|
||||
|
||||
export function applyRotateTransform(transform: CATransform3D, x: number, y: number, z: number): CATransform3D {
|
||||
if (x) {
|
||||
transform = CATransform3DRotate(transform, x * radToDeg, 1, 0, 0);
|
||||
}
|
||||
|
||||
if (y) {
|
||||
transform = CATransform3DRotate(transform, y * radToDeg, 0, 1, 0);
|
||||
}
|
||||
|
||||
if (z) {
|
||||
transform = CATransform3DRotate(transform, z * radToDeg, 0, 0, 1);
|
||||
}
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
export class UIDocumentInteractionControllerDelegateImpl extends NSObject implements UIDocumentInteractionControllerDelegate {
|
||||
public static ObjCProtocols = [UIDocumentInteractionControllerDelegate];
|
||||
|
||||
|
1
nativescript-core/utils/utils.d.ts
vendored
1
nativescript-core/utils/utils.d.ts
vendored
@ -251,6 +251,7 @@ export module ios {
|
||||
* Returns the visible UIViewController.
|
||||
*/
|
||||
export function getVisibleViewController(rootViewController: any/* UIViewController*/): any/* UIViewController*/;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -433,6 +433,7 @@ function animateExtentAndAssertExpected(along: "height" | "width", value: Percen
|
||||
expectedNumber,
|
||||
`PercentLength.toDevicePixels(${inputString}) should be "${expectedNumber}" but is "${observedNumber}"`
|
||||
);
|
||||
|
||||
assertIOSNativeTransformIsCorrect(label);
|
||||
});
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ export function test_ReadTransformAllSet() {
|
||||
const animation = createAnimationFromCSS(css, "test");
|
||||
const { rotate, scale, translate } = getTransformsValues(animation.keyframes[0].declarations);
|
||||
|
||||
TKUnit.assertAreClose(rotate, 10, DELTA);
|
||||
TKUnit.assertAreClose(rotate.z, 10, DELTA);
|
||||
|
||||
TKUnit.assertAreClose(scale.x, 5, SCALE_DELTA);
|
||||
TKUnit.assertAreClose(scale.y, 1, SCALE_DELTA);
|
||||
@ -136,7 +136,7 @@ export function test_ReadTransformNone() {
|
||||
const animation = createAnimationFromCSS(css, "test");
|
||||
const { rotate, scale, translate } = getTransformsValues(animation.keyframes[0].declarations);
|
||||
|
||||
TKUnit.assertEqual(rotate, 0);
|
||||
TKUnit.assertEqual(rotate.z, 0);
|
||||
|
||||
TKUnit.assertEqual(scale.x, 1);
|
||||
TKUnit.assertEqual(scale.y, 1);
|
||||
@ -271,7 +271,7 @@ export function test_ReadRotate() {
|
||||
const { rotate } = getTransforms(animation.keyframes[0].declarations);
|
||||
|
||||
TKUnit.assertEqual(rotate.property, "rotate");
|
||||
TKUnit.assertAreClose(rotate.value, 5, DELTA);
|
||||
TKUnit.assertAreClose(rotate.value.z, 5, DELTA);
|
||||
}
|
||||
|
||||
export function test_ReadRotateDeg() {
|
||||
@ -280,7 +280,7 @@ export function test_ReadRotateDeg() {
|
||||
const { rotate } = getTransforms(animation.keyframes[0].declarations);
|
||||
|
||||
TKUnit.assertEqual(rotate.property, "rotate");
|
||||
TKUnit.assertAreClose(rotate.value, 45, DELTA);
|
||||
TKUnit.assertAreClose(rotate.value.z, 45, DELTA);
|
||||
}
|
||||
|
||||
export function test_ReadRotateRad() {
|
||||
@ -289,7 +289,7 @@ export function test_ReadRotateRad() {
|
||||
const { rotate } = getTransforms(animation.keyframes[0].declarations);
|
||||
|
||||
TKUnit.assertEqual(rotate.property, "rotate");
|
||||
TKUnit.assertAreClose(rotate.value, 45, DELTA);
|
||||
TKUnit.assertAreClose(rotate.value.z, 45, DELTA);
|
||||
}
|
||||
|
||||
export function test_ReadAnimationWithUnsortedKeyframes() {
|
||||
|
@ -576,6 +576,14 @@
|
||||
public static getRotate(view: android.view.View): number;
|
||||
public static setRotate(view: android.view.View, value: number): void;
|
||||
|
||||
public static getRotateX(view: android.view.View): number;
|
||||
public static setRotateX(view: android.view.View, value: number): void;
|
||||
|
||||
public static getRotateY(view: android.view.View): number;
|
||||
public static setRotateY(view: android.view.View, value: number): void;
|
||||
|
||||
public static setPerspective(view: android.view.View, value: number): void;
|
||||
|
||||
public static getScaleX(view: android.view.View): number;
|
||||
public static setScaleX(view: android.view.View, value: number): void;
|
||||
|
||||
|
Reference in New Issue
Block a user