diff --git a/Android/doric/src/main/java/pub/doric/shader/ViewNode.java b/Android/doric/src/main/java/pub/doric/shader/ViewNode.java index 73705098..664253fc 100644 --- a/Android/doric/src/main/java/pub/doric/shader/ViewNode.java +++ b/Android/doric/src/main/java/pub/doric/shader/ViewNode.java @@ -25,10 +25,17 @@ import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.view.View; import android.view.ViewGroup; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; import android.widget.FrameLayout; import android.widget.LinearLayout; import androidx.annotation.NonNull; +import androidx.interpolator.view.animation.FastOutSlowInInterpolator; +import androidx.interpolator.view.animation.LinearOutSlowInInterpolator; import pub.doric.DoricContext; import pub.doric.DoricRegistry; @@ -695,6 +702,7 @@ public abstract class ViewNode extends DoricContextHolder { JSValue repeatMode = value.asObject().getProperty("repeatMode"); JSValue fillMode = value.asObject().getProperty("fillMode"); + JSValue timingFunction = value.asObject().getProperty("timingFunction"); for (int j = 0; j < changeables.size(); j++) { ObjectAnimator animator = parseChangeable(changeables.get(j).asObject(), fillMode); if (repeatCount.isNumber()) { @@ -703,6 +711,9 @@ public abstract class ViewNode extends DoricContextHolder { if (repeatMode.isNumber()) { animator.setRepeatMode(repeatMode.asNumber().toInt()); } + if (timingFunction.isNumber()) { + animator.setInterpolator(getTimingInterpolator(timingFunction.asNumber().toInt())); + } animatorSet.play(animator); } long duration = value.asObject().getProperty("duration").asNumber().toLong(); @@ -711,13 +722,27 @@ public abstract class ViewNode extends DoricContextHolder { if (delayJS.isNumber()) { animatorSet.setStartDelay(delayJS.asNumber().toLong()); } - return animatorSet; } else { return null; } } + private Interpolator getTimingInterpolator(int timingFunction) { + switch (timingFunction) { + case 1: + return new LinearInterpolator(); + case 2: + return new AccelerateInterpolator(); + case 3: + return new DecelerateInterpolator(); + case 4: + return new FastOutSlowInInterpolator(); + default: + return new AccelerateDecelerateInterpolator(); + } + } + private ObjectAnimator parseChangeable(JSObject jsObject, JSValue fillMode) { String key = jsObject.getProperty("key").asString().value(); float startVal = jsObject.getProperty("fromValue").asNumber().toFloat(); diff --git a/demo/src/ComplicatedAnimations.ts b/demo/src/ComplicatedAnimations.ts index 9eb5d3a8..75cdca97 100644 --- a/demo/src/ComplicatedAnimations.ts +++ b/demo/src/ComplicatedAnimations.ts @@ -1,4 +1,4 @@ -import { animate, Group, Panel, gravity, Color, AnimationSet, vlayout, scroller, layoutConfig, IVLayout, modal, IText, network, View, stack, IHLayout, hlayout, IView, text, TranslationAnimation, ScaleAnimation, RotationAnimation, FillMode } from "doric"; +import { animate, Group, Panel, gravity, Color, AnimationSet, vlayout, scroller, layoutConfig, IVLayout, modal, IText, network, View, stack, IHLayout, hlayout, IView, text, TranslationAnimation, ScaleAnimation, RotationAnimation, FillMode, TimingFunction } from "doric"; import { title, colors, box } from "./utils"; function thisLabel(str: string) { @@ -122,6 +122,58 @@ class AnimationDemo extends Panel { } }), ]).apply({ space: 10 } as IHLayout), + hlayout([ + thisLabel('Default').apply({ + onClick: () => { + const translation = new TranslationAnimation + translation.duration = 3000 + translation.fromTranslationX = 0 + translation.toTranslationX = 300 + translation.timingFunction = TimingFunction.Default + view.doAnimation(context, translation) + } + }), + thisLabel('Linear').apply({ + onClick: () => { + const translation = new TranslationAnimation + translation.duration = 3000 + translation.fromTranslationX = 0 + translation.toTranslationX = 300 + translation.timingFunction = TimingFunction.Linear + view.doAnimation(context, translation) + } + }), + thisLabel('EaseIn').apply({ + onClick: () => { + const translation = new TranslationAnimation + translation.duration = 3000 + translation.fromTranslationX = 0 + translation.toTranslationX = 300 + translation.timingFunction = TimingFunction.EaseIn + view.doAnimation(context, translation) + } + }), + thisLabel('EaseOut').apply({ + onClick: () => { + const translation = new TranslationAnimation + translation.duration = 3000 + translation.fromTranslationX = 0 + translation.toTranslationX = 300 + translation.timingFunction = TimingFunction.EaseOut + view.doAnimation(context, translation) + } + }), + thisLabel('EaseInEaseOut').apply({ + onClick: () => { + const translation = new TranslationAnimation + translation.duration = 3000 + translation.fromTranslationX = 0 + translation.toTranslationX = 300 + translation.timingFunction = TimingFunction.EaseInEaseOut + view.doAnimation(context, translation) + } + }), + ]).apply({ space: 10 } as IHLayout), ] ).apply({ space: 10 } as IVLayout), stack([ diff --git a/demo/src/RefreshableDemo.ts b/demo/src/RefreshableDemo.ts index 08e2854e..ea8cfd75 100644 --- a/demo/src/RefreshableDemo.ts +++ b/demo/src/RefreshableDemo.ts @@ -32,7 +32,7 @@ class RefreshableDemo extends Panel { log('stopAnimation') }, setProgressRotation: (rotation: number) => { - refreshImage.setRotation(context, rotation) + refreshImage.rotation = rotation }, }), content: (vlayout([ diff --git a/demo/src/utils.ts b/demo/src/utils.ts index a7c7b2cc..a9ac549a 100644 --- a/demo/src/utils.ts +++ b/demo/src/utils.ts @@ -66,7 +66,7 @@ export function rotatedArrow(context: BridgeContext) { log('stopAnimation') }, setProgressRotation: (rotation: number) => { - refreshImage.setRotation(context, rotation) + refreshImage.rotation = rotation }, }) } \ No newline at end of file diff --git a/iOS/Pod/Classes/Shader/DoricViewNode.m b/iOS/Pod/Classes/Shader/DoricViewNode.m index b51243db..09e9ee8b 100644 --- a/iOS/Pod/Classes/Shader/DoricViewNode.m +++ b/iOS/Pod/Classes/Shader/DoricViewNode.m @@ -454,6 +454,9 @@ - (CAAnimation *)parseAnimation:(id)params { }]; animation.fromValue = [NSValue valueWithCGPoint:from]; animation.toValue = [NSValue valueWithCGPoint:to]; + if (params[@"timingFunction"]) { + animation.timingFunction = [self translateToTimingFunction:params[@"timingFunction"]]; + } [self setAnimation:animation params:params]; return animation; } else { @@ -462,6 +465,9 @@ - (CAAnimation *)parseAnimation:(id)params { [changeables forEach:^(NSDictionary *obj) { CABasicAnimation *animation = [self parseChangeable:obj fillMode:params[@"fillMode"]]; + if (params[@"timingFunction"]) { + animation.timingFunction = [self translateToTimingFunction:params[@"timingFunction"]]; + } [animations addObject:animation]; }]; animationGroup.animations = animations; @@ -603,16 +609,18 @@ - (CABasicAnimation *)parseChangeable:(NSDictionary *)params fillMode:(NSNumber return animation; } -- (CAMediaTimingFillMode)translateToFillMode:(NSNumber *)fillMode { - switch ([fillMode integerValue]) { +- (CAMediaTimingFunction *)translateToTimingFunction:(NSNumber *)timingFunction { + switch (timingFunction.integerValue) { case 1: - return kCAFillModeForwards; + return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; case 2: - return kCAFillModeBackwards; + return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; case 3: - return kCAFillModeBoth; + return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; + case 4: + return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; default: - return kCAFillModeRemoved; + return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]; } } diff --git a/js-framework/src/ui/animation.ts b/js-framework/src/ui/animation.ts index beb535c1..37dc79d2 100644 --- a/js-framework/src/ui/animation.ts +++ b/js-framework/src/ui/animation.ts @@ -55,6 +55,29 @@ export enum FillMode { Both = 0x3, } +export enum TimingFunction { + /** + * The system default timing function. Use this function to ensure that the timing of your animations matches that of most system animations. + */ + Default = 0, + /** + * Linear pacing, which causes an animation to occur evenly over its duration. + */ + Linear, + /** + * Ease-in pacing, which causes an animation to begin slowly and then speed up as it progresses. + */ + EaseIn, + /** + * Ease-out pacing, which causes an animation to begin quickly and then slow as it progresses. + */ + EaseOut, + /** + * Ease-in-ease-out pacing, which causes an animation to begin slowly, accelerate through the middle of its duration, and then slow again before completing. + */ + EaseInEaseOut, +} + abstract class Animation implements IAnimation { changeables: Map = new Map duration = 0 @@ -62,6 +85,7 @@ abstract class Animation implements IAnimation { repeatMode?: RepeatMode delay?: number fillMode = FillMode.Forward + timingFunction?: TimingFunction toModel() { const changeables = [] for (let e of this.changeables.values()) { @@ -79,6 +103,7 @@ abstract class Animation implements IAnimation { repeatCount: this.repeatCount, repeatMode: this.repeatMode, fillMode: this.fillMode, + timingFunction: this.timingFunction } } } diff --git a/js-framework/src/ui/view.ts b/js-framework/src/ui/view.ts index 29052d80..b524773a 100644 --- a/js-framework/src/ui/view.ts +++ b/js-framework/src/ui/view.ts @@ -297,21 +297,6 @@ export abstract class View implements Modeling, IView { return this.nativeChannel(context, 'getHeight')() as Promise } - /** - * - * @param rotation [0..1] - */ - setRotation(context: BridgeContext, rotation: number) { - return this.nativeChannel(context, 'setRotation')(rotation) - } - /** - * - * @return rotation [0..1] - */ - getRotation(context: BridgeContext) { - return this.nativeChannel(context, 'getRotation')() as Promise - } - /**++++++++++transform++++++++++*/ @Property translationX?: number