iOS如何实现可中断可反转的CAShapeLayer勾选动画CAKeyframeAnimation
最优实现方案(解决可中断反转需求)
你原来用CAKeyframeAnimation修改path的方案确实不适合做中断反转,建议换用固定完整对勾路径+动画strokeEnd属性的方案,天然满足你的需求:
- 首先在
initialize方法里就给contentLayer设置完整的对勾路径,初始strokeEnd = 0(完全不显示对勾),不需要后续修改path - 选中/取消选中只需要修改
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
相关产品推荐
相关产品推荐

