Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ios): add layout animation for animation module #1816

Merged
merged 10 commits into from
May 9, 2022
157 changes: 94 additions & 63 deletions framework/js/examples/ios-demo/HippyDemo.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions framework/js/ios/base/HippyBatchedBridge.mm
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,10 @@ - (void)setUpDevClientWithName:(NSString *)name {
}
}

- (std::shared_ptr<hippy::AnimationManager>)animationManager {
return self.parentBridge.animationManager;
}

- (void)registerModuleForFrameUpdates:(id<HippyBridgeModule>)module withModuleData:(HippyModuleData *)moduleData {
[_displayLink registerModuleForFrameUpdates:module withModuleData:moduleData];
}
Expand Down
2 changes: 2 additions & 0 deletions framework/js/ios/base/HippyBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#import "dom/dom_manager.h"
#import "NativeRenderManager.h"
#import "HippyMethodInterceptorProtocol.h"
#import "dom/animation_manager.h"

@class JSValue;
@class HippyBridge;
Expand Down Expand Up @@ -219,6 +220,7 @@ HIPPY_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass);

@property (nonatomic, readonly) std::shared_ptr<hippy::DomManager> domManager;
@property (nonatomic, readonly) std::shared_ptr<NativeRenderManager> renderManager;
@property (nonatomic, readonly) std::shared_ptr<hippy::AnimationManager> animationManager;

/**
* The launch options that were used to initialize the bridge.
Expand Down
3 changes: 3 additions & 0 deletions framework/js/ios/base/HippyBridge.mm
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,9 @@ - (void)setUpWithRootTag:(NSNumber *)tag rootSize:(CGSize)size

[strongSelf setUpDomManager:strongSelf->_domManager];

strongSelf->_animationManager = std::make_shared<hippy::AnimationManager>(strongSelf->_domManager);
strongSelf->_domManager->AddInterceptor(strongSelf->_animationManager);

strongSelf.renderContext = strongSelf->_renderManager->GetRenderContext();
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import "dom/animation_manager.h"

@class HippyAnimation;

Expand Down Expand Up @@ -60,6 +61,7 @@ typedef NS_ENUM(NSInteger, HippyAnimationState) {
@property (nonatomic, assign, readonly) HippyAnimationDirection directionType;
@property (nonatomic, copy) NSNumber *parentAnimationId;
@property (nonatomic, assign) HippyAnimationState state;
@property (nonatomic, assign) std::weak_ptr<hippy::AnimationManager> animationManager;

- (void)updateAnimation:(NSDictionary *)config;

Expand Down
5 changes: 5 additions & 0 deletions framework/js/ios/module/animation/HippyAnimationModule.mm
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ - (void)invalidate {
- (HippyAnimator *)animator {
if (!_animator) {
_animator = [self.bridge.renderContext animator];
_animator.bridge = self.bridge;
_animator.animationTimingDelegate = self;
}
return _animator;
Expand Down Expand Up @@ -103,4 +104,8 @@ - (void)animationDidStop:(HippyAnimator *)animator animationId:(NSNumber *)anima
[self.bridge.eventDispatcher dispatchEvent:@"EventDispatcher" methodName:@"receiveNativeEvent"
args:@{ @"eventName": @"onAnimationEnd", @"extra": animationId }];
}

- (BOOL)animationShouldUseCustomerTimingFunction:(HippyAnimator *)animator {
return YES;
}
@end
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ - (void)traversalProps:(id)props key:(NSString *)key value:(id)value {
}

- (NSDictionary *)animationIdWithPropDictionary {
return _animationIdWithPropDictionary;
return [_animationIdWithPropDictionary copy];
}

- (NSNumber *)hippyTag {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#import <Foundation/Foundation.h>

@class HippyAnimation;
@class HippyBridge;
@protocol HippyRenderContext;

@interface HippyAnimationIdCount : NSObject
Expand All @@ -35,16 +36,20 @@

@protocol HippyAnimationTimingProtocol <NSObject>

- (BOOL)animationShouldUseCustomerTimingFunction:(HippyAnimator *)animator;

- (void)animationDidStart:(HippyAnimator *)animator animationId:(NSNumber *)animationId;
- (void)animationDidStop:(HippyAnimator *)animator animationId:(NSNumber *)animationId finished:(BOOL)finished;

@end

//TODO HippyAnimator相关的代码逻辑应该移动到hippy,而不是render
@interface HippyAnimator : NSObject

- (instancetype)initWithRenderContext:(id<HippyRenderContext>)renderContext;

@property(nonatomic, weak) id<HippyAnimationTimingProtocol> animationTimingDelegate;
@property(nonatomic, weak) HippyBridge *bridge;

- (void)createAnimation:(NSNumber *)animationId mode:(NSString *)mode params:(NSDictionary *)params;
- (void)createAnimationSet:(NSNumber *)animationId animations:(NSDictionary *)animations;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
#import "CALayer+HippyAnimation.h"
#import "UIView+Hippy.h"
#import "HippyRenderContext.h"
#import "TimingAnimation.h"
#import "HippyUIManager.h"
#import "HippyBridge.h"

@implementation HippyAnimationIdCount {
NSMutableDictionary *_animationIdDic;
Expand Down Expand Up @@ -65,25 +68,29 @@ - (NSUInteger)countForAnimationId:(NSNumber *)animationId {
}
@end

@interface HippyAnimator () <CAAnimationDelegate>
@property(nonatomic, weak) id<HippyRenderContext> renderContext;
@end

@implementation HippyAnimator {
@interface HippyAnimator () <CAAnimationDelegate, TimingAnimationDelegate> {
NSMutableDictionary<NSNumber *, HippyAnimation *> *_animationById;
NSMutableDictionary<NSNumber *, NSMutableArray<HippyAnimationViewParams *> *> *_paramsByAnimationId;
NSMutableDictionary<NSNumber *, HippyAnimationViewParams *> *_paramsByHippyTag;
NSMapTable<NSNumber *, TimingAnimation *> *_timingAnimationMap;
HippyAnimationIdCount *_virtualAnimations;
std::mutex _mutex;
}

@property(nonatomic, weak) id<HippyRenderContext> renderContext;

@end

@implementation HippyAnimator

- (instancetype)initWithRenderContext:(id<HippyRenderContext>)renderContext {
if (self = [super init]) {
_animationById = [NSMutableDictionary new];
_paramsByHippyTag = [NSMutableDictionary new];
_paramsByAnimationId = [NSMutableDictionary new];
_renderContext = renderContext;
_virtualAnimations = [[HippyAnimationIdCount alloc] init];
_timingAnimationMap = [NSMapTable strongToWeakObjectsMapTable];
}
return self;
}
Expand Down Expand Up @@ -156,24 +163,36 @@ - (void)startAnimation:(NSNumber *)animationId {

- (void)pauseAnimation:(NSNumber *)animationId {
std::lock_guard<std::mutex> lock(_mutex);
NSArray <HippyAnimationViewParams *> *params = [_paramsByAnimationId[animationId] copy];
[self.renderContext addUIBlock:^(id<HippyRenderContext> renderContext, NSDictionary<NSNumber *,__kindof UIView *> *viewRegistry) {
[params enumerateObjectsUsingBlock:^(HippyAnimationViewParams * _Nonnull param, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
UIView *view = [renderContext viewFromRenderViewTag:param.hippyTag];
[view.layer pauseLayerAnimation];
TimingAnimation *tAni = [_timingAnimationMap objectForKey:animationId];
if (tAni) {
[tAni performSelectorOnMainThread:@selector(pauseAnimating) withObject:nil waitUntilDone:NO];
}
else {
NSArray <HippyAnimationViewParams *> *params = [_paramsByAnimationId[animationId] copy];
[self.renderContext addUIBlock:^(id<HippyRenderContext> renderContext, NSDictionary<NSNumber *,__kindof UIView *> *viewRegistry) {
[params enumerateObjectsUsingBlock:^(HippyAnimationViewParams * _Nonnull param, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
UIView *view = [renderContext viewFromRenderViewTag:param.hippyTag];
[view.layer pauseLayerAnimation];
}];
}];
}];
}
}

- (void)resumeAnimation:(NSNumber *)animationId {
std::lock_guard<std::mutex> lock(_mutex);
NSArray <HippyAnimationViewParams *> *params = [_paramsByAnimationId[animationId] copy];
[self.renderContext addUIBlock:^(id<HippyRenderContext> renderContext, NSDictionary<NSNumber *,__kindof UIView *> *viewRegistry) {
[params enumerateObjectsUsingBlock:^(HippyAnimationViewParams * _Nonnull param, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
UIView *view = [renderContext viewFromRenderViewTag:param.hippyTag];
[view.layer resumeLayerAnimation];
TimingAnimation *tAni = [_timingAnimationMap objectForKey:animationId];
if (tAni) {
[tAni performSelectorOnMainThread:@selector(resumeAnimating) withObject:nil waitUntilDone:NO];
}
else {
NSArray <HippyAnimationViewParams *> *params = [_paramsByAnimationId[animationId] copy];
[self.renderContext addUIBlock:^(id<HippyRenderContext> renderContext, NSDictionary<NSNumber *,__kindof UIView *> *viewRegistry) {
[params enumerateObjectsUsingBlock:^(HippyAnimationViewParams * _Nonnull param, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
UIView *view = [renderContext viewFromRenderViewTag:param.hippyTag];
[view.layer resumeLayerAnimation];
}];
}];
}];
}
}

- (void)paramForAnimationId:(NSNumber *)animationId {
Expand Down Expand Up @@ -256,13 +275,20 @@ - (void)updateAnimation:(NSNumber *__nonnull)animationId params:(NSDictionary *)
- (void)destroyAnimation:(NSNumber * __nonnull)animationId {
std::lock_guard<std::mutex> lock(_mutex);
[_animationById removeObjectForKey: animationId];
NSMutableArray <HippyAnimationViewParams *> *params = _paramsByAnimationId[animationId];
[self.renderContext addUIBlock:^(id<HippyRenderContext> renderContext, NSDictionary<NSNumber *,__kindof UIView *> *viewRegistry) {
[params enumerateObjectsUsingBlock:^(HippyAnimationViewParams * _Nonnull param, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
UIView *view = [renderContext viewFromRenderViewTag:param.hippyTag];
[view.layer removeAnimationForKey: [NSString stringWithFormat: @"%@", animationId]];
TimingAnimation *tAni = [_timingAnimationMap objectForKey:animationId];
if (tAni) {
[tAni removeAnimating];
[_timingAnimationMap removeObjectForKey:animationId];
}
else {
NSMutableArray <HippyAnimationViewParams *> *params = _paramsByAnimationId[animationId];
[self.renderContext addUIBlock:^(id<HippyRenderContext> renderContext, NSDictionary<NSNumber *,__kindof UIView *> *viewRegistry) {
[params enumerateObjectsUsingBlock:^(HippyAnimationViewParams * _Nonnull param, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
UIView *view = [renderContext viewFromRenderViewTag:param.hippyTag];
[view.layer removeAnimationForKey: [NSString stringWithFormat: @"%@", animationId]];
}];
}];
}];
}
[_paramsByAnimationId removeObjectForKey: animationId];
}

Expand Down Expand Up @@ -325,14 +351,31 @@ - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
}
}
}

- (void)timingAnimationDidStart:(TimingAnimation *)animation {
if ([self.animationTimingDelegate respondsToSelector:@selector(animationDidStart:animationId:)]) {
[self.animationTimingDelegate animationDidStart:self animationId:animation.animationId];
}
}

- (void)timingAnimationDidStop:(TimingAnimation *)animation {
if ([self.animationTimingDelegate respondsToSelector:@selector(animationDidStop:animationId:finished:)]) {
[self.animationTimingDelegate animationDidStop:self animationId:animation.animationId finished:YES];
}
}

#pragma mark -
- (NSDictionary *)bindAnimaiton:(NSDictionary *)params viewTag:(NSNumber *)viewTag rootTag:(NSNumber *)rootTag {
std::lock_guard<std::mutex> lock(_mutex);
HippyAnimationViewParams *p = [[HippyAnimationViewParams alloc] initWithParams:params animator:self viewTag:viewTag rootTag:rootTag];
[p parse];
NSDictionary<NSString *, NSNumber *> *animationIdWithPropDictionary = [p animationIdWithPropDictionary];
if (0 == [animationIdWithPropDictionary count]) {
return nil;
}

BOOL contain = [self alreadyConnectAnimation:p];
[p.animationIdWithPropDictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *animationId, __unused BOOL *stop) {
[animationIdWithPropDictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *animationId, __unused BOOL *stop) {
HippyAnimation *ani = self->_animationById[animationId];

if (ani.state == HippyAnimationFinishState) {
Expand Down Expand Up @@ -366,25 +409,41 @@ - (void)connectAnimationToView:(UIView *)view {
std::lock_guard<std::mutex> lock(_mutex);
NSNumber *hippyTag = view.hippyTag;
HippyAnimationViewParams *p = _paramsByHippyTag[hippyTag];

NSMutableArray<CAAnimation *> *animations = [NSMutableArray new];
[p.animationIdWithPropDictionary enumerateKeysAndObjectsUsingBlock:^(NSString *prop, NSNumber *animationId, __unused BOOL *stop) {
NSDictionary *animationIdWithPropDictionary = p.animationIdWithPropDictionary;
[animationIdWithPropDictionary enumerateKeysAndObjectsUsingBlock:^(NSString *prop, NSNumber *animationId, __unused BOOL *stop) {
HippyAnimation *animation = self->_animationById[animationId];
if (animation.state != HippyAnimationReadyState) {
return;
}
CAAnimation *ani = [animation animationOfView:view forProp:prop];
animation.state = HippyAnimationStartedState;
[ani setValue:animationId forKey:@"animationID"];
if (animation.parentAnimationId) {
[ani setValue:animation.parentAnimationId forKey:@"animationParentID"];
BOOL useCustomerTimingFunction = NO;
if ([self.animationTimingDelegate respondsToSelector:@selector(animationShouldUseCustomerTimingFunction:)]) {
useCustomerTimingFunction = [self.animationTimingDelegate animationShouldUseCustomerTimingFunction:self];
}
useCustomerTimingFunction &= [TimingAnimation canHandleAnimationForProperty:prop];
if (useCustomerTimingFunction) {
//TODO implemente customer animation timing function
TimingAnimation *tAnimation =
[[TimingAnimation alloc] initWithKeyPath:prop timingFunction:animation.timingFunction
domManager:self.bridge.animationManager viewTag:[[view hippyTag] intValue]];
tAnimation.delegate = self;
tAnimation.hpAni = animation;
tAnimation.duration = animation.duration;
tAnimation.animationId = animationId;
[view addTimingAnimation:tAnimation];
[_timingAnimationMap setObject:tAnimation forKey:animationId];
}
else {
CAAnimation *ani = [animation animationOfView:view forProp:prop];
animation.state = HippyAnimationStartedState;
[ani setValue:animationId forKey:@"animationID"];
if (animation.parentAnimationId) {
[ani setValue:animation.parentAnimationId forKey:@"animationParentID"];
}
[ani setValue:view.hippyTag forKey:@"viewID"];
ani.delegate = self;
[animations addObject:ani];
}
[ani setValue:view.hippyTag forKey:@"viewID"];
ani.delegate = self;
[animations addObject:ani];
// HippyLogInfo(@"[Hippy_OC_Log][Animation],Connect_Animation:[%@] to view [%@] prop [%@] from [%@] to [%@]", animationId, view.hippyTag, prop, @(animation.startValue),
// @(animation.endValue));

}];
[animations enumerateObjectsUsingBlock:^(CAAnimation *_Nonnull ani, __unused NSUInteger idx, __unused BOOL *stop) {
NSNumber *animationId = [ani valueForKey:@"animationID"];
Expand Down
60 changes: 60 additions & 0 deletions framework/js/ios/module/animation/RSTimingFunction.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// RSTimingFunction.h
//
// Created by Raphael Schaad on 2013-09-28.
// This is free and unencumbered software released into the public domain.
//

//A open source code from https://gist.github.com/raphaelschaad/6739676

#import <UIKit/UIKit.h>


// Common predefined timing functions
extern NSString * const kRSTimingFunctionLinear; // controlPoint1=(0, 0), controlPoint2=(1, 1)
extern NSString * const kRSTimingFunctionEaseIn; // controlPoint1=(0.42, 0), controlPoint2=(1, 1)
extern NSString * const kRSTimingFunctionEaseOut; // controlPoint1=(0, 0), controlPoint2=(0.58, 1)
extern NSString * const kRSTimingFunctionEaseInEaseOut; // controlPoint1=(0.42, 0), controlPoint2=(0.58, 1)
extern NSString * const kRSTimingFunctionDefault; // controlPoint1=(0.25, 0.1), controlPoint2=(0.25, 1)


//
// Like `CAMediaTimingFunction` but for versatile (animation) use: allows you to get the value for any given 'x' with `-valueForX:`.
// Any timing function maps an input value normalized to the interval [0..1] on the curve to an output value also in the interval [0..1].
// The implementation math is based on WebCore (WebKit), which is presumably what CoreAnimation is using under the hood too.
//
@interface RSTimingFunction : NSObject <NSCoding>

// Convenience methods to create a common timing function listed above
- (instancetype)initWithName:(NSString *)name;
+ (instancetype)timingFunctionWithName:(NSString *)name;

// Creates a timing function modelled on a cubic Bezier curve.
// The end points of the curve are at (0,0) and (1,1) and the two points defined by the class instance are its control points. Thus the points defining the Bezier curve are: '[(0,0), controlPoint1, controlPoint2, (1,1)]'
// Example: `RSTimingFunction *heavyEaseInTimingFunction = [RSTimingFunction timingFunctionWithControlPoint1:CGPointMake(0.8, 0.0) controlPoint2:CGPointMake(1.0, 1.0)];`
// [y]^ .---controlPoint2=(1,1)
// | |
// | .
// | ,
// | .
// |___.--'
// +-------.--->
// | [x]
// controlPoint1=(0.8,0)
// To visualize what curves given points will produce, use this great tool: http://netcetera.org/camtf-playground.html
- (instancetype)initWithControlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;
+ (instancetype)timingFunctionWithControlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;

// This is the meat and potatoes: returns `y` for a given `x` value.
- (CGFloat)valueForX:(CGFloat)x;

// If control points are changed after creation, returned values will reflect the changed curve immediately.
// It's more performant to use multiple timing functions with set control points instead of reusing one and changing its control points over and over.
@property (nonatomic, assign) CGPoint controlPoint1;
@property (nonatomic, assign) CGPoint controlPoint2;

// Optionally hint the duration to improve the precision of the values, e.g. when used for an animation.
// Shorter duration is more performant. Default is 1 second.
@property (nonatomic, assign) NSTimeInterval duration;

@end
Loading