mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-24 04:16:00 +08:00
[React Native] Add preliminary animation API
This commit is contained in:
@@ -4,8 +4,8 @@
|
||||
|
||||
#define RCTErrorDomain @"RCTErrorDomain"
|
||||
|
||||
#define RCTAssert(condition, message, ...) _RCTAssert(condition, message, ##__VA_ARGS__)
|
||||
#define RCTCAssert(condition, message, ...) _RCTCAssert(condition, message, ##__VA_ARGS__)
|
||||
#define RCTAssert(condition, message, ...) _RCTAssert((condition) != 0, message, ##__VA_ARGS__)
|
||||
#define RCTCAssert(condition, message, ...) _RCTCAssert((condition) != 0, message, ##__VA_ARGS__)
|
||||
|
||||
typedef void (^RCTAssertFunction)(BOOL condition, NSString *message, ...);
|
||||
|
||||
|
||||
@@ -88,6 +88,12 @@ BOOL RCTSetProperty(id target, NSString *keypath, id json);
|
||||
*/
|
||||
BOOL RCTCopyProperty(id target, id source, NSString *keypath);
|
||||
|
||||
/**
|
||||
* This function attempts to convert a JSON value to an object that can be used
|
||||
* in KVC with the specific target and key path.
|
||||
*/
|
||||
id RCTConvertValue(id target, NSString *keypath, id json);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -608,7 +608,7 @@ static NSString *RCTGuessTypeEncoding(id target, NSString *key, id value, NSStri
|
||||
return nil;
|
||||
}
|
||||
|
||||
static NSDictionary *RCTConvertValue(id value, NSString *encoding)
|
||||
static id RCTConvertValueWithEncoding(id value, NSString *encoding)
|
||||
{
|
||||
static NSDictionary *converters = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
@@ -690,18 +690,7 @@ static NSDictionary *RCTConvertValue(id value, NSString *encoding)
|
||||
return converter ? converter(value) : value;
|
||||
}
|
||||
|
||||
BOOL RCTSetProperty(id target, NSString *keypath, id value)
|
||||
{
|
||||
// Split keypath
|
||||
NSArray *parts = [keypath componentsSeparatedByString:@"."];
|
||||
NSString *key = [parts lastObject];
|
||||
for (NSUInteger i = 0; i < parts.count - 1; i++) {
|
||||
target = [target valueForKey:parts[i]];
|
||||
if (!target) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
static NSString *RCTPropertyEncoding(id target, NSString *key, id value) {
|
||||
// Check target class for property definition
|
||||
NSString *encoding = nil;
|
||||
objc_property_t property = class_getProperty([target class], [key UTF8String]);
|
||||
@@ -720,7 +709,7 @@ BOOL RCTSetProperty(id target, NSString *keypath, id value)
|
||||
[key substringFromIndex:1]]);
|
||||
|
||||
if (![target respondsToSelector:setter]) {
|
||||
return NO;
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Get type of first method argument
|
||||
@@ -730,17 +719,92 @@ BOOL RCTSetProperty(id target, NSString *keypath, id value)
|
||||
encoding = @(typeEncoding);
|
||||
free(typeEncoding);
|
||||
}
|
||||
|
||||
if (encoding.length == 0 || [encoding isEqualToString:@(@encode(id))]) {
|
||||
// Not enough info about the type encoding to be useful, so
|
||||
// try to guess the type from the value and property name
|
||||
encoding = RCTGuessTypeEncoding(target, key, value, encoding);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (encoding.length == 0 || [encoding isEqualToString:@(@encode(id))]) {
|
||||
// Not enough info about the type encoding to be useful, so
|
||||
// try to guess the type from the value and property name
|
||||
encoding = RCTGuessTypeEncoding(target, key, value, encoding);
|
||||
return encoding;
|
||||
}
|
||||
|
||||
static id RCTConvertValueWithExplicitEncoding(id target, NSString *key, id json, NSString *encoding) {
|
||||
if (!encoding) return nil;
|
||||
|
||||
// Special case for numeric encodings, which may be enums
|
||||
if ([json isKindOfClass:[NSString class]] &&
|
||||
[@"iIsSlLqQ" rangeOfString:[encoding substringToIndex:1]].length) {
|
||||
|
||||
/**
|
||||
* NOTE: the property names below may seem weird, but it's
|
||||
* because they are tested as case-sensitive suffixes, so
|
||||
* "apitalizationType" will match any of the following
|
||||
*
|
||||
* - capitalizationType
|
||||
* - autocapitalizationType
|
||||
* - autoCapitalizationType
|
||||
* - titleCapitalizationType
|
||||
* - etc.
|
||||
*/
|
||||
static NSDictionary *converters = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
converters =
|
||||
@{
|
||||
@"apitalizationType": ^(id val) {
|
||||
return [RCTConvert UITextAutocapitalizationType:val];
|
||||
},
|
||||
@"eyboardType": ^(id val) {
|
||||
return [RCTConvert UIKeyboardType:val];
|
||||
},
|
||||
@"extAlignment": ^(id val) {
|
||||
return [RCTConvert NSTextAlignment:val];
|
||||
},
|
||||
@"ointerEvents": ^(id val) {
|
||||
return [RCTConvert RCTPointerEvents:val];
|
||||
},
|
||||
};
|
||||
});
|
||||
for (NSString *subkey in converters) {
|
||||
if ([key hasSuffix:subkey]) {
|
||||
NSInteger (^converter)(NSString *) = converters[subkey];
|
||||
json = @(converter(json));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RCTConvertValueWithEncoding(json, encoding);
|
||||
}
|
||||
|
||||
id RCTConvertValue(id target, NSString *key, id json) {
|
||||
NSString *encoding = RCTPropertyEncoding(target, key, json);
|
||||
return RCTConvertValueWithExplicitEncoding(target, key, json, encoding);
|
||||
}
|
||||
|
||||
BOOL RCTSetProperty(id target, NSString *keypath, id value)
|
||||
{
|
||||
// Split keypath
|
||||
NSArray *parts = [keypath componentsSeparatedByString:@"."];
|
||||
NSString *key = [parts lastObject];
|
||||
for (NSUInteger i = 0; i < parts.count - 1; i++) {
|
||||
target = [target valueForKey:parts[i]];
|
||||
if (!target) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
NSString *encoding = RCTPropertyEncoding(target, key, value);
|
||||
if (!encoding) return NO;
|
||||
|
||||
value = RCTConvertValueWithExplicitEncoding(target, keypath, value, encoding);
|
||||
|
||||
// Special case for numeric encodings, which may be enums
|
||||
if ([value isKindOfClass:[NSString class]] &&
|
||||
[@"iIsSlLqQ" rangeOfString:[encoding substringToIndex:1]].length) {
|
||||
[@"iIsSlLqQ" rangeOfString:[encoding substringToIndex:1]].location != NSNotFound) {
|
||||
|
||||
/**
|
||||
* NOTE: the property names below may seem weird, but it's
|
||||
@@ -798,15 +862,15 @@ BOOL RCTSetProperty(id target, NSString *keypath, id value)
|
||||
});
|
||||
|
||||
void (^block)(UITextField *f, NSInteger v) = specialCases[key];
|
||||
if (block)
|
||||
{
|
||||
if (block) {
|
||||
block(target, [value integerValue]);
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
// Set converted value
|
||||
[target setValue:RCTConvertValue(value, encoding) forKey:key];
|
||||
[target setValue:value forKey:key];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
9
ReactKit/Modules/RCTAnimationManager.h
Normal file
9
ReactKit/Modules/RCTAnimationManager.h
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
|
||||
@interface RCTAnimationManager : NSObject <RCTBridgeModule>
|
||||
|
||||
@end
|
||||
203
ReactKit/Modules/RCTAnimationManager.m
Normal file
203
ReactKit/Modules/RCTAnimationManager.m
Normal file
@@ -0,0 +1,203 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTAnimationManager.h"
|
||||
|
||||
#import <Accelerate/Accelerate.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTSparseArray.h"
|
||||
#import "RCTUIManager.h"
|
||||
|
||||
#if CGFLOAT_IS_DOUBLE
|
||||
#define CG_APPEND(PREFIX, SUFFIX_F, SUFFIX_D) PREFIX##SUFFIX_D
|
||||
#else
|
||||
#define CG_APPEND(PREFIX, SUFFIX_F, SUFFIX_D) PREFIX##SUFFIX_F
|
||||
#endif
|
||||
|
||||
@implementation RCTAnimationManager
|
||||
{
|
||||
RCTSparseArray *_animationRegistry; // Main thread only; animation tag -> view tag
|
||||
}
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_animationRegistry = [[RCTSparseArray alloc] init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id (^)(CGFloat))interpolateFrom:(CGFloat[])fromArray to:(CGFloat[])toArray count:(NSUInteger)count typeName:(const char *)typeName
|
||||
{
|
||||
if (count == 1) {
|
||||
CGFloat from = *fromArray, to = *toArray, delta = to - from;
|
||||
return ^(CGFloat t) {
|
||||
return @(from + t * delta);
|
||||
};
|
||||
}
|
||||
|
||||
CG_APPEND(vDSP_vsub,,D)(fromArray, 1, toArray, 1, toArray, 1, count);
|
||||
|
||||
const size_t size = count * sizeof(CGFloat);
|
||||
NSData *deltaData = [NSData dataWithBytes:toArray length:size];
|
||||
NSData *fromData = [NSData dataWithBytes:fromArray length:size];
|
||||
|
||||
return ^(CGFloat t) {
|
||||
const CGFloat *delta = deltaData.bytes;
|
||||
const CGFloat *fromArray = fromData.bytes;
|
||||
|
||||
CGFloat value[count];
|
||||
CG_APPEND(vDSP_vma,,D)(delta, 1, &t, 0, fromArray, 1, value, 1, count);
|
||||
return [NSValue valueWithBytes:value objCType:typeName];
|
||||
};
|
||||
}
|
||||
|
||||
- (void)startAnimationForTag:(NSNumber *)reactTag animationTag:(NSNumber *)animationTag duration:(double)duration delay:(double)delay easingSample:(NSArray *)easingSample properties:(NSDictionary *)properties
|
||||
{
|
||||
RCT_EXPORT(startAnimation);
|
||||
|
||||
__weak RCTAnimationManager *weakSelf = self;
|
||||
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
RCTAnimationManager *strongSelf = weakSelf;
|
||||
|
||||
UIView *view = viewRegistry[reactTag];
|
||||
if (!view) {
|
||||
RCTLogWarn(@"React tag %@ is not registered with the view registry", reactTag);
|
||||
return;
|
||||
}
|
||||
|
||||
[properties enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
|
||||
NSValue *toValue = nil;
|
||||
if ([key isEqualToString:@"scaleXY"]) {
|
||||
key = @"transform.scale";
|
||||
toValue = obj[0];
|
||||
} else if ([obj respondsToSelector:@selector(count)]) {
|
||||
switch ([obj count]) {
|
||||
case 2:
|
||||
if ([obj respondsToSelector:@selector(objectForKey:)] && [obj objectForKey:@"w"]) {
|
||||
toValue = [NSValue valueWithCGSize:[RCTConvert CGSize:obj]];
|
||||
} else {
|
||||
toValue = [NSValue valueWithCGPoint:[RCTConvert CGPoint:obj]];
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
toValue = [NSValue valueWithCGRect:[RCTConvert CGRect:obj]];
|
||||
break;
|
||||
case 16:
|
||||
toValue = [NSValue valueWithCGAffineTransform:[RCTConvert CGAffineTransform:obj]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!toValue) toValue = obj;
|
||||
|
||||
const char *typeName = toValue.objCType;
|
||||
|
||||
size_t count;
|
||||
switch (typeName[0]) {
|
||||
case 'i':
|
||||
case 'I':
|
||||
case 's':
|
||||
case 'S':
|
||||
case 'l':
|
||||
case 'L':
|
||||
case 'q':
|
||||
case 'Q':
|
||||
count = 1;
|
||||
break;
|
||||
|
||||
default: {
|
||||
NSUInteger size;
|
||||
NSGetSizeAndAlignment(typeName, &size, NULL);
|
||||
count = size / sizeof(CGFloat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CGFloat toFields[count];
|
||||
|
||||
switch (typeName[0]) {
|
||||
#define CASE(encoding, type) \
|
||||
case encoding: { \
|
||||
type value; \
|
||||
[toValue getValue:&value]; \
|
||||
toFields[0] = value; \
|
||||
break; \
|
||||
}
|
||||
|
||||
CASE('i', int)
|
||||
CASE('I', unsigned int)
|
||||
CASE('s', short)
|
||||
CASE('S', unsigned short)
|
||||
CASE('l', long)
|
||||
CASE('L', unsigned long)
|
||||
CASE('q', long long)
|
||||
CASE('Q', unsigned long long)
|
||||
|
||||
#undef CASE
|
||||
|
||||
default:
|
||||
[toValue getValue:toFields];
|
||||
break;
|
||||
}
|
||||
|
||||
NSValue *fromValue = [view.layer.presentationLayer valueForKeyPath:key];
|
||||
CGFloat fromFields[count];
|
||||
[fromValue getValue:fromFields];
|
||||
|
||||
id (^interpolationBlock)(CGFloat t) = [strongSelf interpolateFrom:fromFields to:toFields count:count typeName:typeName];
|
||||
|
||||
NSMutableArray *sampledValues = [NSMutableArray arrayWithCapacity:easingSample.count];
|
||||
for (NSNumber *sample in easingSample) {
|
||||
CGFloat t = sample.CG_APPEND(, floatValue, doubleValue);
|
||||
[sampledValues addObject:interpolationBlock(t)];
|
||||
}
|
||||
|
||||
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:key];
|
||||
animation.beginTime = CACurrentMediaTime() + delay / 1000.0;
|
||||
animation.duration = duration / 1000.0;
|
||||
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
|
||||
animation.values = sampledValues;
|
||||
|
||||
[view.layer setValue:toValue forKey:key];
|
||||
|
||||
NSString *animationKey = [NSString stringWithFormat:@"RCT.%@.%@", animationTag, key];
|
||||
[view.layer addAnimation:animation forKey:animationKey];
|
||||
}];
|
||||
|
||||
strongSelf->_animationRegistry[animationTag] = reactTag;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)stopAnimation:(NSNumber *)animationTag
|
||||
{
|
||||
RCT_EXPORT(stopAnimation);
|
||||
|
||||
__weak RCTAnimationManager *weakSelf = self;
|
||||
[_bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
RCTAnimationManager *strongSelf = weakSelf;
|
||||
|
||||
NSNumber *reactTag = strongSelf->_animationRegistry[animationTag];
|
||||
if (!reactTag) return;
|
||||
|
||||
UIView *view = viewRegistry[reactTag];
|
||||
for (NSString *animationKey in view.layer.animationKeys) {
|
||||
if ([animationKey hasPrefix:@"RCT"]) {
|
||||
NSRange periodLocation = [animationKey rangeOfString:@"." options:0 range:NSMakeRange(3, animationKey.length - 3)];
|
||||
if (periodLocation.location != NSNotFound) {
|
||||
NSInteger integerTag = [[animationKey substringWithRange:NSMakeRange(3, periodLocation.location)] integerValue];
|
||||
if (animationTag.integerValue == integerTag) {
|
||||
[view.layer removeAnimationForKey:animationKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf->_animationRegistry[animationTag] = nil;
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -2,24 +2,25 @@
|
||||
|
||||
#import "RCTUIManager.h"
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <objc/message.h>
|
||||
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
#import "Layout.h"
|
||||
#import "RCTAnimationType.h"
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTRootView.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTNavigator.h"
|
||||
#import "RCTRootView.h"
|
||||
#import "RCTScrollableProtocol.h"
|
||||
#import "RCTShadowView.h"
|
||||
#import "RCTSparseArray.h"
|
||||
#import "RCTUtils.h"
|
||||
#import "RCTView.h"
|
||||
#import "RCTViewNodeProtocol.h"
|
||||
#import "RCTViewManager.h"
|
||||
#import "RCTViewNodeProtocol.h"
|
||||
#import "UIView+ReactKit.h"
|
||||
|
||||
typedef void (^react_view_node_block_t)(id<RCTViewNodeProtocol>);
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; };
|
||||
830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 830BA4541A8E3BDA00D53203 /* RCTCache.m */; };
|
||||
832348161A77A5AA00B55238 /* Layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FC71A68125100A75B9A /* Layout.c */; };
|
||||
83C911101AAE6521001323A3 /* RCTAnimationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 83C9110F1AAE6521001323A3 /* RCTAnimationManager.m */; };
|
||||
83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA4B1A601E3B00E9B192 /* RCTAssert.m */; };
|
||||
83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA4E1A601E3B00E9B192 /* RCTLog.m */; };
|
||||
83CBBA531A601E3B00E9B192 /* RCTUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA501A601E3B00E9B192 /* RCTUtils.m */; };
|
||||
@@ -153,6 +154,8 @@
|
||||
830BA4541A8E3BDA00D53203 /* RCTCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCache.m; sourceTree = "<group>"; };
|
||||
83BEE46C1A6D19BC00B5863B /* RCTSparseArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSparseArray.h; sourceTree = "<group>"; };
|
||||
83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSparseArray.m; sourceTree = "<group>"; };
|
||||
83C9110E1AAE6521001323A3 /* RCTAnimationManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimationManager.h; sourceTree = "<group>"; };
|
||||
83C9110F1AAE6521001323A3 /* RCTAnimationManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAnimationManager.m; sourceTree = "<group>"; };
|
||||
83CBBA2E1A601D0E00E9B192 /* libReactKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libReactKit.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
83CBBA4A1A601E3B00E9B192 /* RCTAssert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAssert.h; sourceTree = "<group>"; };
|
||||
83CBBA4B1A601E3B00E9B192 /* RCTAssert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssert.m; sourceTree = "<group>"; };
|
||||
@@ -210,6 +213,8 @@
|
||||
children = (
|
||||
13B07FE71A69327A00A75B9A /* RCTAlertManager.h */,
|
||||
13B07FE81A69327A00A75B9A /* RCTAlertManager.m */,
|
||||
83C9110E1AAE6521001323A3 /* RCTAnimationManager.h */,
|
||||
83C9110F1AAE6521001323A3 /* RCTAnimationManager.m */,
|
||||
13B07FE91A69327A00A75B9A /* RCTExceptionsManager.h */,
|
||||
13B07FEA1A69327A00A75B9A /* RCTExceptionsManager.m */,
|
||||
5F5F0D971A9E456B001279FA /* RCTLocationObserver.h */,
|
||||
@@ -458,6 +463,7 @@
|
||||
13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */,
|
||||
83CBBA531A601E3B00E9B192 /* RCTUtils.m in Sources */,
|
||||
14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */,
|
||||
83C911101AAE6521001323A3 /* RCTAnimationManager.m in Sources */,
|
||||
83CBBA601A601EAA00E9B192 /* RCTBridge.m in Sources */,
|
||||
58114A161AAE854800E7D092 /* RCTPicker.m in Sources */,
|
||||
137327E81AA5CF210034F82E /* RCTTabBarItem.m in Sources */,
|
||||
|
||||
Reference in New Issue
Block a user