CALayer 没有像 UIView 动画一样的动画
CALayer does not animate the same as the UIView animation
我有一个 class 有 16 个子层来形成一个标志。当我为 UIView
设置动画时,CALayers
没有设置动画,而是进入最终状态,如下面的动画 gif 所示:
我的代码是:
@implementation LogoView
#define CGRECTMAKE(a, b, w, h) {.origin={.x=(a),.y=(b)},.size={.width=(w),.height=(h)}}
#pragma mark - Create Subviews
const static CGRect path[] = {
CGRECTMAKE(62.734375,-21.675000,18.900000,18.900000),
CGRECTMAKE(29.784375,-31.725000,27.400000,27.300000),
CGRECTMAKE(2.534375,-81.775000,18.900000,18.900000),
CGRECTMAKE(4.384375,-57.225000,27.400000,27.300000),
CGRECTMAKE(2.784375,62.875000,18.900000,18.900000),
CGRECTMAKE(4.334375,29.925000,27.400000,27.300000),
CGRECTMAKE(62.734375,2.525000,18.900000,18.900000),
CGRECTMAKE(29.784375,4.475000,27.400000,27.300000),
CGRECTMAKE(-21.665625,-81.775000,18.900000,18.900000),
CGRECTMAKE(-31.765625,-57.225000,27.400000,27.300000),
CGRECTMAKE(-81.615625,-21.425000,18.900000,18.900000),
CGRECTMAKE(-57.215625,-31.775000,27.400000,27.300000),
CGRECTMAKE(-81.615625,2.775000,18.900000,18.900000),
CGRECTMAKE(-57.215625,4.425000,27.400000,27.300000),
CGRECTMAKE(-21.415625,62.875000,18.900000,18.900000),
CGRECTMAKE(-31.765625,29.925000,27.400000,27.300000)
};
- (void) createSubviews
{
self.contentMode = UIViewContentModeRedraw;
for (int i = 0; i < 16; i++) {
CGRect rect = CGRectApplyAffineTransform(path[i],
CGAffineTransformMakeScale(self.frame.size.width / 213.0,
self.frame.size.height / 213.0));
UIBezierPath * b = [UIBezierPath bezierPathWithOvalInRect:
CGRectOffset(rect,
self.frame.size.width/2.0,
self.frame.size.height/2)];
CAShapeLayer * layer = [[CAShapeLayer alloc] init];
layer.path = [b CGPath];
layer.fillColor = [self.tintColor CGColor];
[self.layer addSublayer:layer];
}
self.layer.needsDisplayOnBoundsChange = YES;
self.initialLenght = self.frame.size.width;
}
- (void) layoutSublayersOfLayer:(CALayer *)layer
{
for (int i = 0; i < 16; i++) {
CGRect rect = CGRectApplyAffineTransform(path[i],
CGAffineTransformMakeScale(layer.frame.size.width / 213.0,
layer.frame.size.height / 213.0));
UIBezierPath * b = [UIBezierPath
bezierPathWithOvalInRect:CGRectOffset(rect,
layer.frame.size.width/2.0,
layer.frame.size.height/2)];
((CAShapeLayer*)(layer.sublayers[i])).path = b.CGPath;
}
}
- (void)layoutSubviews {
[super layoutSubviews];
// get current animation for bounds
CAAnimation *anim = [self.layer animationForKey:@"bounds"];
[CATransaction begin];
if(anim) {
// animating, apply same duration and timing function.
[CATransaction setAnimationDuration:anim.duration];
[CATransaction setAnimationTimingFunction:anim.timingFunction];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
[self.layer addAnimation:pathAnimation forKey:@"path"];
}
else {
// not animating, we should disable implicit animations.
[CATransaction disableActions];
}
self.layer.frame = self.frame;
[CATransaction commit];
}
我正在制作动画:
[UIView animateWithDuration:3.0 animations:^{
[v setFrame:CGRectMake(0.0, 0.0, 300.0, 300.0)];
}];
如何同步图层动画和视图动画?
为了让图层正确地动画化,您可能需要为您的点子类化 CALayer
(或 CAShapeLayer
)并实现以下方法:
+ (BOOL)needsDisplayForKey:(NSString *)key;
- (instancetype)initWithLayer:(id)layer;
- (void)drawInContext:(CGContextRef)context;
- (id<CAAction>)actionForKey:(NSString *)event;
您可能还需要将任何图层属性标记为 @dynamic
。
编辑:另一种(可能更简单)的方法是使用 CABasicAnimation
而不是使用 UIView
的 animateWithDuration:animations:
方法。
与UI视图动画同步的动画层属性可能很棘手。相反,让我们使用不同的视图结构来利用内置支持对视图的 transform
进行动画处理,而不是尝试对层的 path
.
进行动画处理
我们将使用两个视图:一个父视图和一个子视图。超级视图是一个 LogoView
并且是我们在故事板中布局的内容(或者您创建 UI)。 LogoView
为 class LogoLayerView
自身添加了一个子视图。此 LogoLayerView
使用 CAShapeLayer
作为图层而不是普通的 CALayer
.
请注意,我们只需要一个 CAShapeLayer
,因为一条路径可以包含多个断开连接的区域。
我们将 LogoLayerView
的 frame/bounds 设置为 CGRectMake(0, 0, 213, 213)
一次,并且永远不会更改它。相反,当外部 LogoView
改变大小时,我们设置 LogoLayerView
的 transform
以便它仍然填充外部 LogoView
.
结果如下:
代码如下:
LogoView.h
#import <UIKit/UIKit.h>
IB_DESIGNABLE
@interface LogoView : UIView
@end
LogoView.m
#import "LogoView.h"
#define CGRECTMAKE(a, b, w, h) {.origin={.x=(a),.y=(b)},.size={.width=(w),.height=(h)}}
const static CGRect ovalRects[] = {
CGRECTMAKE(62.734375,-21.675000,18.900000,18.900000),
CGRECTMAKE(29.784375,-31.725000,27.400000,27.300000),
CGRECTMAKE(2.534375,-81.775000,18.900000,18.900000),
CGRECTMAKE(4.384375,-57.225000,27.400000,27.300000),
CGRECTMAKE(2.784375,62.875000,18.900000,18.900000),
CGRECTMAKE(4.334375,29.925000,27.400000,27.300000),
CGRECTMAKE(62.734375,2.525000,18.900000,18.900000),
CGRECTMAKE(29.784375,4.475000,27.400000,27.300000),
CGRECTMAKE(-21.665625,-81.775000,18.900000,18.900000),
CGRECTMAKE(-31.765625,-57.225000,27.400000,27.300000),
CGRECTMAKE(-81.615625,-21.425000,18.900000,18.900000),
CGRECTMAKE(-57.215625,-31.775000,27.400000,27.300000),
CGRECTMAKE(-81.615625,2.775000,18.900000,18.900000),
CGRECTMAKE(-57.215625,4.425000,27.400000,27.300000),
CGRECTMAKE(-21.415625,62.875000,18.900000,18.900000),
CGRECTMAKE(-31.765625,29.925000,27.400000,27.300000)
};
#define LogoDimension 213.0
@interface LogoLayerView : UIView
@property (nonatomic, strong, readonly) CAShapeLayer *layer;
@end
@implementation LogoLayerView
@dynamic layer;
+ (Class)layerClass {
return [CAShapeLayer class];
}
- (void)layoutSubviews {
[super layoutSubviews];
if (self.layer.path == nil) {
[self initShapeLayer];
}
}
- (void)initShapeLayer {
self.layer.backgroundColor = [UIColor yellowColor].CGColor;
self.layer.strokeColor = nil;
self.layer.fillColor = [UIColor greenColor].CGColor;
UIBezierPath *path = [UIBezierPath bezierPath];
for (size_t i = 0; i < sizeof ovalRects / sizeof *ovalRects; ++i) {
[path appendPath:[UIBezierPath bezierPathWithOvalInRect:ovalRects[i]]];
}
[path applyTransform:CGAffineTransformMakeTranslation(LogoDimension / 2, LogoDimension / 2)];
self.layer.path = path.CGPath;
}
@end
@implementation LogoView {
LogoLayerView *layerView;
}
- (void)layoutSubviews {
[super layoutSubviews];
if (layerView == nil) {
layerView = [[LogoLayerView alloc] init];
layerView.layer.anchorPoint = CGPointZero;
layerView.frame = CGRectMake(0, 0, LogoDimension, LogoDimension);
[self addSubview:layerView];
}
[self layoutShapeLayer];
}
- (void)layoutShapeLayer {
CGSize mySize = self.bounds.size;
layerView.transform = CGAffineTransformMakeScale(mySize.width / LogoDimension, mySize.height / LogoDimension);
}
@end
您可以使 CAShapeLayer.path
可动画并在 -layoutSublayersOfLayer
中更新您的自定义层。只需确保匹配 UIView
和 CAShapeLayer
子类的持续时间。
简单明了:
//: Playground - noun: a place where people can play
import UIKit
import XCPlayground
let kAnimationDuration: NSTimeInterval = 4.0
class AnimatablePathShape: CAShapeLayer {
override func actionForKey(event: String) -> CAAction? {
if event == "path" {
let value = self.presentationLayer()?.valueForKey(event) ?? self.valueForKey(event)
let anim = CABasicAnimation(keyPath: event)
anim.duration = kAnimationDuration
anim.fromValue = value
anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
return anim
}
return super.actionForKey(event)
}
override class func needsDisplayForKey(key: String) -> Bool {
if key == "path" {
return true
}
return super.needsDisplayForKey(key)
}
}
class View: UIView {
let shape = AnimatablePathShape()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.whiteColor().colorWithAlphaComponent(0.1)
self.shape.fillColor = UIColor.magentaColor().CGColor
self.layer.addSublayer(self.shape)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func yoyo(grow: Bool = true) {
let options: UIViewAnimationOptions = [.CurveEaseInOut]
let animations = { () -> Void in
let scale: CGFloat = CGFloat(grow ? 4 : 1.0 / 4)
self.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame) * scale, CGRectGetHeight(self.frame) * scale)
}
let completion = { (finished: Bool) -> Void in
self.yoyo(!grow)
}
UIView.animateWithDuration(kAnimationDuration, delay: 0, options: options, animations: animations, completion: completion)
}
override func layoutSublayersOfLayer(layer: CALayer) {
super.layoutSublayersOfLayer(layer)
let radius = min(CGRectGetWidth(self.frame), CGRectGetHeight(self.frame)) * 0.25
let center = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
let bezierPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: CGFloat(M_PI * 2), clockwise: true)
self.shape.path = bezierPath.CGPath
}
}
let view = View(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
let container = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
container.addSubview(view)
view.yoyo()
XCPlaygroundPage.currentPage.liveView = container
这是转换为Swift 4.2+:
import UIKit
import PlaygroundSupport
let animDuration = 4.0
class AnimatablePathShape: CAShapeLayer {
override func action(forKey event: String) -> CAAction? {
if event == "path" {
let anim = CABasicAnimation(keyPath: event)
anim.duration = animDuration
anim.fromValue = presentation()?.value(forKey: event) ?? value(forKey: event)
anim.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
return anim
}
return super.action(forKey: event)
}
override class func needsDisplay(forKey key: String) -> Bool {
if key == "path" {
return true
}
return super.needsDisplay(forKey: key)
}
}
class View: UIView {
let shape = AnimatablePathShape()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white.withAlphaComponent(0.1)
shape.fillColor = UIColor.magenta.cgColor
layer.addSublayer(shape)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func yoyo(grow: Bool = true) {
UIView.animate(withDuration: animDuration, delay: 0, options: .curveEaseInOut, animations: {
let scale = CGFloat(grow ? 4 : 1 / 4)
self.frame = CGRect(x: 0, y: 0, width: self.frame.width * scale, height: self.frame.height * scale)
}) { finished in
if finished {
self.yoyo(grow: !grow)
}
}
}
override func layoutSublayers(of layer: CALayer) {
super.layoutSublayers(of: layer)
let radius = min(frame.width, frame.height) * 0.25
let center = CGPoint(x: frame.midX, y: frame.midY)
let bezierPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: .pi * 2, clockwise: true)
shape.path = bezierPath.cgPath
}
}
let view = View(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
let container = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
container.addSubview(view)
view.yoyo()
PlaygroundPage.current.liveView = container
实际上原始代码很好,只是它永远不会使用键 @"bounds"
获取正在进行的动画,而是应该使用键 @"bounds.size"
.
因此,我将通过完成以下所有工作来重构代码:- (void) layoutSublayersOfLayer:(CALayer *)layer
那就是 — 假设徽标只是一个名为 _logoLayer
的 CAShapeLayer
内部 属性 并且您还制作了一个内部方法 returns 正确的 CGPath
用于在给定矩形内绘制徽标:
- (void) layoutSublayersOfLayer:(CALayer *)layer {
if layer == self.layer {
CGPath *newPath = [self _logoPathInRect: self.bounds]
CAAnimation *anim = [self.layer animationForKey:@"bounds.size"];
[CATransaction begin];
if(anim) {
// animating, apply same duration and timing function.
[CATransaction setAnimationDuration:anim.duration];
[CATransaction setAnimationTimingFunction:anim.timingFunction];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
[self.layer addAnimation:pathAnimation forKey:@"path"];
}
else {
// not animating, we should disable implicit animations.
[CATransaction disableActions];
}
self._logoLayer.path = newPath
self._logoLayer.frame = self.layer.bounds;
[CATransaction commit];
}
}
我有一个 class 有 16 个子层来形成一个标志。当我为 UIView
设置动画时,CALayers
没有设置动画,而是进入最终状态,如下面的动画 gif 所示:
我的代码是:
@implementation LogoView
#define CGRECTMAKE(a, b, w, h) {.origin={.x=(a),.y=(b)},.size={.width=(w),.height=(h)}}
#pragma mark - Create Subviews
const static CGRect path[] = {
CGRECTMAKE(62.734375,-21.675000,18.900000,18.900000),
CGRECTMAKE(29.784375,-31.725000,27.400000,27.300000),
CGRECTMAKE(2.534375,-81.775000,18.900000,18.900000),
CGRECTMAKE(4.384375,-57.225000,27.400000,27.300000),
CGRECTMAKE(2.784375,62.875000,18.900000,18.900000),
CGRECTMAKE(4.334375,29.925000,27.400000,27.300000),
CGRECTMAKE(62.734375,2.525000,18.900000,18.900000),
CGRECTMAKE(29.784375,4.475000,27.400000,27.300000),
CGRECTMAKE(-21.665625,-81.775000,18.900000,18.900000),
CGRECTMAKE(-31.765625,-57.225000,27.400000,27.300000),
CGRECTMAKE(-81.615625,-21.425000,18.900000,18.900000),
CGRECTMAKE(-57.215625,-31.775000,27.400000,27.300000),
CGRECTMAKE(-81.615625,2.775000,18.900000,18.900000),
CGRECTMAKE(-57.215625,4.425000,27.400000,27.300000),
CGRECTMAKE(-21.415625,62.875000,18.900000,18.900000),
CGRECTMAKE(-31.765625,29.925000,27.400000,27.300000)
};
- (void) createSubviews
{
self.contentMode = UIViewContentModeRedraw;
for (int i = 0; i < 16; i++) {
CGRect rect = CGRectApplyAffineTransform(path[i],
CGAffineTransformMakeScale(self.frame.size.width / 213.0,
self.frame.size.height / 213.0));
UIBezierPath * b = [UIBezierPath bezierPathWithOvalInRect:
CGRectOffset(rect,
self.frame.size.width/2.0,
self.frame.size.height/2)];
CAShapeLayer * layer = [[CAShapeLayer alloc] init];
layer.path = [b CGPath];
layer.fillColor = [self.tintColor CGColor];
[self.layer addSublayer:layer];
}
self.layer.needsDisplayOnBoundsChange = YES;
self.initialLenght = self.frame.size.width;
}
- (void) layoutSublayersOfLayer:(CALayer *)layer
{
for (int i = 0; i < 16; i++) {
CGRect rect = CGRectApplyAffineTransform(path[i],
CGAffineTransformMakeScale(layer.frame.size.width / 213.0,
layer.frame.size.height / 213.0));
UIBezierPath * b = [UIBezierPath
bezierPathWithOvalInRect:CGRectOffset(rect,
layer.frame.size.width/2.0,
layer.frame.size.height/2)];
((CAShapeLayer*)(layer.sublayers[i])).path = b.CGPath;
}
}
- (void)layoutSubviews {
[super layoutSubviews];
// get current animation for bounds
CAAnimation *anim = [self.layer animationForKey:@"bounds"];
[CATransaction begin];
if(anim) {
// animating, apply same duration and timing function.
[CATransaction setAnimationDuration:anim.duration];
[CATransaction setAnimationTimingFunction:anim.timingFunction];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
[self.layer addAnimation:pathAnimation forKey:@"path"];
}
else {
// not animating, we should disable implicit animations.
[CATransaction disableActions];
}
self.layer.frame = self.frame;
[CATransaction commit];
}
我正在制作动画:
[UIView animateWithDuration:3.0 animations:^{
[v setFrame:CGRectMake(0.0, 0.0, 300.0, 300.0)];
}];
如何同步图层动画和视图动画?
为了让图层正确地动画化,您可能需要为您的点子类化 CALayer
(或 CAShapeLayer
)并实现以下方法:
+ (BOOL)needsDisplayForKey:(NSString *)key;
- (instancetype)initWithLayer:(id)layer;
- (void)drawInContext:(CGContextRef)context;
- (id<CAAction>)actionForKey:(NSString *)event;
您可能还需要将任何图层属性标记为 @dynamic
。
编辑:另一种(可能更简单)的方法是使用 CABasicAnimation
而不是使用 UIView
的 animateWithDuration:animations:
方法。
与UI视图动画同步的动画层属性可能很棘手。相反,让我们使用不同的视图结构来利用内置支持对视图的 transform
进行动画处理,而不是尝试对层的 path
.
我们将使用两个视图:一个父视图和一个子视图。超级视图是一个 LogoView
并且是我们在故事板中布局的内容(或者您创建 UI)。 LogoView
为 class LogoLayerView
自身添加了一个子视图。此 LogoLayerView
使用 CAShapeLayer
作为图层而不是普通的 CALayer
.
请注意,我们只需要一个 CAShapeLayer
,因为一条路径可以包含多个断开连接的区域。
我们将 LogoLayerView
的 frame/bounds 设置为 CGRectMake(0, 0, 213, 213)
一次,并且永远不会更改它。相反,当外部 LogoView
改变大小时,我们设置 LogoLayerView
的 transform
以便它仍然填充外部 LogoView
.
结果如下:
代码如下:
LogoView.h
#import <UIKit/UIKit.h>
IB_DESIGNABLE
@interface LogoView : UIView
@end
LogoView.m
#import "LogoView.h"
#define CGRECTMAKE(a, b, w, h) {.origin={.x=(a),.y=(b)},.size={.width=(w),.height=(h)}}
const static CGRect ovalRects[] = {
CGRECTMAKE(62.734375,-21.675000,18.900000,18.900000),
CGRECTMAKE(29.784375,-31.725000,27.400000,27.300000),
CGRECTMAKE(2.534375,-81.775000,18.900000,18.900000),
CGRECTMAKE(4.384375,-57.225000,27.400000,27.300000),
CGRECTMAKE(2.784375,62.875000,18.900000,18.900000),
CGRECTMAKE(4.334375,29.925000,27.400000,27.300000),
CGRECTMAKE(62.734375,2.525000,18.900000,18.900000),
CGRECTMAKE(29.784375,4.475000,27.400000,27.300000),
CGRECTMAKE(-21.665625,-81.775000,18.900000,18.900000),
CGRECTMAKE(-31.765625,-57.225000,27.400000,27.300000),
CGRECTMAKE(-81.615625,-21.425000,18.900000,18.900000),
CGRECTMAKE(-57.215625,-31.775000,27.400000,27.300000),
CGRECTMAKE(-81.615625,2.775000,18.900000,18.900000),
CGRECTMAKE(-57.215625,4.425000,27.400000,27.300000),
CGRECTMAKE(-21.415625,62.875000,18.900000,18.900000),
CGRECTMAKE(-31.765625,29.925000,27.400000,27.300000)
};
#define LogoDimension 213.0
@interface LogoLayerView : UIView
@property (nonatomic, strong, readonly) CAShapeLayer *layer;
@end
@implementation LogoLayerView
@dynamic layer;
+ (Class)layerClass {
return [CAShapeLayer class];
}
- (void)layoutSubviews {
[super layoutSubviews];
if (self.layer.path == nil) {
[self initShapeLayer];
}
}
- (void)initShapeLayer {
self.layer.backgroundColor = [UIColor yellowColor].CGColor;
self.layer.strokeColor = nil;
self.layer.fillColor = [UIColor greenColor].CGColor;
UIBezierPath *path = [UIBezierPath bezierPath];
for (size_t i = 0; i < sizeof ovalRects / sizeof *ovalRects; ++i) {
[path appendPath:[UIBezierPath bezierPathWithOvalInRect:ovalRects[i]]];
}
[path applyTransform:CGAffineTransformMakeTranslation(LogoDimension / 2, LogoDimension / 2)];
self.layer.path = path.CGPath;
}
@end
@implementation LogoView {
LogoLayerView *layerView;
}
- (void)layoutSubviews {
[super layoutSubviews];
if (layerView == nil) {
layerView = [[LogoLayerView alloc] init];
layerView.layer.anchorPoint = CGPointZero;
layerView.frame = CGRectMake(0, 0, LogoDimension, LogoDimension);
[self addSubview:layerView];
}
[self layoutShapeLayer];
}
- (void)layoutShapeLayer {
CGSize mySize = self.bounds.size;
layerView.transform = CGAffineTransformMakeScale(mySize.width / LogoDimension, mySize.height / LogoDimension);
}
@end
您可以使 CAShapeLayer.path
可动画并在 -layoutSublayersOfLayer
中更新您的自定义层。只需确保匹配 UIView
和 CAShapeLayer
子类的持续时间。
简单明了:
//: Playground - noun: a place where people can play
import UIKit
import XCPlayground
let kAnimationDuration: NSTimeInterval = 4.0
class AnimatablePathShape: CAShapeLayer {
override func actionForKey(event: String) -> CAAction? {
if event == "path" {
let value = self.presentationLayer()?.valueForKey(event) ?? self.valueForKey(event)
let anim = CABasicAnimation(keyPath: event)
anim.duration = kAnimationDuration
anim.fromValue = value
anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
return anim
}
return super.actionForKey(event)
}
override class func needsDisplayForKey(key: String) -> Bool {
if key == "path" {
return true
}
return super.needsDisplayForKey(key)
}
}
class View: UIView {
let shape = AnimatablePathShape()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.whiteColor().colorWithAlphaComponent(0.1)
self.shape.fillColor = UIColor.magentaColor().CGColor
self.layer.addSublayer(self.shape)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func yoyo(grow: Bool = true) {
let options: UIViewAnimationOptions = [.CurveEaseInOut]
let animations = { () -> Void in
let scale: CGFloat = CGFloat(grow ? 4 : 1.0 / 4)
self.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame) * scale, CGRectGetHeight(self.frame) * scale)
}
let completion = { (finished: Bool) -> Void in
self.yoyo(!grow)
}
UIView.animateWithDuration(kAnimationDuration, delay: 0, options: options, animations: animations, completion: completion)
}
override func layoutSublayersOfLayer(layer: CALayer) {
super.layoutSublayersOfLayer(layer)
let radius = min(CGRectGetWidth(self.frame), CGRectGetHeight(self.frame)) * 0.25
let center = CGPoint(x: CGRectGetMidX(self.frame), y: CGRectGetMidY(self.frame))
let bezierPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: CGFloat(M_PI * 2), clockwise: true)
self.shape.path = bezierPath.CGPath
}
}
let view = View(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
let container = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
container.addSubview(view)
view.yoyo()
XCPlaygroundPage.currentPage.liveView = container
这是
import UIKit
import PlaygroundSupport
let animDuration = 4.0
class AnimatablePathShape: CAShapeLayer {
override func action(forKey event: String) -> CAAction? {
if event == "path" {
let anim = CABasicAnimation(keyPath: event)
anim.duration = animDuration
anim.fromValue = presentation()?.value(forKey: event) ?? value(forKey: event)
anim.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
return anim
}
return super.action(forKey: event)
}
override class func needsDisplay(forKey key: String) -> Bool {
if key == "path" {
return true
}
return super.needsDisplay(forKey: key)
}
}
class View: UIView {
let shape = AnimatablePathShape()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.white.withAlphaComponent(0.1)
shape.fillColor = UIColor.magenta.cgColor
layer.addSublayer(shape)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func yoyo(grow: Bool = true) {
UIView.animate(withDuration: animDuration, delay: 0, options: .curveEaseInOut, animations: {
let scale = CGFloat(grow ? 4 : 1 / 4)
self.frame = CGRect(x: 0, y: 0, width: self.frame.width * scale, height: self.frame.height * scale)
}) { finished in
if finished {
self.yoyo(grow: !grow)
}
}
}
override func layoutSublayers(of layer: CALayer) {
super.layoutSublayers(of: layer)
let radius = min(frame.width, frame.height) * 0.25
let center = CGPoint(x: frame.midX, y: frame.midY)
let bezierPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: .pi * 2, clockwise: true)
shape.path = bezierPath.cgPath
}
}
let view = View(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
let container = UIView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
container.addSubview(view)
view.yoyo()
PlaygroundPage.current.liveView = container
实际上原始代码很好,只是它永远不会使用键 @"bounds"
获取正在进行的动画,而是应该使用键 @"bounds.size"
.
因此,我将通过完成以下所有工作来重构代码:- (void) layoutSublayersOfLayer:(CALayer *)layer
那就是 — 假设徽标只是一个名为 _logoLayer
的 CAShapeLayer
内部 属性 并且您还制作了一个内部方法 returns 正确的 CGPath
用于在给定矩形内绘制徽标:
- (void) layoutSublayersOfLayer:(CALayer *)layer {
if layer == self.layer {
CGPath *newPath = [self _logoPathInRect: self.bounds]
CAAnimation *anim = [self.layer animationForKey:@"bounds.size"];
[CATransaction begin];
if(anim) {
// animating, apply same duration and timing function.
[CATransaction setAnimationDuration:anim.duration];
[CATransaction setAnimationTimingFunction:anim.timingFunction];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
[self.layer addAnimation:pathAnimation forKey:@"path"];
}
else {
// not animating, we should disable implicit animations.
[CATransaction disableActions];
}
self._logoLayer.path = newPath
self._logoLayer.frame = self.layer.bounds;
[CATransaction commit];
}
}