Skip to content

Commit

Permalink
feat(ios): support background animation (Tencent#686)
Browse files Browse the repository at this point in the history
 feat(ios): support backgroundcolor property animation
  • Loading branch information
ozonelmy authored and boxizen committed Aug 16, 2021
1 parent 1e671a6 commit c91e34f
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 71 deletions.
12 changes: 12 additions & 0 deletions examples/ios-demo/HippyDemo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,11 @@
064C5A5D23AB1A51001E80DD /* HippyWebSocketManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 064C59E923AB1A51001E80DD /* HippyWebSocketManager.m */; };
064C5AF023AB1A70001E80DD /* res in Resources */ = {isa = PBXBuildFile; fileRef = 064C5AEF23AB1A70001E80DD /* res */; };
0657F5F3254836F70076F5C5 /* HippyModalTransitioningDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0657F5F2254836F70076F5C5 /* HippyModalTransitioningDelegate.mm */; };
066EF46E26243E5A00F56F35 /* UIView+HippyAnimationProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 066EF46D26243E5A00F56F35 /* UIView+HippyAnimationProtocol.m */; };
067AB96F23B5EEF4009D5EE2 /* TestModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 067AB96D23B5EEF3009D5EE2 /* TestModule.m */; };
067AB97523B5F309009D5EE2 /* MyViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 067AB97123B5F309009D5EE2 /* MyViewManager.m */; };
067AB97623B5F309009D5EE2 /* MyView.m in Sources */ = {isa = PBXBuildFile; fileRef = 067AB97423B5F309009D5EE2 /* MyView.m */; };
0680B4F02625480100F83EAB /* HippyView+HippyViewAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 0680B4EF2625480100F83EAB /* HippyView+HippyViewAnimation.m */; };
06868AF925CA4A6B0024A0BA /* HippyDeviceBaseInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 06868AF825CA4A6B0024A0BA /* HippyDeviceBaseInfo.m */; };
06F2A76F2519E319000AF839 /* HippyJSEnginesMapper.mm in Sources */ = {isa = PBXBuildFile; fileRef = 06F2A76E2519E319000AF839 /* HippyJSEnginesMapper.mm */; };
06F3E3E025A5D867007934F6 /* HippyListTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 06F3E3DF25A5D867007934F6 /* HippyListTableView.m */; };
Expand Down Expand Up @@ -448,12 +450,16 @@
0657F5F1254836F70076F5C5 /* HippyModalTransitioningDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HippyModalTransitioningDelegate.h; sourceTree = "<group>"; };
0657F5F2254836F70076F5C5 /* HippyModalTransitioningDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = HippyModalTransitioningDelegate.mm; sourceTree = "<group>"; };
0657F5F5254837C40076F5C5 /* HippyModalHostViewInteractor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HippyModalHostViewInteractor.h; sourceTree = "<group>"; };
066EF46C26243E5A00F56F35 /* UIView+HippyAnimationProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+HippyAnimationProtocol.h"; sourceTree = "<group>"; };
066EF46D26243E5A00F56F35 /* UIView+HippyAnimationProtocol.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIView+HippyAnimationProtocol.m"; sourceTree = "<group>"; };
067AB96D23B5EEF3009D5EE2 /* TestModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestModule.m; sourceTree = "<group>"; };
067AB96E23B5EEF3009D5EE2 /* TestModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestModule.h; sourceTree = "<group>"; };
067AB97123B5F309009D5EE2 /* MyViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MyViewManager.m; sourceTree = "<group>"; };
067AB97223B5F309009D5EE2 /* MyView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MyView.h; sourceTree = "<group>"; };
067AB97323B5F309009D5EE2 /* MyViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MyViewManager.h; sourceTree = "<group>"; };
067AB97423B5F309009D5EE2 /* MyView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MyView.m; sourceTree = "<group>"; };
0680B4EE2625480100F83EAB /* HippyView+HippyViewAnimation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "HippyView+HippyViewAnimation.h"; sourceTree = "<group>"; };
0680B4EF2625480100F83EAB /* HippyView+HippyViewAnimation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "HippyView+HippyViewAnimation.m"; sourceTree = "<group>"; };
06868AF725CA4A6B0024A0BA /* HippyDeviceBaseInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HippyDeviceBaseInfo.h; sourceTree = "<group>"; };
06868AF825CA4A6B0024A0BA /* HippyDeviceBaseInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HippyDeviceBaseInfo.m; sourceTree = "<group>"; };
06F2A76D2519E319000AF839 /* HippyJSEnginesMapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HippyJSEnginesMapper.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -952,6 +958,10 @@
064C596523AB1A51001E80DD /* HippyBackgroundImageCacheManager.m */,
0640D25624AC75DD00F122A0 /* UIView+AppearEvent.h */,
0640D25724AC75DD00F122A0 /* UIView+AppearEvent.m */,
066EF46C26243E5A00F56F35 /* UIView+HippyAnimationProtocol.h */,
066EF46D26243E5A00F56F35 /* UIView+HippyAnimationProtocol.m */,
0680B4EE2625480100F83EAB /* HippyView+HippyViewAnimation.h */,
0680B4EF2625480100F83EAB /* HippyView+HippyViewAnimation.m */,
);
path = view;
sourceTree = "<group>";
Expand Down Expand Up @@ -1549,6 +1559,7 @@
064C5A4723AB1A51001E80DD /* HippyTouchHandler.m in Sources */,
85BCD46E2578C58000638DB4 /* js_native_api_value_jsc.cc in Sources */,
0640D25824AC75DD00F122A0 /* UIView+AppearEvent.m in Sources */,
066EF46E26243E5A00F56F35 /* UIView+HippyAnimationProtocol.m in Sources */,
064C5A3023AB1A51001E80DD /* HippyNavigatorViewManager.m in Sources */,
064C5A3F23AB1A51001E80DD /* NSNumber+HippyNumberDeepCopy.m in Sources */,
064C59F723AB1A51001E80DD /* HippyExtAnimationGroup.m in Sources */,
Expand Down Expand Up @@ -1581,6 +1592,7 @@
064C5A1D23AB1A51001E80DD /* HippyAnimatedImage.m in Sources */,
067AB96F23B5EEF4009D5EE2 /* TestModule.m in Sources */,
06FF8EEC2511C10B00C03900 /* HippyDefaultImageProvider.m in Sources */,
0680B4F02625480100F83EAB /* HippyView+HippyViewAnimation.m in Sources */,
064C5A2E23AB1A51001E80DD /* HippyNavigatorHostView.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
1 change: 0 additions & 1 deletion ios/sdk/base/virtualNode/HippyVirtualNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
@property (nonatomic, retain) NSMutableArray<HippyVirtualNode *> *subNodes;

@property (nonatomic, weak) HippyVirtualList *listNode;
@property (nonatomic, weak) HippyVirtualCell *cellNode;
@property (nonatomic, copy) NSNumber *rootTag;
@property (nonatomic, weak) HippyBridge *bridge;

Expand Down
32 changes: 32 additions & 0 deletions ios/sdk/component/view/HippyView+HippyViewAnimation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*!
* iOS SDK
*
* Tencent is pleased to support the open source community by making
* Hippy available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company.
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import "HippyView.h"
#import "UIView+HippyAnimationProtocol.h"

NS_ASSUME_NONNULL_BEGIN

@interface HippyView (HippyViewAnimation)

@end

NS_ASSUME_NONNULL_END
67 changes: 67 additions & 0 deletions ios/sdk/component/view/HippyView+HippyViewAnimation.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*!
* iOS SDK
*
* Tencent is pleased to support the open source community by making
* Hippy available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company.
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


#import "HippyView+HippyViewAnimation.h"
#import "HippyExtAnimation.h"
#import "HippyConvert.h"

@implementation HippyView (HippyViewAnimation)

- (CAAnimation *)animation:(HippyExtAnimation *)animation keyPath:(NSString *)keyPath {
if ([keyPath isEqualToString:@"backgroundColor"]) {
__block id fromValue = nil;
__block id toValue = nil;
UIColor *fromColor = [HippyConvert UIColor:@(animation.startValue)];
UIColor *toColor = [HippyConvert UIColor:@(animation.endValue)];
if (self.layer.contents) {
BOOL sync = [self getLayerContentForColor:toColor completionBlock:^(UIImage *image) {
toValue = (id)image.CGImage;
}];
if (sync && toValue) {
keyPath = @"contents";
}
else {
return nil;
}
}
else {
toValue = (id)toColor.CGColor;
fromValue = (id)fromColor.CGColor;
}
CABasicAnimation *ani = [CABasicAnimation animationWithKeyPath:keyPath];
ani.duration = animation.duration;
if (fabs(animation.delay) > CGFLOAT_MIN) {
ani.beginTime = CACurrentMediaTime() + animation.delay;
}
ani.timingFunction = [CAMediaTimingFunction functionWithName:animation.timingFunction];
ani.repeatCount = animation.repeatCount;
ani.removedOnCompletion = NO;
ani.fillMode = kCAFillModeForwards;
ani.toValue = toValue;
ani.fromValue = fromValue;
return ani;
}
return nil;
}

@end
6 changes: 6 additions & 0 deletions ios/sdk/component/view/HippyView.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@
*/
- (void)updateClippedSubviews;

/**
* get content for layer
* return YES if getting content synchronized,else return NO
*/
- (BOOL)getLayerContentForColor:(UIColor *)color completionBlock:(void (^)(UIImage *))contentBlock;

/**
* Border radii.
*/
Expand Down
139 changes: 70 additions & 69 deletions ios/sdk/component/view/HippyView.m
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,6 @@ - (void)displayLayer:(CALayer *)layer {
return;
}

__weak CALayer *weakLayer = layer;
[self drawShadowForLayer];

const HippyCornerRadii cornerRadii = [self cornerRadii];
Expand Down Expand Up @@ -562,87 +561,89 @@ - (void)displayLayer:(CALayer *)layer {
layer.mask = nil;
return;
}

CGRect theFrame = self.frame;
NSInteger clipToBounds = self.clipsToBounds;
NSString *backgroundSize = self.backgroundSize;

__weak typeof(self) weakSelf = self;
HippyBoarderColorsRetain(borderColors);
dispatch_async(global_hpview_queue(), ^{
UIImage *image = HippyGetBorderImage(
weakSelf.borderStyle, theFrame.size, cornerRadii, borderInsets, borderColors, backgroundColor.CGColor, clipToBounds);
HippyBoarderColorsRelease(borderColors);
if (image == nil) {
weakLayer.contents = nil;
weakLayer.needsDisplayOnBoundsChange = NO;
return;
}
[self getLayerContentForColor:nil completionBlock:^(UIImage *contentImage) {
dispatch_async(dispatch_get_main_queue(), ^{
typeof(weakSelf) strongSelf = weakSelf;
CALayer *strongLayer = strongSelf.layer;
CGRect contentsCenter = ({
CGSize size = contentImage.size;
UIEdgeInsets insets = contentImage.capInsets;
CGRectMake(insets.left / size.width, insets.top / size.height, 1.0 / size.width, 1.0 / size.height);
});

CGRect contentsCenter = ({
CGSize size = image.size;
UIEdgeInsets insets = image.capInsets;
if (size.width == 0 || size.height == 0) {
HippyLogError(@"divided by zero in HippyView.m");
return;
}
CGRectMake(insets.left / size.width, insets.top / size.height, 1.0 / size.width, 1.0 / size.height);
});
strongLayer.contents = (id)contentImage.CGImage;

void (^setImageBlock)(UIImage *) = ^(UIImage *resultingImage) {
dispatch_sync(dispatch_get_main_queue(), ^{
weakLayer.contents = (id)resultingImage.CGImage;
strongLayer.backgroundColor = NULL;

weakLayer.backgroundColor = NULL;
// weakLayer.contents = (id)image.CGImage;
strongLayer.contentsScale = contentImage.scale;
strongLayer.needsDisplayOnBoundsChange = YES;
strongLayer.magnificationFilter = kCAFilterNearest;

// weakLayer.contents = (id)image.CGImage;
weakLayer.contentsScale = image.scale;
weakLayer.needsDisplayOnBoundsChange = YES;
weakLayer.magnificationFilter = kCAFilterNearest;
const BOOL isResizable = !UIEdgeInsetsEqualToEdgeInsets(contentImage.capInsets, UIEdgeInsetsZero);

const BOOL isResizable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero);
[strongSelf updateClippingForLayer:strongLayer];

[self updateClippingForLayer:weakLayer];
if (isResizable) {
strongLayer.contentsCenter = contentsCenter;
} else {
strongLayer.contentsCenter = CGRectMake(0.0, 0.0, 1.0, 1.0);
}
});
}];
}

if (isResizable) {
weakLayer.contentsCenter = contentsCenter;
} else {
weakLayer.contentsCenter = CGRectMake(0.0, 0.0, 1.0, 1.0);
}
});
};
- (BOOL)getLayerContentForColor:(UIColor *)color completionBlock:(void (^)(UIImage *))contentBlock {
const HippyCornerRadii cornerRadii = [self cornerRadii];
const UIEdgeInsets borderInsets = [self bordersAsInsets];
const HippyBorderColors borderColors = [self borderColors];
UIColor *backgroundColor = color?:self.backgroundColor;

CGRect theFrame = self.frame;
NSInteger clipToBounds = self.clipsToBounds;
NSString *backgroundSize = self.backgroundSize;
UIImage *image = HippyGetBorderImage(
self.borderStyle, theFrame.size, cornerRadii, borderInsets, borderColors, backgroundColor.CGColor, clipToBounds);
if (image == nil) {
contentBlock(nil);
return YES;
}

if (!weakSelf.backgroundImageUrl) {
setImageBlock(image);
} else {
CGFloat backgroundPositionX = weakSelf.backgroundPositionX;
CGFloat backgroundPositionY = weakSelf.backgroundPositionY;
HippyBackgroundImageCacheManager *weakBackgroundCacheManager = [weakSelf backgroundCachemanager];
[weakBackgroundCacheManager imageWithUrl:weakSelf.backgroundImageUrl completionHandler:^(UIImage *decodedImage, NSError *error) {
if (error) {
HippyLogError(@"weakBackgroundCacheManagerLog %@", error);
return;
}
if (!decodedImage) {
setImageBlock(nil);
}
if (!self.backgroundImageUrl) {
contentBlock(image);
return YES;
} else {
CGFloat backgroundPositionX = self.backgroundPositionX;
CGFloat backgroundPositionY = self.backgroundPositionY;
HippyBackgroundImageCacheManager *weakBackgroundCacheManager = [self backgroundCachemanager];
[weakBackgroundCacheManager imageWithUrl:self.backgroundImageUrl completionHandler:^(UIImage *decodedImage, NSError *error) {
if (error) {
HippyLogError(@"weakBackgroundCacheManagerLog %@", error);
return;
}
if (!decodedImage) {
contentBlock(nil);
}

UIGraphicsBeginImageContextWithOptions(theFrame.size, NO, image.scale);
CGSize size = theFrame.size;
UIGraphicsBeginImageContextWithOptions(theFrame.size, NO, image.scale);
CGSize size = theFrame.size;

[image drawInRect:(CGRect) { CGPointZero, size }];
CGSize imageSize = decodedImage.size;
CGSize targetSize = UIEdgeInsetsInsetRect(theFrame, [weakSelf bordersAsInsets]).size;
[image drawInRect:(CGRect) { CGPointZero, size }];
CGSize imageSize = decodedImage.size;
CGSize targetSize = UIEdgeInsetsInsetRect(theFrame, [self bordersAsInsets]).size;

CGSize drawSize = makeSizeConstrainWithType(imageSize, targetSize, backgroundSize);
CGSize drawSize = makeSizeConstrainWithType(imageSize, targetSize, backgroundSize);

[decodedImage drawInRect:CGRectMake(borderInsets.left + backgroundPositionX, borderInsets.top + backgroundPositionY, drawSize.width,
drawSize.height)];
UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
setImageBlock(resultingImage);
}];
}
});
[decodedImage drawInRect:CGRectMake(borderInsets.left + backgroundPositionX, borderInsets.top + backgroundPositionY, drawSize.width,
drawSize.height)];
UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
contentBlock(resultingImage);
}];
return NO;
}
}

- (HippyBackgroundImageCacheManager *)backgroundCachemanager {
Expand Down
39 changes: 39 additions & 0 deletions ios/sdk/component/view/UIView+HippyAnimationProtocol.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*!
* iOS SDK
*
* Tencent is pleased to support the open source community by making
* Hippy available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company.
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@class HippyExtAnimation;

@interface UIView (HippyAnimationProtocol)

/**
* some animation should be created by View itself rather than HippyExtAnimation.
* for example:backgroundColor, etc.
*/
- (CAAnimation *)animation:(HippyExtAnimation *)animation keyPath:(NSString *)keyPath;

@end

NS_ASSUME_NONNULL_END
Loading

0 comments on commit c91e34f

Please sign in to comment.