diff --git a/tns-core-modules/ui/animation/animation.ios.ts b/tns-core-modules/ui/animation/animation.ios.ts index cce5b1add..74346d4a6 100644 --- a/tns-core-modules/ui/animation/animation.ios.ts +++ b/tns-core-modules/ui/animation/animation.ios.ts @@ -1,5 +1,5 @@ import { AnimationDefinition } from "."; -import { View } from "../core/view"; +import { View, layout } from "../core/view"; import { AnimationBase, Properties, PropertyAnimation, CubicBezierAnimationCurve, AnimationPromise, traceWrite, traceEnabled, traceCategories } from "./animation-common"; import { @@ -589,7 +589,9 @@ export class Animation extends AnimationBase { export function _getTransformMismatchErrorMessage(view: View): string { // Order is important: translate, rotate, scale let result: CGAffineTransform = CGAffineTransformIdentity; - result = CGAffineTransformTranslate(result, Length.toDevicePixels(view.translateX || 0, 0), Length.toDevicePixels(view.translateY || 0, 0)); + const tx = layout.toDeviceIndependentPixels(Length.toDevicePixels(view.translateX || 0, 0)); + const ty = layout.toDeviceIndependentPixels(Length.toDevicePixels(view.translateY || 0, 0)); + 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); diff --git a/tns-core-modules/ui/core/view-base/view-base.d.ts b/tns-core-modules/ui/core/view-base/view-base.d.ts index 93a273472..abc7defa4 100644 --- a/tns-core-modules/ui/core/view-base/view-base.d.ts +++ b/tns-core-modules/ui/core/view-base/view-base.d.ts @@ -247,6 +247,19 @@ export abstract class ViewBase extends Observable { //@private public _styleScope: any; + + /** + * Determines the depth of batchUpdates. + * When the value is 0 the current updates are not batched. + * If the value is 1 or greater, the current updates are batched. + * Do not set this field, the _batchUpdate method is responsible to keep the count up to date. + */ + public _batchUpdateScope: number; + + /** + * Allow multiple updates to be performed on the instance at once. + */ + public _batchUpdate(callback: () => T): T; //@endprivate } diff --git a/tns-core-modules/ui/core/view-base/view-base.ts b/tns-core-modules/ui/core/view-base/view-base.ts index 77737d81f..6f3b06d62 100644 --- a/tns-core-modules/ui/core/view-base/view-base.ts +++ b/tns-core-modules/ui/core/view-base/view-base.ts @@ -279,6 +279,16 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition this._emit("unloaded"); } + public _batchUpdateScope: number; + public _batchUpdate(callback: () => T): T { + try { + ++this._batchUpdateScope; + return callback(); + } finally { + --this._batchUpdateScope; + } + } + private _unloadEachChild() { this.eachChild((child) => { if (child.isLoaded) { @@ -371,13 +381,14 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition private _invalidateCssHandlerSuspended: boolean; private applyCssState(): void { - if (!this._cssState) { - return; - } - - // this.style._beginUpdate(); - this._cssState.apply(); - // this.style._endUpdate(); + this._batchUpdate(() => { + if (!this._cssState) { + this._cancelAllAnimations(); + resetCSSProperties(this.style); + return; + } + this._cssState.apply(); + }); } private pseudoClassAliases = { @@ -847,6 +858,8 @@ ViewBase.prototype._defaultPaddingRight = 0; ViewBase.prototype._defaultPaddingBottom = 0; ViewBase.prototype._defaultPaddingLeft = 0; +ViewBase.prototype._batchUpdateScope = 0; + export const bindingContextProperty = new InheritedProperty({ name: "bindingContext" }); bindingContextProperty.register(ViewBase); @@ -864,8 +877,6 @@ export const classNameProperty = new Property({ classNameProperty.register(ViewBase); function resetStyles(view: ViewBase): void { - view._cancelAllAnimations(); - resetCSSProperties(view.style); view._applyStyleFromScope(); view.eachChild((child) => { resetStyles(child); diff --git a/tns-core-modules/ui/core/view/view.ios.ts b/tns-core-modules/ui/core/view/view.ios.ts index c7e8fb0e9..c97f75f0a 100644 --- a/tns-core-modules/ui/core/view/view.ios.ts +++ b/tns-core-modules/ui/core/view/view.ios.ts @@ -231,8 +231,8 @@ export class View extends ViewCommon { } public updateNativeTransform() { - let translateX = Length.toDevicePixels(this.translateX || 0, 0); - let translateY = Length.toDevicePixels(this.translateY || 0, 0); + let translateX = layout.toDeviceIndependentPixels(Length.toDevicePixels(this.translateX || 0, 0)); + let translateY = layout.toDeviceIndependentPixels(Length.toDevicePixels(this.translateY || 0, 0)); let scaleX = this.scaleX || 1; let scaleY = this.scaleY || 1; let rotate = this.rotate || 0; @@ -241,8 +241,15 @@ export class View extends ViewCommon { newTransform = CGAffineTransformRotate(newTransform, rotate * Math.PI / 180); newTransform = CGAffineTransformScale(newTransform, scaleX === 0 ? 0.001 : scaleX, scaleY === 0 ? 0.001 : scaleY); if (!CGAffineTransformEqualToTransform(this.nativeView.transform, newTransform)) { + let updateSuspended = this._isPresentationLayerUpdateSuspeneded(); + if (!updateSuspended) { + CATransaction.begin(); + } this.nativeView.transform = newTransform; this._hasTransfrom = this.nativeView && !CGAffineTransformEqualToTransform(this.nativeView.transform, CGAffineTransformIdentity); + if (!updateSuspended) { + CATransaction.commit(); + } } } @@ -266,7 +273,7 @@ export class View extends ViewCommon { } public _isPresentationLayerUpdateSuspeneded() { - return this._suspendCATransaction; + return this._suspendCATransaction || this._batchUpdateScope; } [isEnabledProperty.getDefault](): boolean { diff --git a/tns-core-modules/ui/styling/style-scope.ts b/tns-core-modules/ui/styling/style-scope.ts index 58952e839..cd609536a 100644 --- a/tns-core-modules/ui/styling/style-scope.ts +++ b/tns-core-modules/ui/styling/style-scope.ts @@ -102,11 +102,12 @@ export class CssState { matchingSelectors.push(this.view.inlineStyleSelector); } - matchingSelectors.forEach(s => this.applyDescriptors(this.view, s.ruleset)); + matchingSelectors.forEach(s => this.applyDescriptors(s.ruleset)); + matchingSelectors.forEach(s => this.playKeyframeAnimations(s.ruleset)); } - private applyDescriptors(view: ViewBase, ruleset: RuleSet): void { - let style = view.style; + private applyDescriptors(ruleset: RuleSet): void { + let style = this.view.style; ruleset.declarations.forEach(d => { try { // Use the "css:" prefixed name, so that CSS value source is set. @@ -114,23 +115,25 @@ export class CssState { if (cssPropName in style) { style[cssPropName] = d.value; } else { - view[d.property] = d.value; + this.view[d.property] = d.value; } } catch (e) { - traceWrite(`Failed to apply property [${d.property}] with value [${d.value}] to ${view}. ${e}`, traceCategories.Error, traceMessageType.error); + traceWrite(`Failed to apply property [${d.property}] with value [${d.value}] to ${this.view}. ${e}`, traceCategories.Error, traceMessageType.error); } }); + } + private playKeyframeAnimations(ruleset: RuleSet): void { let ruleAnimations: kam.KeyframeAnimationInfo[] = ruleset[animationsSymbol]; if (ruleAnimations) { ensureKeyframeAnimationModule(); for (let animationInfo of ruleAnimations) { let animation = keyframeAnimationModule.KeyframeAnimation.keyframeAnimationFromInfo(animationInfo); if (animation) { - view._registerAnimation(animation); - animation.play(view) - .then(() => { view._unregisterAnimation(animation); }) - .catch((e) => { view._unregisterAnimation(animation); }); + this.view._registerAnimation(animation); + animation.play(this.view) + .then(() => { this.view._unregisterAnimation(animation); }) + .catch((e) => { this.view._unregisterAnimation(animation); }); } } } diff --git a/tns-core-modules/ui/transition/transition.d.ts b/tns-core-modules/ui/transition/transition.d.ts index e8eba2192..c44709e1e 100644 --- a/tns-core-modules/ui/transition/transition.d.ts +++ b/tns-core-modules/ui/transition/transition.d.ts @@ -20,7 +20,7 @@ export class Transition { //@private export function _clearBackwardTransitions(fragment: any): void; export function _clearForwardTransitions(fragment: any): void; -export function _setAndroidFragmentTransitions(cachePagesOnNavigate: boolean, navigationTransition: NavigationTransition, currentFragment: any, newFragment: any, fragmentTransaction: android.app.FragmentTransaction): void; +export function _setAndroidFragmentTransitions(cachePagesOnNavigate: boolean, navigationTransition: NavigationTransition, currentFragment: any, newFragment: any, fragmentTransaction: any): void; export function _onFragmentCreateAnimator(fragment: any, nextAnim: number): any; export function _onFragmentShown(fragment: any, isBack: boolean): void; export function _onFragmentHidden(fragment: any, isBack: boolean, destroyed: boolean): void;