Merge branch 'master' into myankov/merge-release-master

This commit is contained in:
Martin Yankov
2019-05-21 17:12:20 +03:00
committed by GitHub
23 changed files with 933 additions and 48 deletions

View File

@@ -11,6 +11,7 @@ import { View } from "../core/view";
// Types.
import { Color } from "../../color";
import { isEnabled as traceEnabled, write as traceWrite, categories as traceCategories, messageType as traceType } from "../../trace";
import { PercentLength } from "../styling/style-properties";
export { Color, traceEnabled, traceWrite, traceCategories, traceType };
export { AnimationPromise } from ".";
@@ -21,6 +22,8 @@ export module Properties {
export const translate = "translate";
export const rotate = "rotate";
export const scale = "scale";
export const height = "height";
export const width = "width";
}
export interface PropertyAnimation {
@@ -166,6 +169,9 @@ export abstract class AnimationBase implements AnimationBaseDefinition {
throw new Error(`Property ${item} must be valid Pair. Value: ${animationDefinition[item]}`);
} else if (item === Properties.backgroundColor && !Color.isValid(animationDefinition.backgroundColor)) {
throw new Error(`Property ${item} must be valid color. Value: ${animationDefinition[item]}`);
} 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]);
}
}
@@ -237,8 +243,34 @@ export abstract class AnimationBase implements AnimationBaseDefinition {
});
}
// height
if (animationDefinition.height !== undefined) {
propertyAnimations.push({
target: animationDefinition.target,
property: Properties.height,
value: animationDefinition.height,
duration: animationDefinition.duration,
delay: animationDefinition.delay,
iterations: animationDefinition.iterations,
curve: animationDefinition.curve
});
}
// width
if (animationDefinition.width !== undefined) {
propertyAnimations.push({
target: animationDefinition.target,
property: Properties.width,
value: animationDefinition.width,
duration: animationDefinition.duration,
delay: animationDefinition.delay,
iterations: animationDefinition.iterations,
curve: animationDefinition.curve
});
}
if (propertyAnimations.length === 0) {
throw new Error("No animation property specified.");
throw new Error("No known animation properties specified");
}
return propertyAnimations;
@@ -255,4 +287,4 @@ export abstract class AnimationBase implements AnimationBaseDefinition {
curve: animation.curve
});
}
}
}

View File

@@ -5,13 +5,13 @@ import { View } from "../core/view";
import { AnimationBase, Properties, PropertyAnimation, CubicBezierAnimationCurve, AnimationPromise, Color, traceWrite, traceEnabled, traceCategories, traceType } from "./animation-common";
import {
opacityProperty, backgroundColorProperty, rotateProperty,
translateXProperty, translateYProperty, scaleXProperty, scaleYProperty
translateXProperty, translateYProperty, scaleXProperty, scaleYProperty,
heightProperty, widthProperty, PercentLength
} from "../styling/style-properties";
import { layout } from "../../utils/utils";
import { device } from "../../platform";
import { device, screen } from "../../platform";
import lazy from "../../utils/lazy";
export * from "./animation-common";
interface AnimationDefinitionInternal extends AnimationDefinition {
@@ -38,6 +38,8 @@ propertyKeys[Properties.opacity] = Symbol(keyPrefix + Properties.opacity);
propertyKeys[Properties.rotate] = Symbol(keyPrefix + Properties.rotate);
propertyKeys[Properties.scale] = Symbol(keyPrefix + Properties.scale);
propertyKeys[Properties.translate] = Symbol(keyPrefix + Properties.translate);
propertyKeys[Properties.height] = Symbol(keyPrefix + Properties.height);
propertyKeys[Properties.width] = Symbol(keyPrefix + Properties.width);
export function _resolveAnimationCurve(curve: string | CubicBezierAnimationCurve | android.view.animation.Interpolator | android.view.animation.LinearInterpolator): android.view.animation.Interpolator {
switch (curve) {
@@ -224,7 +226,7 @@ export class Animation extends AnimationBase {
}
}
private _onAndroidAnimationCancel() { // tslint:disable-line
private _onAndroidAnimationCancel() { // tslint:disable-line
this._propertyResetCallbacks.forEach(v => v());
this._rejectAnimationFinishedPromise();
@@ -450,9 +452,48 @@ export class Animation extends AnimationBase {
}));
animators.push(android.animation.ObjectAnimator.ofFloat(nativeView, "rotation", nativeArray));
break;
case Properties.width:
case Properties.height: {
const isVertical: boolean = propertyAnimation.property === "height";
const extentProperty = isVertical ? heightProperty : widthProperty;
extentProperty._initDefaultNativeValue(style);
nativeArray = Array.create("float", 2);
let toValue = propertyAnimation.value;
let parent = propertyAnimation.target.parent as View;
if (!parent) {
throw new Error(`cannot animate ${propertyAnimation.property} on root view`);
}
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();
const targetStyle: string = setLocal ? extentProperty.name : extentProperty.keyframe;
originalValue1 = nativeHeight / screen.mainScreen.scale;
nativeArray[0] = originalValue1;
nativeArray[1] = toValue;
let extentAnimator = android.animation.ValueAnimator.ofFloat(nativeArray);
extentAnimator.addUpdateListener(new android.animation.ValueAnimator.AnimatorUpdateListener({
onAnimationUpdate(animator: android.animation.ValueAnimator) {
const argb = (<java.lang.Float>animator.getAnimatedValue()).floatValue();
propertyAnimation.target.style[setLocal ? extentProperty.name : extentProperty.keyframe] = argb;
}
}));
propertyUpdateCallbacks.push(checkAnimation(() => {
propertyAnimation.target.style[targetStyle] = propertyAnimation.value;
}));
propertyResetCallbacks.push(checkAnimation(() => {
propertyAnimation.target.style[targetStyle] = originalValue1;
if (propertyAnimation.target.nativeViewProtected) {
const setter = propertyAnimation.target[extentProperty.setNative];
setter(propertyAnimation.target.style[propertyAnimation.property]);
}
}));
animators.push(extentAnimator);
break;
}
default:
throw new Error("Cannot animate " + propertyAnimation.property);
throw new Error(`Animating property '${propertyAnimation.property}' is unsupported`);
}
for (let i = 0, length = animators.length; i < length; i++) {

View File

@@ -3,6 +3,7 @@
*/ /** */
import { View, Color } from "../core/view";
import { PercentLength } from "../styling/style-properties";
/**
* Defines animation options for the View.animate method.
@@ -33,6 +34,16 @@ export interface AnimationDefinition {
*/
scale?: Pair;
/**
* Animates the height of a view.
*/
height?: PercentLength | string;
/**
* Animates the width of a view.
*/
width?: PercentLength | string;
/**
* Animates the rotate affine transform of the view. Value should be a number specifying the rotation amount in degrees.
*/

View File

@@ -4,9 +4,12 @@ import { View } from "../core/view";
import { AnimationBase, Properties, PropertyAnimation, CubicBezierAnimationCurve, AnimationPromise, traceWrite, traceEnabled, traceCategories, traceType } from "./animation-common";
import {
opacityProperty, backgroundColorProperty, rotateProperty,
translateXProperty, translateYProperty, scaleXProperty, scaleYProperty
translateXProperty, translateYProperty, scaleXProperty, scaleYProperty,
heightProperty, widthProperty, PercentLength
} from "../styling/style-properties";
import { screen } from "../../platform";
export * from "./animation-common";
let _transform = "_transform";
@@ -78,6 +81,12 @@ class AnimationDelegateImpl extends NSObject implements CAAnimationDelegate {
targetStyle[setLocal ? translateXProperty.name : translateXProperty.keyframe] = value.x;
targetStyle[setLocal ? translateYProperty.name : translateYProperty.keyframe] = value.y;
break;
case Properties.height:
targetStyle[setLocal ? heightProperty.name : heightProperty.keyframe] = value;
break;
case Properties.width:
targetStyle[setLocal ? widthProperty.name : widthProperty.keyframe] = value;
break;
case Properties.scale:
targetStyle[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = value.x === 0 ? 0.001 : value.x;
targetStyle[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = value.y === 0 ? 0.001 : value.y;
@@ -263,8 +272,10 @@ export class Animation extends AnimationBase {
let nativeView = <UIView>animation.target.nativeViewProtected;
let propertyNameToAnimate = animation.property;
let value = animation.value;
let originalValue;
let toValue = animation.value;
let fromValue;
const parent = animation.target.parent as View;
const screenScale: number = screen.mainScreen.scale;
let tempRotate = (animation.target.rotate || 0) * Math.PI / 180;
let abs;
@@ -277,18 +288,18 @@ export class Animation extends AnimationBase {
animation._propertyResetCallback = (value, valueSource) => {
animation.target.style[setLocal ? backgroundColorProperty.name : backgroundColorProperty.keyframe] = value;
};
originalValue = nativeView.layer.backgroundColor;
fromValue = nativeView.layer.backgroundColor;
if (nativeView instanceof UILabel) {
nativeView.setValueForKey(UIColor.clearColor, "backgroundColor");
}
value = value.CGColor;
toValue = toValue.CGColor;
break;
case Properties.opacity:
animation._originalValue = animation.target.opacity;
animation._propertyResetCallback = (value, valueSource) => {
animation.target.style[setLocal ? opacityProperty.name : opacityProperty.keyframe] = value;
};
originalValue = nativeView.layer.opacity;
fromValue = nativeView.layer.opacity;
break;
case Properties.rotate:
animation._originalValue = animation.target.rotate !== undefined ? animation.target.rotate : 0;
@@ -296,44 +307,44 @@ export class Animation extends AnimationBase {
animation.target.style[setLocal ? rotateProperty.name : rotateProperty.keyframe] = value;
};
propertyNameToAnimate = "transform.rotation";
originalValue = nativeView.layer.valueForKeyPath("transform.rotation");
if (animation.target.rotate !== undefined && animation.target.rotate !== 0 && Math.floor(value / 360) - value / 360 === 0) {
originalValue = animation.target.rotate * Math.PI / 180;
fromValue = nativeView.layer.valueForKeyPath("transform.rotation");
if (animation.target.rotate !== undefined && animation.target.rotate !== 0 && Math.floor(toValue / 360) - toValue / 360 === 0) {
fromValue = animation.target.rotate * Math.PI / 180;
}
value = value * Math.PI / 180;
abs = fabs(originalValue - value);
if (abs < 0.001 && originalValue !== tempRotate) {
originalValue = tempRotate;
toValue = toValue * Math.PI / 180;
abs = fabs(fromValue - toValue);
if (abs < 0.001 && fromValue !== tempRotate) {
fromValue = tempRotate;
}
break;
case Properties.translate:
animation._originalValue = { x: animation.target.translateX, y: animation.target.translateY };
animation._originalValue = {x: animation.target.translateX, y: animation.target.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;
};
propertyNameToAnimate = "transform";
originalValue = NSValue.valueWithCATransform3D(nativeView.layer.transform);
value = NSValue.valueWithCATransform3D(CATransform3DTranslate(nativeView.layer.transform, value.x, value.y, 0));
fromValue = NSValue.valueWithCATransform3D(nativeView.layer.transform);
toValue = NSValue.valueWithCATransform3D(CATransform3DTranslate(nativeView.layer.transform, toValue.x, toValue.y, 0));
break;
case Properties.scale:
if (value.x === 0) {
value.x = 0.001;
if (toValue.x === 0) {
toValue.x = 0.001;
}
if (value.y === 0) {
value.y = 0.001;
if (toValue.y === 0) {
toValue.y = 0.001;
}
animation._originalValue = { x: animation.target.scaleX, y: animation.target.scaleY };
animation._originalValue = {x: animation.target.scaleX, y: animation.target.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;
};
propertyNameToAnimate = "transform";
originalValue = NSValue.valueWithCATransform3D(nativeView.layer.transform);
value = NSValue.valueWithCATransform3D(CATransform3DScale(nativeView.layer.transform, value.x, value.y, 1));
fromValue = NSValue.valueWithCATransform3D(nativeView.layer.transform);
toValue = NSValue.valueWithCATransform3D(CATransform3DScale(nativeView.layer.transform, toValue.x, toValue.y, 1));
break;
case _transform:
originalValue = NSValue.valueWithCATransform3D(nativeView.layer.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
@@ -345,10 +356,33 @@ export class Animation extends AnimationBase {
animation.target.style[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = value.ys;
};
propertyNameToAnimate = "transform";
value = NSValue.valueWithCATransform3D(Animation._createNativeAffineTransform(animation));
toValue = NSValue.valueWithCATransform3D(Animation._createNativeAffineTransform(animation));
break;
case Properties.width:
case Properties.height:
const direction: string = animation.property;
const isHeight: boolean = direction === "height";
propertyNameToAnimate = "bounds";
if (!parent) {
throw new Error(`cannot animate ${direction} on root view`);
}
const parentExtent: number = isHeight ? parent.getMeasuredHeight() : parent.getMeasuredWidth();
const asNumber = PercentLength.toDevicePixels(PercentLength.parse(toValue), parentExtent, parentExtent) / screenScale;
let currentBounds = nativeView.layer.bounds;
let extentX = isHeight ? currentBounds.size.width : asNumber;
let extentY = isHeight ? asNumber : currentBounds.size.height;
fromValue = NSValue.valueWithCGRect(currentBounds);
toValue = NSValue.valueWithCGRect(
CGRectMake(currentBounds.origin.x, currentBounds.origin.y, extentX, extentY)
);
animation._originalValue = animation.target.height;
animation._propertyResetCallback = (value, valueSource) => {
const prop = isHeight ? heightProperty : widthProperty;
animation.target.style[setLocal ? prop.name : prop.keyframe] = value;
};
break;
default:
throw new Error("Cannot animate " + animation.property);
throw new Error(`Animating property '${animation.property}' is unsupported`);
}
let duration = 0.3;
@@ -373,8 +407,8 @@ export class Animation extends AnimationBase {
return {
propertyNameToAnimate: propertyNameToAnimate,
fromValue: originalValue,
toValue: value,
fromValue: fromValue,
toValue: toValue,
duration: duration,
repeatCount: repeatCount,
delay: delay
@@ -450,6 +484,14 @@ export class Animation extends AnimationBase {
case Properties.opacity:
animation.target.opacity = args.toValue;
break;
case Properties.height:
case Properties.width:
animation._originalValue = animation.target[animation.property];
nativeView.layer.setValueForKey(args.toValue, args.propertyNameToAnimate);
animation._propertyResetCallback = function (value) {
animation.target[animation.property] = value;
};
break;
case Properties.rotate:
nativeView.layer.setValueForKey(args.toValue, args.propertyNameToAnimate);
break;
@@ -458,11 +500,11 @@ export class Animation extends AnimationBase {
nativeView.layer.setValueForKey(args.toValue, args.propertyNameToAnimate);
animation._propertyResetCallback = function (value) {
nativeView.layer.transform = value;
}
};
break;
}
}, function (finished: boolean) {
if (finished) {
}, function (animationDidFinish: boolean) {
if (animationDidFinish) {
if (animation.property === _transform) {
if (animation.value[Properties.translate] !== undefined) {
animation.target.translateX = animation.value[Properties.translate].x;
@@ -480,10 +522,10 @@ export class Animation extends AnimationBase {
}
}
if (finishedCallback) {
let cancelled = !finished;
let cancelled = !animationDidFinish;
finishedCallback(cancelled);
}
if (finished && nextAnimation) {
if (animationDidFinish && nextAnimation) {
nextAnimation();
}
});

View File

@@ -21,7 +21,8 @@ import {
backgroundColorProperty,
scaleXProperty, scaleYProperty,
translateXProperty, translateYProperty,
rotateProperty, opacityProperty
rotateProperty, opacityProperty,
widthProperty, heightProperty, PercentLength
} from "../styling/style-properties";
export class Keyframes implements KeyframesDefinition {
@@ -62,6 +63,8 @@ interface Keyframe {
translate?: { x: number, y: number };
rotate?: number;
opacity?: number;
width?: PercentLength;
height?: PercentLength;
valueSource?: "keyframe" | "animation";
duration?: number;
curve?: any;
@@ -213,6 +216,12 @@ export class KeyframeAnimation implements KeyframeAnimationDefinition {
if ("opacity" in animation) {
view.style[opacityProperty.keyframe] = animation.opacity;
}
if ("height" in animation) {
view.style[heightProperty.keyframe] = animation.height;
}
if ("width" in animation) {
view.style[widthProperty.keyframe] = animation.width;
}
setTimeout(() => this.animate(view, 1, iterations), 1);
}
@@ -287,5 +296,11 @@ export class KeyframeAnimation implements KeyframeAnimationDefinition {
if ("opacity" in animation) {
view.style[opacityProperty.keyframe] = unsetValue;
}
if ("height" in animation) {
view.style[heightProperty.keyframe] = unsetValue;
}
if ("width" in animation) {
view.style[widthProperty.keyframe] = unsetValue;
}
}
}

View File

@@ -85,8 +85,8 @@ export const opacityProperty: CssAnimationProperty<Style, number>;
export const minWidthProperty: CssProperty<Style, dip | LengthDipUnit | LengthPxUnit>;
export const minHeightProperty: CssProperty<Style, dip | LengthDipUnit | LengthPxUnit>;
export const widthProperty: CssProperty<Style, PercentLength>;
export const heightProperty: CssProperty<Style, PercentLength>;
export const widthProperty: CssAnimationProperty<Style, PercentLength>;
export const heightProperty: CssAnimationProperty<Style, PercentLength>;
export const lineHeightProperty: CssProperty<Style, number>;
export const marginProperty: ShorthandProperty<Style, string | PercentLength>;
export const marginLeftProperty: CssProperty<Style, PercentLength>;

View File

@@ -49,13 +49,19 @@ function equalsCommon(a: PercentLength, b: PercentLength): boolean {
if (typeof b === "number") {
return a == b; // tslint:disable-line
}
if (!b) {
return false;
}
return b.unit == "dip" && a == b.value; // tslint:disable-line
}
if (b == "auto") { // tslint:disable-line
return false;
}
if (typeof b === "number") {
return a.unit == "dip" && a.value == b; // tslint:disable-line
return a ? (a.unit == "dip" && a.value == b) : false; // tslint:disable-line
}
if (!a || !b) {
return false;
}
return a.value == b.value && a.unit == b.unit; // tslint:disable-line
}
@@ -84,6 +90,9 @@ function toDevicePixelsCommon(length: PercentLength, auto: number = Number.NaN,
if (typeof length === "number") {
return layout.round(layout.toDevicePixels(length));
}
if (!length) {
return auto;
}
switch (length.unit) {
case "px":
return layout.round(length.value);
@@ -109,6 +118,7 @@ export namespace PercentLength {
if (percentIndex !== (stringValue.length - 1) || percentIndex === 0) {
value = Number.NaN;
} else {
// Normalize result to values between -1 and 1
value = parseFloat(stringValue.substring(0, stringValue.length - 1).trim()) / 100;
}
@@ -188,10 +198,28 @@ export const minHeightProperty = new CssProperty<Style, Length>({
});
minHeightProperty.register(Style);
export const widthProperty = new CssProperty<Style, PercentLength>({ name: "width", cssName: "width", defaultValue: "auto", affectsLayout: isIOS, equalityComparer: Length.equals, valueConverter: PercentLength.parse });
export const widthProperty = new CssAnimationProperty<Style, PercentLength>({
name: "width", cssName: "width", defaultValue: "auto", equalityComparer: Length.equals,
// TODO: CSSAnimationProperty was needed for keyframe (copying other impls), but `affectsLayout` does not exist
// on the animation property, so fake it here. x_x
valueChanged: (target, oldValue, newValue) => {
if (isIOS) {
target.view.requestLayout();
}
}, valueConverter: PercentLength.parse });
widthProperty.register(Style);
export const heightProperty = new CssProperty<Style, PercentLength>({ name: "height", cssName: "height", defaultValue: "auto", affectsLayout: isIOS, equalityComparer: Length.equals, valueConverter: PercentLength.parse });
export const heightProperty = new CssAnimationProperty<Style, PercentLength>({
name: "height", cssName: "height", defaultValue: "auto", equalityComparer: Length.equals,
// TODO: CSSAnimationProperty was needed for keyframe (copying other impls), but `affectsLayout` does not exist
// on the animation property, so fake it here. -_-
valueChanged: (target, oldValue, newValue) => {
if (isIOS) {
target.view.requestLayout();
}
}, valueConverter: PercentLength.parse,
});
heightProperty.register(Style);
const marginProperty = new ShorthandProperty<Style, string | PercentLength>({