You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS如何实现可中断可反转的CAShapeLayer勾选动画CAKeyframeAnimation

最优实现方案(解决可中断反转需求)

你原来用CAKeyframeAnimation修改path的方案确实不适合做中断反转,建议换用固定完整对勾路径+动画strokeEnd属性的方案,天然满足你的需求:

  1. 首先在initialize方法里就给contentLayer设置完整的对勾路径,初始strokeEnd = 0(完全不显示对勾),不需要后续修改path
  2. 选中/取消选中只需要修改strokeEnd的数值,0是未选中,1是选中,动画过程是连续的,中断时直接取当前的strokeEnd值作为反向动画的起点即可
    这个方案完全可以复现你原来的三段绘制效果:strokeEnd从0到1的过程就是先画对勾下段、再画上段,和你原来的关键帧path效果一致。

解决你更新后遇到的动画跳变问题

你遇到的跳变问题本质是非根Layer的隐式动画冲突:
CAShapeLayer属于自定义子Layer,修改它的可动画属性时,系统默认会自动添加0.25秒的隐式动画,你自己添加的显式动画和隐式动画冲突就会出现跳变。
你之前在CATransaction的completion里设置model层值的方案,会导致动画结束后presentationLayer销毁,此时model层还是旧值,就会先闪一下旧状态,再触发隐式动画,反而出现反效果。

修正方案

只需要在设置model层strokeEnd属性的时候,用CATransaction关闭隐式动画即可,完整的动画代码如下:

切换至选中状态

NSTimeInterval animationDuration = 1.0;
// 安全获取当前strokeEnd值,无动画时直接取model层值
CGFloat currentStroke = self.contentLayer.presentationLayer ? self.contentLayer.presentationLayer.strokeEnd : self.contentLayer.strokeEnd;

// 移除旧动画
[self.contentLayer removeAnimationForKey:@"undraw"];
[self.contentLayer removeAnimationForKey:@"draw"];

// 创建显式动画
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
anim.fromValue = @(currentStroke);
anim.toValue = @(1.0);
anim.duration = (1.0 - currentStroke) * animationDuration;
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
anim.removedOnCompletion = YES;

// 添加动画
[self.contentLayer addAnimation:anim forKey:@"draw"];

// 关闭隐式动画后设置model层最终值
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.contentLayer.strokeEnd = 1.0;
[CATransaction commit];

self.checked = YES;

切换至未选中状态

NSTimeInterval animationDuration = 1.0;
// 安全获取当前strokeEnd值
CGFloat currentStroke = self.contentLayer.presentationLayer ? self.contentLayer.presentationLayer.strokeEnd : self.contentLayer.strokeEnd;

// 移除旧动画
[self.contentLayer removeAnimationForKey:@"draw"];
[self.contentLayer removeAnimationForKey:@"undraw"];

// 创建显式动画
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
anim.fromValue = @(currentStroke);
anim.toValue = @(0.0);
anim.duration = currentStroke * animationDuration;
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
anim.removedOnCompletion = YES;

// 添加动画
[self.contentLayer addAnimation:anim forKey:@"undraw"];

// 关闭隐式动画后设置model层最终值
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.contentLayer.strokeEnd = 0.0;
[CATransaction commit];

self.checked = NO;

补充初始化修改

你需要在initialize方法里给contentLayer提前设置好完整的对勾路径,不需要后续修改:

- (void)initialize {
    // ... 原有代码不变
    // 初始化contentLayer时直接设置完整对勾路径
    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)];
    self.contentLayer.path = checkmarkPath.CGPath;
    self.contentLayer.strokeEnd = 0.0; // 初始状态不显示对勾
}

这个方案完全满足你的需求:动画可随时中断、反转,没有跳变,动画结束后状态自动持久化。

内容的提问来源于stack exchange,提问作者ashipma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 05:24:04