From 1cd84d8720c7de461df7a6ed670120e1a2449096 Mon Sep 17 00:00:00 2001 From: "pengfei.zhou" Date: Mon, 2 Dec 2019 18:07:03 +0800 Subject: [PATCH] feat:iOS Affects Transformation --- demo/index.ts | 1 + demo/src/AnimatorDemo.ts | 34 ----- demo/src/ComplicatedAnimations.ts | 110 ++++++++++++++ iOS/Pod/Classes/Shader/DoricViewNode.m | 199 ++++++++++++++++++++++--- js-framework/src/ui/animation.ts | 3 - js-framework/src/ui/view.ts | 7 +- 6 files changed, 298 insertions(+), 56 deletions(-) create mode 100644 demo/src/ComplicatedAnimations.ts diff --git a/demo/index.ts b/demo/index.ts index cabb9c45..e9c37c1c 100644 --- a/demo/index.ts +++ b/demo/index.ts @@ -16,4 +16,5 @@ export default [ 'src/FlowLayoutDemo', 'src/PopoverDemo', 'src/AnimatorDemo', + 'src/ComplicatedAnimations', ] \ No newline at end of file diff --git a/demo/src/AnimatorDemo.ts b/demo/src/AnimatorDemo.ts index 12ac6896..42a19ecc 100644 --- a/demo/src/AnimatorDemo.ts +++ b/demo/src/AnimatorDemo.ts @@ -167,40 +167,6 @@ class AnimatorDemo extends Panel { }); } }), - thisLabel('animationSet').apply({ - onClick: () => { - const animationSet = new AnimationSet - //animationSet.fillMode = FillMode.Removed - const translate = new TranslationAnimation - translate.fromTranslationX = 100 - translate.toTranslationX = 200 - translate.fromTranslationY = 10 - translate.toTranslationY = 200 - translate.duration = 2000 - translate.delay = 1000 - translate.fillMode = FillMode.Forward - const scale = new ScaleAnimation - scale.fromScaleX = 1 - scale.toScaleX = 5 - scale.fromScaleY = 1 - scale.toScaleY = 5 - //scale.delay = 1000 - scale.duration = 2000 - scale.fillMode = FillMode.Backward - const rotation = new RotationAnimation - rotation.fromRotation = 0 - rotation.toRotation = 6.3 - rotation.duration = 2000 - rotation.fillMode = FillMode.Removed - animationSet.addAnimation(translate) - animationSet.addAnimation(scale) - animationSet.addAnimation(rotation) - - view.doAnimation(context, animationSet).then(() => { - modal(context).toast('Animation finished') - }) - } - }), ]).apply({ space: 10 } as IHLayout), ] ).apply({ space: 10 } as IVLayout), diff --git a/demo/src/ComplicatedAnimations.ts b/demo/src/ComplicatedAnimations.ts new file mode 100644 index 00000000..8f4d0e26 --- /dev/null +++ b/demo/src/ComplicatedAnimations.ts @@ -0,0 +1,110 @@ +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 { title, colors, box } from "./utils"; + +function thisLabel(str: string) { + return text({ + text: str, + width: 80, + height: 30, + bgColor: colors[0], + textSize: 10, + textColor: Color.WHITE, + layoutConfig: layoutConfig().exactly(), + }) +} + +@Entry +class AnimationDemo extends Panel { + build(rootView: Group): void { + const view = box(2) + view.onClick = () => { + modal(context).toast('Clicked') + } + vlayout([ + title("Complicated Animation"), + vlayout( + [ + hlayout([ + thisLabel('TranslationX').apply({ + onClick: () => { + const animation = new TranslationAnimation + animation.duration = 1000 + animation.fromTranslationX = view.translationX || 0 + animation.toTranslationX = animation.fromTranslationX + 100 + animation.fromTranslationY = view.translationY || 0 + animation.toTranslationY = view.translationY || 0 + view.doAnimation(context, animation) + } + }), + thisLabel('TranslationY').apply({ + onClick: () => { + const animation = new TranslationAnimation + animation.duration = 1000 + animation.fromTranslationX = view.translationX || 0 + animation.toTranslationX = view.translationX || 0 + animation.fromTranslationY = view.translationY || 0 + animation.toTranslationY = animation.fromTranslationY + 100 + view.doAnimation(context, animation) + } + }), + thisLabel('ScaleX').apply({ + onClick: () => { + const animation = new ScaleAnimation + animation.duration = 1000 + animation.fromScaleX = 0 + animation.toScaleX = 5 + view.doAnimation(context, animation) + } + }), + thisLabel('ScaleY').apply({ + onClick: () => { + const animation = new ScaleAnimation + animation.duration = 1000 + animation.fromScaleY = 0 + animation.toScaleY = 5 + view.doAnimation(context, animation) + } + }), + thisLabel('rotation').apply({ + onClick: () => { + const animation = new RotationAnimation + animation.duration = 1000 + animation.fromRotation = 0 + animation.toRotation = 4 + view.doAnimation(context, animation) + } + }), + ]).apply({ space: 10 } as IHLayout), + hlayout([ + thisLabel('group').apply({ + onClick: () => { + const rotation = new RotationAnimation + rotation.duration = 1000 + rotation.fromRotation = 0 + rotation.toRotation = 4 + const translation = new TranslationAnimation + translation.duration = 1000 + translation.fromTranslationX = view.translationX || 0 + translation.toTranslationX = 100 + const animationSet = new AnimationSet + animationSet.addAnimation(rotation) + animationSet.addAnimation(translation) + view.doAnimation(context, animationSet) + } + }), + ]).apply({ space: 10 } as IHLayout), + ] + ).apply({ space: 10 } as IVLayout), + stack([ + view, + ]).apply({ + layoutConfig: layoutConfig().atmost(), + bgColor: colors[1].alpha(0.3 * 255), + }), + ]).apply({ + layoutConfig: layoutConfig().atmost(), + gravity: gravity().center(), + space: 10, + } as IVLayout).in(rootView) + } +} \ No newline at end of file diff --git a/iOS/Pod/Classes/Shader/DoricViewNode.m b/iOS/Pod/Classes/Shader/DoricViewNode.m index ce23909e..61da0980 100644 --- a/iOS/Pod/Classes/Shader/DoricViewNode.m +++ b/iOS/Pod/Classes/Shader/DoricViewNode.m @@ -67,13 +67,29 @@ CGPathRef DoricCreateRoundedRectPath(CGRect bounds, } @interface AnimationCallback : NSObject -@property(nonatomic, strong) void (^endBlock)(); +@property(nonatomic, strong) NSMutableDictionary *dictionary; +@property(nonatomic, strong) void (^startBlock)(AnimationCallback *callback); + +@property(nonatomic, strong) void (^endBlock)(AnimationCallback *callback); @end @implementation AnimationCallback +- (instancetype)init { + if (self = [super init]) { + _dictionary = [NSMutableDictionary new]; + } + return self; +} + +- (void)animationDidStart:(CAAnimation *)anim { + if (self.startBlock) { + self.startBlock(self); + } +} + - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { if (self.endBlock) { - self.endBlock(); + self.endBlock(self); } } @end @@ -321,11 +337,42 @@ - (void)blendLayoutConfig:(NSDictionary *)params { } } +- (NSDictionary *)transformation { + NSMutableDictionary *dictionary = [NSMutableDictionary new]; + if (self.translationX) { + dictionary[@"translationX"] = self.translationX; + } + if (self.translationY) { + dictionary[@"translationY"] = self.translationY; + } + if (self.scaleX) { + dictionary[@"scaleX"] = self.scaleX; + } + if (self.scaleY) { + dictionary[@"scaleY"] = self.scaleY; + } + if (self.rotation) { + dictionary[@"rotation"] = self.rotation; + } + return dictionary; +} + - (void)doAnimation:(id)params withPromise:(DoricPromise *)promise { CAAnimation *animation = [self parseAnimation:params]; + AnimationCallback *originDelegate = animation.delegate; AnimationCallback *animationCallback = [[AnimationCallback new] also:^(AnimationCallback *it) { - it.endBlock = ^{ - [promise resolve:nil]; + it.startBlock = ^(AnimationCallback *callback) { + if (originDelegate) { + originDelegate.startBlock(callback); + } + [self transformProperties]; + }; + it.endBlock = ^(AnimationCallback *callback) { + if (originDelegate) { + originDelegate.endBlock(callback); + } + [self transformProperties]; + [promise resolve:self.transformation]; }; }]; animation.delegate = animationCallback; @@ -340,9 +387,7 @@ - (CFTimeInterval)computeDurationOfAnimations:(NSArray *)animatio [animations forEach:^(CAAnimation *obj) { interval = MAX(interval, obj.beginTime + obj.duration * (1 + obj.repeatCount)); }]; - /// Here add 0.00001 to force animationGroup's last child animation affects fill mode. - /// Otherwise the child's fill mode will be overwritten by parent. - return interval + 0.00001; + return interval; } - (CAAnimation *)parseAnimation:(id)params { @@ -354,9 +399,27 @@ - (CAAnimation *)parseAnimation:(id)params { [animations addObject:[self parseAnimation:obj]]; }]; animationGroup.duration = [self computeDurationOfAnimations:animations]; - animationGroup.fillMode = [self translateToFillMode:params[@"fillMode"]]; - animationGroup.removedOnCompletion = [animationGroup.fillMode isEqualToString:kCAFillModeRemoved]; animationGroup.animations = animations; + animationGroup.delegate = [[AnimationCallback new] also:^(AnimationCallback *it) { + it.startBlock = ^(AnimationCallback *callback) { + [[animations map:^id(CABasicAnimation *obj) { + return obj.delegate; + }] forEach:^(AnimationCallback *obj) { + if (obj.startBlock) { + obj.startBlock(obj); + } + }]; + }; + it.endBlock = ^(AnimationCallback *callback) { + [[animations map:^id(CABasicAnimation *obj) { + return obj.delegate; + }] forEach:^(AnimationCallback *obj) { + if (obj.endBlock) { + obj.endBlock(obj); + } + }]; + }; + }]; if (params[@"delay"]) { animationGroup.beginTime = [params[@"delay"] floatValue] / 1000; } @@ -371,12 +434,23 @@ - (CAAnimation *)parseAnimation:(id)params { [changeables forEach:^(NSDictionary *obj) { NSString *key = obj[@"key"]; if ([@"translationX" isEqualToString:key]) { - from.x += [obj[@"fromValue"] floatValue]; - to.x += [obj[@"toValue"] floatValue]; + from.x += [obj[@"fromValue"] floatValue] - self.translationX.floatValue; + to.x += [obj[@"toValue"] floatValue] - self.translationX.floatValue; + [self setFillMode:animation + key:key + startValue:obj[@"fromValue"] + endValue:obj[@"toValue"] + fillMode:params[@"fillMode"]]; } else if ([@"translationY" isEqualToString:key]) { - from.y += [obj[@"fromValue"] floatValue]; - to.y += [obj[@"toValue"] floatValue]; + from.y += [obj[@"fromValue"] floatValue] - self.translationY.floatValue; + to.y += [obj[@"toValue"] floatValue] - self.translationY.floatValue; + [self setFillMode:animation + key:key + startValue:obj[@"fromValue"] + endValue:obj[@"toValue"] + fillMode:params[@"fillMode"]]; } + }]; animation.fromValue = [NSValue valueWithCGPoint:from]; animation.toValue = [NSValue valueWithCGPoint:to]; @@ -384,11 +458,33 @@ - (CAAnimation *)parseAnimation:(id)params { return animation; } else { CAAnimationGroup *animationGroup = [CAAnimationGroup animation]; - NSMutableArray *animations = [NSMutableArray new]; + NSMutableArray *animations = [NSMutableArray new]; + [changeables forEach:^(NSDictionary *obj) { - [animations addObject:[self parseChangeable:obj]]; + CABasicAnimation *animation = [self parseChangeable:obj fillMode:params[@"fillMode"]]; + [animations addObject:animation]; }]; animationGroup.animations = animations; + animationGroup.delegate = [[AnimationCallback new] also:^(AnimationCallback *it) { + it.startBlock = ^(AnimationCallback *callback) { + [[animations map:^id(CABasicAnimation *obj) { + return obj.delegate; + }] forEach:^(AnimationCallback *obj) { + if (obj.startBlock) { + obj.startBlock(obj); + } + }]; + }; + it.endBlock = ^(AnimationCallback *callback) { + [[animations map:^id(CABasicAnimation *obj) { + return obj.delegate; + }] forEach:^(AnimationCallback *obj) { + if (obj.endBlock) { + obj.endBlock(obj); + } + }]; + }; + }]; [self setAnimation:animationGroup params:params]; return animationGroup; } @@ -413,11 +509,73 @@ - (void)setAnimation:(CAAnimation *)animation params:(NSDictionary *)params { animation.beginTime = [params[@"delay"] floatValue] / 1000; } animation.duration = [params[@"duration"] floatValue] / 1000; - animation.fillMode = [self translateToFillMode:params[@"fillMode"]]; - animation.removedOnCompletion = [animation.fillMode isEqualToString:kCAFillModeRemoved]; } -- (CAAnimation *)parseChangeable:(NSDictionary *)params { +- (void)setFillMode:(CAAnimation *)animation + key:(NSString *)key + startValue:(NSNumber *)startValue + endValue:(NSNumber *)endValue + fillMode:(NSNumber *)fillMode { + NSUInteger fillModeInt = fillMode.unsignedIntegerValue; + if ((fillModeInt & 2) == 2) { + [self setAnimatedValue:key value:startValue]; + } + AnimationCallback *callback = [AnimationCallback new]; + AnimationCallback *originCallback = animation.delegate; + __weak typeof(self) _self = self; + callback.startBlock = ^(AnimationCallback *callback) { + __strong typeof(_self) self = _self; + callback.dictionary[key] = [self getAnimatedValue:key]; + if (originCallback) { + originCallback.startBlock(callback); + } + }; + callback.endBlock = ^(AnimationCallback *callback) { + __strong typeof(_self) self = _self; + if ((fillModeInt & 1) == 1) { + [self setAnimatedValue:key value:endValue]; + } + if (originCallback) { + originCallback.endBlock(callback); + } + }; + animation.delegate = callback; +} + +- (NSNumber *)getAnimatedValue:(NSString *)key { + if ([@"translationX" isEqualToString:key]) { + return self.translationX; + } + if ([@"translationY" isEqualToString:key]) { + return self.translationY; + } + if ([@"scaleX" isEqualToString:key]) { + return self.scaleX; + } + if ([@"scaleY" isEqualToString:key]) { + return self.scaleY; + } + if ([@"rotation" isEqualToString:key]) { + return self.rotation; + } + return nil; +} + +- (void)setAnimatedValue:(NSString *)key value:(NSNumber *)value { + if ([@"translationX" isEqualToString:key]) { + self.translationX = value; + } else if ([@"translationY" isEqualToString:key]) { + self.translationY = value; + } else if ([@"scaleX" isEqualToString:key]) { + self.scaleX = value; + } else if ([@"scaleY" isEqualToString:key]) { + self.scaleY = value; + } else if ([@"rotation" isEqualToString:key]) { + self.rotation = value; + } +} + +- (CABasicAnimation *)parseChangeable:(NSDictionary *)params fillMode:(NSNumber *)fillMode { NSString *key = params[@"key"]; CABasicAnimation *animation = [CABasicAnimation animation]; if ([@"scaleX" isEqualToString:key]) { @@ -437,6 +595,11 @@ - (CAAnimation *)parseChangeable:(NSDictionary *)params { animation.fromValue = params[@"fromValue"]; animation.toValue = params[@"toValue"]; } + [self setFillMode:animation + key:key + startValue:params[@"fromValue"] + endValue:params[@"toValue"] + fillMode:fillMode]; return animation; } diff --git a/js-framework/src/ui/animation.ts b/js-framework/src/ui/animation.ts index de8fad0b..beb535c1 100644 --- a/js-framework/src/ui/animation.ts +++ b/js-framework/src/ui/animation.ts @@ -27,7 +27,6 @@ export enum RepeatMode { export interface IAnimation extends Modeling { duration: number delay?: number - fillMode: FillMode } export interface Changeable { @@ -215,7 +214,6 @@ export class AnimationSet implements IAnimation { private animations: IAnimation[] = [] _duration = 0 delay?: number - fillMode = FillMode.Removed addAnimation(anim: IAnimation) { this.animations.push(anim) } @@ -234,7 +232,6 @@ export class AnimationSet implements IAnimation { animations: this.animations.map(e => { return e.toModel() }) as Model, - fillMode: this.fillMode, delay: this.delay, } } diff --git a/js-framework/src/ui/view.ts b/js-framework/src/ui/view.ts index ee300022..b8a0a7a6 100644 --- a/js-framework/src/ui/view.ts +++ b/js-framework/src/ui/view.ts @@ -336,7 +336,12 @@ export abstract class View implements Modeling, IView { /**----------transform----------*/ doAnimation(context: BridgeContext, animation: IAnimation) { - return this.nativeChannel(context, "doAnimation")(animation.toModel()) + return this.nativeChannel(context, "doAnimation")(animation.toModel()).then((args) => { + for (let key in args) { + Reflect.set(this, key, Reflect.get(args, key, args), this) + Reflect.deleteProperty(this.__dirty_props__, key) + } + }) } }