feat:iOS Affects Transformation
This commit is contained in:
		| @@ -16,4 +16,5 @@ export default [ | ||||
|     'src/FlowLayoutDemo', | ||||
|     'src/PopoverDemo', | ||||
|     'src/AnimatorDemo', | ||||
|     'src/ComplicatedAnimations', | ||||
| ] | ||||
| @@ -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), | ||||
|   | ||||
							
								
								
									
										110
									
								
								demo/src/ComplicatedAnimations.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								demo/src/ComplicatedAnimations.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||
|     } | ||||
| } | ||||
| @@ -67,13 +67,29 @@ CGPathRef DoricCreateRoundedRectPath(CGRect bounds, | ||||
| } | ||||
| 
 | ||||
| @interface AnimationCallback : NSObject <CAAnimationDelegate> | ||||
| @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<CAAnimation *> *)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 <CABasicAnimation *> *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; | ||||
| } | ||||
| 
 | ||||
|   | ||||
| @@ -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, | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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) | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user