中断和反转 CAKeyframeAnimation
Interrupt and reverse CAKeyframeAnimation
我有一个可以选中或取消选中的复选框视图。我想为复选标记的绘图设置动画,为此,我正在使用 CAKeyframeAnimation 并在 CAShapeLayer 上绘图。
这很好用,但我也希望能够支持在抽签中撤销检查决定。现在动画的持续时间配置为一秒。因此,如果一个人点击视图并开始绘制复选标记,然后在 0.5 秒的时间点击视图,那么我希望动画停止绘制并开始反转。同样,如果复选标记未被选中并且有人点击它,那么我希望它反转其清除动画并再次开始绘制复选标记。
我只是不确定该怎么做。我不知道使用 CAKeyframeAnimation 是否可行,或者我是否应该使用 UIViewPropertyAnimator 或其他东西,或者我是否可以使用 UIViewPropertyAnimator,因为它是一个视图动画器,我正在 CAShapeLayer 上制作 属性 动画。而且我将复选标记分解为三个部分(一个起始点,复选标记的第一个向下部分,以及整个复选标记),所以我不确定如何使用 UIViewPropertyAnimator 为它制作动画(也许链接动画,但是那么这似乎会使反转动画变得困难。
这是我的代码。有没有人对如何使这个可中断和可逆有任何想法?
MyCheckmarkView.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface MyCheckmarkView : UIView<CAAnimationDelegate>
@end
NS_ASSUME_NONNULL_END
MyCheckmarkView.m
#import "MyCheckmarkView.h"
@interface MyCheckmarkView ()
@property (strong, nonatomic) UIColor *strokeColor;
@property (assign, nonatomic, getter=isChecked) BOOL checked;
@property (strong, nonatomic) CAShapeLayer *contentLayer;
@end
@implementation MyCheckmarkView
+ (Class)layerClass {
return [CAShapeLayer class];
}
- (instancetype)init {
return [self initWithFrame:CGRectMake(0, 0, 100, 100)];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
[self initialize];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self initialize];
}
return self;
}
- (void)initialize {
self->_strokeColor = [UIColor colorWithRed: 0.2 green: 0.6 blue: 1 alpha: 1];
CAShapeLayer *backgroundLayer = (CAShapeLayer *)self.layer;
backgroundLayer.fillColor = nil;
backgroundLayer.strokeColor = self.strokeColor.CGColor;
backgroundLayer.lineWidth = 7.88;
backgroundLayer.miterLimit = 7.88;
backgroundLayer.lineCap = kCALineCapRound;
backgroundLayer.lineJoin = kCALineJoinRound;
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(4.95, 4.92, 90, 90) cornerRadius: 22.6];
backgroundLayer.path = rectanglePath.CGPath;
[backgroundLayer addSublayer:self.contentLayer];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleCheckedState)];
[self addGestureRecognizer:tapGestureRecognizer];
}
- (CGSize)intrinsicContentSize {
return CGSizeMake(100, 100);
}
- (void)toggleCheckedState {
if (self.checked) {
[self animateToUncheckedState];
} else {
[self animateToCheckedState];
}
self.checked = ![self isChecked];
}
- (CAShapeLayer *)contentLayer {
if (!self->_contentLayer) {
self->_contentLayer = [[CAShapeLayer alloc] init];
self->_contentLayer.fillColor = nil;
self->_contentLayer.strokeColor = self.strokeColor.CGColor;
self->_contentLayer.lineWidth = 7.88;
self->_contentLayer.miterLimit = 7.88;
self->_contentLayer.lineCap = kCALineCapRound;
self->_contentLayer.lineJoin = kCALineJoinRound;
}
return self->_contentLayer;
}
- (void)animateToCheckedState {
UIBezierPath *initialPath = [UIBezierPath bezierPath];
[initialPath moveToPoint:CGPointMake(25.94, 48.05)];
[initialPath addLineToPoint:CGPointMake(25.94, 48.05)];
UIBezierPath *startPath = [UIBezierPath bezierPath];
[startPath moveToPoint:CGPointMake(25.94, 48.05)];
[startPath addLineToPoint: CGPointMake(43.81, 65.34)];
UIBezierPath* checkmarkPath = [UIBezierPath bezierPath];
[checkmarkPath moveToPoint: CGPointMake(25.94, 48.05)];
[checkmarkPath addLineToPoint: CGPointMake(43.81, 65.34)];
[checkmarkPath addLineToPoint: CGPointMake(73.94, 34.53)];
UIViewPropertyAnimator *animator = [[UIViewPropertyAnimator alloc] init];
[animator addAnimations:^{}];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"path"];
animation.duration = 1;
animation.values = @[
(id)initialPath.CGPath,
(id)startPath.CGPath,
(id)checkmarkPath.CGPath
];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fillMode = kCAFillModeBoth;
animation.repeatCount = 0;
animation.autoreverses = NO;
animation.removedOnCompletion = YES;
[self.contentLayer addAnimation:animation forKey:@"checkmarkAnimation"];
self.contentLayer.path = checkmarkPath.CGPath;
}
- (void)animateToUncheckedState {
UIBezierPath *initialPath = [UIBezierPath bezierPath];
[initialPath moveToPoint:CGPointMake(25.94, 48.05)];
[initialPath addLineToPoint:CGPointMake(25.94, 48.05)];
UIBezierPath *startPath = [UIBezierPath bezierPath];
[startPath moveToPoint:CGPointMake(25.94, 48.05)];
[startPath addLineToPoint: CGPointMake(43.81, 65.34)];
UIBezierPath* checkmarkPath = [UIBezierPath bezierPath];
[checkmarkPath moveToPoint: CGPointMake(25.94, 48.05)];
[checkmarkPath addLineToPoint: CGPointMake(43.81, 65.34)];
[checkmarkPath addLineToPoint: CGPointMake(73.94, 34.53)];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"path"];
animation.duration = 1;
animation.values = @[
(id)checkmarkPath.CGPath,
(id)startPath.CGPath,
(id)initialPath.CGPath
];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fillMode = kCAFillModeBoth;
animation.repeatCount = 0;
animation.autoreverses = NO;
animation.removedOnCompletion = YES;
[self.contentLayer addAnimation:animation forKey:@"checkmarkAnimation"];
self.contentLayer.path = nil;
}
@end
更新
这是我根据@DonMag 的代码更新的动画部分代码。一切正常,除了动画开始时,它似乎首先直接将图层的 strokeEnd 值设置为所需的结束值,然后开始动画。这可能是因为我在方法的最后设置了 strokeEnd 属性,但这样在动画被移除并且 presentationLayer 更新为原始内容层中的值后该值将被保留 属性.
我也尝试过使用 CATransaction 然后在 completionBlock 中设置它,但这只是产生了相反的效果。动画开始并成功完成,但随后动画被删除并短暂显示旧状态然后(出于某种原因)快速动画即使我没有做任何明确动画 属性 (我想这可能是隐式动画 属性?).
但我认为无论如何我都不必使用 CATransaction,因为我将动画添加到层,然后在表示层显示动画时更新 属性。这是不正确的吗?有更好的方法吗?如果可能的话,我希望动画能够被移除,并且原始图层在动画被移除后显示正确的状态。
这是我针对选中和未选中状态的代码。
选中状态的动画
NSTimeInterval animationDuration = 3.0;
// get current strokeEnd value
double f = self.contentLayer.presentationLayer.strokeEnd;
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
// animate strokeEnd from current to 1.0
[anim setFromValue:[NSNumber numberWithDouble:f]];
[anim setToValue:[NSNumber numberWithDouble:1.0]];
[anim setDuration:((1.0 - f) * animationDuration)];
[anim setRemovedOnCompletion:YES];
// start animation
[self.contentLayer addAnimation:anim forKey:@"draw"];
// if checkMark was being "un-drawn"
// remove that animation
[self.contentLayer removeAnimationForKey:@"undraw"];
// update the original "model" layer so that when the animation is
// finished, the updates will persist to the layer
self.contentLayer.strokeEnd = 1.0;
未选中状态的动画
NSTimeInterval animationDuration = 3.0;
// get current strokeEnd value
double f = self.contentLayer.presentationLayer.strokeEnd;
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
// animate strokeEnd from current to 0.0
[anim setFromValue:[NSNumber numberWithDouble:f]];
[anim setToValue:[NSNumber numberWithDouble:0.0]];
[anim setDuration:(f * animationDuration)];
[anim setRemovedOnCompletion:YES];
// start animation
[self.contentLayer addAnimation:anim forKey:@"undraw"];
// if checkMark was being "drawn"
// remove that animation
[self.contentLayer removeAnimationForKey:@"draw"];
// persist the changes to the original layer
self.contentLayer.strokeEnd = 0.0;
首先,我建议您为复选标记使用单一路径:
[checkmarkPath moveToPoint:CGPointMake(25.94, 48.05)];
[checkmarkPath addLineToPoint: CGPointMake(43.81, 65.34)];
[checkmarkPath addLineToPoint: CGPointMake(73.94, 34.53)];
然后我们可以使用 strokeEnd
属性 从 0.0 到 1.0“绘制”它,或者从 1.0 到 0.0“取消绘制”它
接下来,要中断和反转它,我们可以从 .presentationLayer
中获取当前的 .strokeEnd
值并将其用作我们动画的 from
值。
这里是对您的 class 的修改(不更改头文件):
#import "MyCheckmarkView.h"
@interface MyCheckmarkView ()
@property (strong, nonatomic) UIColor *strokeColor;
@property (assign, nonatomic, getter=isChecked) BOOL checked;
@property (strong, nonatomic) CAShapeLayer *contentLayer;
@end
@implementation MyCheckmarkView
+ (Class)layerClass {
return [CAShapeLayer class];
}
- (instancetype)init {
return [self initWithFrame:CGRectMake(0, 0, 100, 100)];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
[self initialize];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self initialize];
}
return self;
}
- (void)initialize {
self->_strokeColor = [UIColor colorWithRed: 0.2 green: 0.6 blue: 1 alpha: 1];
CAShapeLayer *backgroundLayer = (CAShapeLayer *)self.layer;
backgroundLayer.fillColor = nil;
backgroundLayer.strokeColor = self.strokeColor.CGColor;
backgroundLayer.lineWidth = 7.88;
backgroundLayer.miterLimit = 7.88;
backgroundLayer.lineCap = kCALineCapRound;
backgroundLayer.lineJoin = kCALineJoinRound;
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(4.95, 4.92, 90, 90) cornerRadius: 22.6];
backgroundLayer.path = rectanglePath.CGPath;
[backgroundLayer addSublayer:self.contentLayer];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleCheckedState)];
[self addGestureRecognizer:tapGestureRecognizer];
}
- (CGSize)intrinsicContentSize {
return CGSizeMake(100, 100);
}
- (void)toggleCheckedState {
if (self.checked) {
[self animateToUncheckedState];
} else {
[self animateToCheckedState];
}
self.checked = ![self isChecked];
}
- (void)layoutSubviews {
[super layoutSubviews];
// single path for checkmark shape
UIBezierPath *checkmarkPath = [UIBezierPath bezierPath];
[checkmarkPath moveToPoint:CGPointMake(25.94, 48.05)];
[checkmarkPath addLineToPoint: CGPointMake(43.81, 65.34)];
[checkmarkPath addLineToPoint: CGPointMake(73.94, 34.53)];
_contentLayer.path = [checkmarkPath CGPath];
// start with strokeEnd at Zero if not checked
_contentLayer.strokeEnd = self.checked ? 1.0 : 0.0;
}
- (void)animateToCheckedState {
// get current strokeEnd value
double f = _contentLayer.presentationLayer.strokeEnd;
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
// animate strokeEnd from current to 1.0
[anim setFromValue:[NSNumber numberWithDouble:f]];
[anim setToValue:[NSNumber numberWithDouble:1.0]];
[anim setDuration:1.0];
// we're "showing" the checkMark,
// so leave it when "finished"
[anim setRemovedOnCompletion:NO];
[anim setFillMode:kCAFillModeBoth];
// start animation
[self.contentLayer addAnimation:anim forKey:@"draw"];
// if checkMark was being "un-drawn"
// remove that animation
[self.contentLayer removeAnimationForKey:@"undraw"];
}
- (void)animateToUncheckedState {
// get current strokeEnd value
double f = _contentLayer.presentationLayer.strokeEnd;
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
// animate strokeEnd from current to 0.0
[anim setFromValue:[NSNumber numberWithDouble:f]];
[anim setToValue:[NSNumber numberWithDouble:0.0]];
[anim setDuration:1.0];
// we're "un-drawing" the checkMark,
// so remove it when "finished"
[anim setRemovedOnCompletion:YES];
[anim setFillMode:kCAFillModeBoth];
// start animation
[self.contentLayer addAnimation:anim forKey:@"undraw"];
// if checkMark was being "drawn"
// remove that animation
[self.contentLayer removeAnimationForKey:@"draw"];
}
- (CAShapeLayer *)contentLayer {
if (!self->_contentLayer) {
self->_contentLayer = [[CAShapeLayer alloc] init];
self->_contentLayer.fillColor = nil;
self->_contentLayer.strokeColor = self.strokeColor.CGColor;
self->_contentLayer.lineWidth = 7.88;
self->_contentLayer.miterLimit = 7.88;
self->_contentLayer.lineCap = kCALineCapRound;
self->_contentLayer.lineJoin = kCALineJoinRound;
}
return self->_contentLayer;
}
@end
我有一个可以选中或取消选中的复选框视图。我想为复选标记的绘图设置动画,为此,我正在使用 CAKeyframeAnimation 并在 CAShapeLayer 上绘图。
这很好用,但我也希望能够支持在抽签中撤销检查决定。现在动画的持续时间配置为一秒。因此,如果一个人点击视图并开始绘制复选标记,然后在 0.5 秒的时间点击视图,那么我希望动画停止绘制并开始反转。同样,如果复选标记未被选中并且有人点击它,那么我希望它反转其清除动画并再次开始绘制复选标记。
我只是不确定该怎么做。我不知道使用 CAKeyframeAnimation 是否可行,或者我是否应该使用 UIViewPropertyAnimator 或其他东西,或者我是否可以使用 UIViewPropertyAnimator,因为它是一个视图动画器,我正在 CAShapeLayer 上制作 属性 动画。而且我将复选标记分解为三个部分(一个起始点,复选标记的第一个向下部分,以及整个复选标记),所以我不确定如何使用 UIViewPropertyAnimator 为它制作动画(也许链接动画,但是那么这似乎会使反转动画变得困难。
这是我的代码。有没有人对如何使这个可中断和可逆有任何想法?
MyCheckmarkView.h
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface MyCheckmarkView : UIView<CAAnimationDelegate>
@end
NS_ASSUME_NONNULL_END
MyCheckmarkView.m
#import "MyCheckmarkView.h"
@interface MyCheckmarkView ()
@property (strong, nonatomic) UIColor *strokeColor;
@property (assign, nonatomic, getter=isChecked) BOOL checked;
@property (strong, nonatomic) CAShapeLayer *contentLayer;
@end
@implementation MyCheckmarkView
+ (Class)layerClass {
return [CAShapeLayer class];
}
- (instancetype)init {
return [self initWithFrame:CGRectMake(0, 0, 100, 100)];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
[self initialize];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self initialize];
}
return self;
}
- (void)initialize {
self->_strokeColor = [UIColor colorWithRed: 0.2 green: 0.6 blue: 1 alpha: 1];
CAShapeLayer *backgroundLayer = (CAShapeLayer *)self.layer;
backgroundLayer.fillColor = nil;
backgroundLayer.strokeColor = self.strokeColor.CGColor;
backgroundLayer.lineWidth = 7.88;
backgroundLayer.miterLimit = 7.88;
backgroundLayer.lineCap = kCALineCapRound;
backgroundLayer.lineJoin = kCALineJoinRound;
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(4.95, 4.92, 90, 90) cornerRadius: 22.6];
backgroundLayer.path = rectanglePath.CGPath;
[backgroundLayer addSublayer:self.contentLayer];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleCheckedState)];
[self addGestureRecognizer:tapGestureRecognizer];
}
- (CGSize)intrinsicContentSize {
return CGSizeMake(100, 100);
}
- (void)toggleCheckedState {
if (self.checked) {
[self animateToUncheckedState];
} else {
[self animateToCheckedState];
}
self.checked = ![self isChecked];
}
- (CAShapeLayer *)contentLayer {
if (!self->_contentLayer) {
self->_contentLayer = [[CAShapeLayer alloc] init];
self->_contentLayer.fillColor = nil;
self->_contentLayer.strokeColor = self.strokeColor.CGColor;
self->_contentLayer.lineWidth = 7.88;
self->_contentLayer.miterLimit = 7.88;
self->_contentLayer.lineCap = kCALineCapRound;
self->_contentLayer.lineJoin = kCALineJoinRound;
}
return self->_contentLayer;
}
- (void)animateToCheckedState {
UIBezierPath *initialPath = [UIBezierPath bezierPath];
[initialPath moveToPoint:CGPointMake(25.94, 48.05)];
[initialPath addLineToPoint:CGPointMake(25.94, 48.05)];
UIBezierPath *startPath = [UIBezierPath bezierPath];
[startPath moveToPoint:CGPointMake(25.94, 48.05)];
[startPath addLineToPoint: CGPointMake(43.81, 65.34)];
UIBezierPath* checkmarkPath = [UIBezierPath bezierPath];
[checkmarkPath moveToPoint: CGPointMake(25.94, 48.05)];
[checkmarkPath addLineToPoint: CGPointMake(43.81, 65.34)];
[checkmarkPath addLineToPoint: CGPointMake(73.94, 34.53)];
UIViewPropertyAnimator *animator = [[UIViewPropertyAnimator alloc] init];
[animator addAnimations:^{}];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"path"];
animation.duration = 1;
animation.values = @[
(id)initialPath.CGPath,
(id)startPath.CGPath,
(id)checkmarkPath.CGPath
];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fillMode = kCAFillModeBoth;
animation.repeatCount = 0;
animation.autoreverses = NO;
animation.removedOnCompletion = YES;
[self.contentLayer addAnimation:animation forKey:@"checkmarkAnimation"];
self.contentLayer.path = checkmarkPath.CGPath;
}
- (void)animateToUncheckedState {
UIBezierPath *initialPath = [UIBezierPath bezierPath];
[initialPath moveToPoint:CGPointMake(25.94, 48.05)];
[initialPath addLineToPoint:CGPointMake(25.94, 48.05)];
UIBezierPath *startPath = [UIBezierPath bezierPath];
[startPath moveToPoint:CGPointMake(25.94, 48.05)];
[startPath addLineToPoint: CGPointMake(43.81, 65.34)];
UIBezierPath* checkmarkPath = [UIBezierPath bezierPath];
[checkmarkPath moveToPoint: CGPointMake(25.94, 48.05)];
[checkmarkPath addLineToPoint: CGPointMake(43.81, 65.34)];
[checkmarkPath addLineToPoint: CGPointMake(73.94, 34.53)];
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"path"];
animation.duration = 1;
animation.values = @[
(id)checkmarkPath.CGPath,
(id)startPath.CGPath,
(id)initialPath.CGPath
];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fillMode = kCAFillModeBoth;
animation.repeatCount = 0;
animation.autoreverses = NO;
animation.removedOnCompletion = YES;
[self.contentLayer addAnimation:animation forKey:@"checkmarkAnimation"];
self.contentLayer.path = nil;
}
@end
更新
这是我根据@DonMag 的代码更新的动画部分代码。一切正常,除了动画开始时,它似乎首先直接将图层的 strokeEnd 值设置为所需的结束值,然后开始动画。这可能是因为我在方法的最后设置了 strokeEnd 属性,但这样在动画被移除并且 presentationLayer 更新为原始内容层中的值后该值将被保留 属性.
我也尝试过使用 CATransaction 然后在 completionBlock 中设置它,但这只是产生了相反的效果。动画开始并成功完成,但随后动画被删除并短暂显示旧状态然后(出于某种原因)快速动画即使我没有做任何明确动画 属性 (我想这可能是隐式动画 属性?).
但我认为无论如何我都不必使用 CATransaction,因为我将动画添加到层,然后在表示层显示动画时更新 属性。这是不正确的吗?有更好的方法吗?如果可能的话,我希望动画能够被移除,并且原始图层在动画被移除后显示正确的状态。
这是我针对选中和未选中状态的代码。
选中状态的动画
NSTimeInterval animationDuration = 3.0;
// get current strokeEnd value
double f = self.contentLayer.presentationLayer.strokeEnd;
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
// animate strokeEnd from current to 1.0
[anim setFromValue:[NSNumber numberWithDouble:f]];
[anim setToValue:[NSNumber numberWithDouble:1.0]];
[anim setDuration:((1.0 - f) * animationDuration)];
[anim setRemovedOnCompletion:YES];
// start animation
[self.contentLayer addAnimation:anim forKey:@"draw"];
// if checkMark was being "un-drawn"
// remove that animation
[self.contentLayer removeAnimationForKey:@"undraw"];
// update the original "model" layer so that when the animation is
// finished, the updates will persist to the layer
self.contentLayer.strokeEnd = 1.0;
未选中状态的动画
NSTimeInterval animationDuration = 3.0;
// get current strokeEnd value
double f = self.contentLayer.presentationLayer.strokeEnd;
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
// animate strokeEnd from current to 0.0
[anim setFromValue:[NSNumber numberWithDouble:f]];
[anim setToValue:[NSNumber numberWithDouble:0.0]];
[anim setDuration:(f * animationDuration)];
[anim setRemovedOnCompletion:YES];
// start animation
[self.contentLayer addAnimation:anim forKey:@"undraw"];
// if checkMark was being "drawn"
// remove that animation
[self.contentLayer removeAnimationForKey:@"draw"];
// persist the changes to the original layer
self.contentLayer.strokeEnd = 0.0;
首先,我建议您为复选标记使用单一路径:
[checkmarkPath moveToPoint:CGPointMake(25.94, 48.05)];
[checkmarkPath addLineToPoint: CGPointMake(43.81, 65.34)];
[checkmarkPath addLineToPoint: CGPointMake(73.94, 34.53)];
然后我们可以使用 strokeEnd
属性 从 0.0 到 1.0“绘制”它,或者从 1.0 到 0.0“取消绘制”它
接下来,要中断和反转它,我们可以从 .presentationLayer
中获取当前的 .strokeEnd
值并将其用作我们动画的 from
值。
这里是对您的 class 的修改(不更改头文件):
#import "MyCheckmarkView.h"
@interface MyCheckmarkView ()
@property (strong, nonatomic) UIColor *strokeColor;
@property (assign, nonatomic, getter=isChecked) BOOL checked;
@property (strong, nonatomic) CAShapeLayer *contentLayer;
@end
@implementation MyCheckmarkView
+ (Class)layerClass {
return [CAShapeLayer class];
}
- (instancetype)init {
return [self initWithFrame:CGRectMake(0, 0, 100, 100)];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
[self initialize];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self initialize];
}
return self;
}
- (void)initialize {
self->_strokeColor = [UIColor colorWithRed: 0.2 green: 0.6 blue: 1 alpha: 1];
CAShapeLayer *backgroundLayer = (CAShapeLayer *)self.layer;
backgroundLayer.fillColor = nil;
backgroundLayer.strokeColor = self.strokeColor.CGColor;
backgroundLayer.lineWidth = 7.88;
backgroundLayer.miterLimit = 7.88;
backgroundLayer.lineCap = kCALineCapRound;
backgroundLayer.lineJoin = kCALineJoinRound;
UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(4.95, 4.92, 90, 90) cornerRadius: 22.6];
backgroundLayer.path = rectanglePath.CGPath;
[backgroundLayer addSublayer:self.contentLayer];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleCheckedState)];
[self addGestureRecognizer:tapGestureRecognizer];
}
- (CGSize)intrinsicContentSize {
return CGSizeMake(100, 100);
}
- (void)toggleCheckedState {
if (self.checked) {
[self animateToUncheckedState];
} else {
[self animateToCheckedState];
}
self.checked = ![self isChecked];
}
- (void)layoutSubviews {
[super layoutSubviews];
// single path for checkmark shape
UIBezierPath *checkmarkPath = [UIBezierPath bezierPath];
[checkmarkPath moveToPoint:CGPointMake(25.94, 48.05)];
[checkmarkPath addLineToPoint: CGPointMake(43.81, 65.34)];
[checkmarkPath addLineToPoint: CGPointMake(73.94, 34.53)];
_contentLayer.path = [checkmarkPath CGPath];
// start with strokeEnd at Zero if not checked
_contentLayer.strokeEnd = self.checked ? 1.0 : 0.0;
}
- (void)animateToCheckedState {
// get current strokeEnd value
double f = _contentLayer.presentationLayer.strokeEnd;
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
// animate strokeEnd from current to 1.0
[anim setFromValue:[NSNumber numberWithDouble:f]];
[anim setToValue:[NSNumber numberWithDouble:1.0]];
[anim setDuration:1.0];
// we're "showing" the checkMark,
// so leave it when "finished"
[anim setRemovedOnCompletion:NO];
[anim setFillMode:kCAFillModeBoth];
// start animation
[self.contentLayer addAnimation:anim forKey:@"draw"];
// if checkMark was being "un-drawn"
// remove that animation
[self.contentLayer removeAnimationForKey:@"undraw"];
}
- (void)animateToUncheckedState {
// get current strokeEnd value
double f = _contentLayer.presentationLayer.strokeEnd;
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
// animate strokeEnd from current to 0.0
[anim setFromValue:[NSNumber numberWithDouble:f]];
[anim setToValue:[NSNumber numberWithDouble:0.0]];
[anim setDuration:1.0];
// we're "un-drawing" the checkMark,
// so remove it when "finished"
[anim setRemovedOnCompletion:YES];
[anim setFillMode:kCAFillModeBoth];
// start animation
[self.contentLayer addAnimation:anim forKey:@"undraw"];
// if checkMark was being "drawn"
// remove that animation
[self.contentLayer removeAnimationForKey:@"draw"];
}
- (CAShapeLayer *)contentLayer {
if (!self->_contentLayer) {
self->_contentLayer = [[CAShapeLayer alloc] init];
self->_contentLayer.fillColor = nil;
self->_contentLayer.strokeColor = self.strokeColor.CGColor;
self->_contentLayer.lineWidth = 7.88;
self->_contentLayer.miterLimit = 7.88;
self->_contentLayer.lineCap = kCALineCapRound;
self->_contentLayer.lineJoin = kCALineJoinRound;
}
return self->_contentLayer;
}
@end