动画CALayer边框变化

35

我正在为一个 UIView 子类设置边框宽度和颜色,方法如下:

- (void) setViewBorder
{
    self.layer.borderColor = [UIColor greenColor].CGColor;
    self.layer.borderWidth = 3.0f;
}

我的问题是: 我该如何为此添加动画效果?谢谢。


你想要动画化什么?边框外观?还是旋转? - Valentin Shamardin
@ValentinShamardin 边框外观。与更改帧动画相同的效果。 - iOS Dev
4个回答

62

无论是borderColor还是borderWidth都是可以进行动画处理的属性,但由于您是在视图子类中而不是在UIView动画块中执行此操作,因此禁用了隐式动画(自动更改值时发生的动画)。

如果要对这些属性进行动画处理,则可以使用CABasicAnimation进行显式动画。由于您正在对同一层上的两个属性进行动画处理,因此可以将它们都添加到一个动画组中,只需配置一次持续时间、时间控制等即可。请注意,显式动画仅为视觉效果,模型值(实际属性)在添加它们时不会更改。这就是为什么必须同时配置动画和设置模型值的原因。

CABasicAnimation *color = [CABasicAnimation animationWithKeyPath:@"borderColor"];
// animate from red to blue border ...
color.fromValue = (id)[UIColor redColor].CGColor;
color.toValue   = (id)[UIColor blueColor].CGColor;
// ... and change the model value
self.layer.borderColor = [UIColor blueColor].CGColor;

CABasicAnimation *width = [CABasicAnimation animationWithKeyPath:@"borderWidth"];
// animate from 2pt to 4pt wide border ...
width.fromValue = @2;
width.toValue   = @4;
// ... and change the model value
self.layer.borderWidth = 4;

CAAnimationGroup *both = [CAAnimationGroup animation];
// animate both as a group with the duration of 0.5 seconds
both.duration   = 0.5;
both.animations = @[color, width];
// optionally add other configuration (that applies to both animations)
both.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

[self.layer addAnimation:both forKey:@"color and width"];

如果您查看CABasicAnimation文档中“设置插值值”部分,您会发现不必像我一样同时指定toValue和fromValue,因此代码可以稍微缩短。然而,为了清晰易懂(尤其是当您刚开始接触核心动画时),更加明确可能有助于您(和您的同事)理解代码。


由于您正在视图子类中执行此操作,而不是在UIView动画块中,因此禁用了隐式动画(在更改值时自动发生的动画)。 这是否有记录? 我经常禁用图层操作,以前不知道这一点。 - jrturton
@jrturton 在UIView的layer属性文档中,你可以看到视图是代理,并且不应更改代理。 - David Rönnqvist
2
@jrturton 关于actionForLayer:forKey:中视图实际执行的操作,我能找到的最好答案是我的回答,它又引用了这个回答。但是,您可以进行自己的实验,并在动画块内外询问层代理(或直接询问视图)actionForLayer:forKey:,看看您得到的响应是什么。我不记得在哪里正式记录过它。 - David Rönnqvist
那是有用的东西。谢谢。 - jrturton
1
@SwiftArchitect 是的,不知何故那个问题很长时间都没有被注意到。 - David Rönnqvist
显示剩余10条评论

29

以下是对 @David 答案的快速解决方案:

let colorAnimation = CABasicAnimation(keyPath: "borderColor")
colorAnimation.fromValue = UIColor.red.cgColor
colorAnimation.toValue = UIColor.blue.cgColor
view.layer.borderColor = UIColor.blue.cgColor

let widthAnimation = CABasicAnimation(keyPath: "borderWidth")
widthAnimation.fromValue = 2
widthAnimation.toValue = 4
widthAnimation.duration = 4
view.layer.borderWidth = 4

let bothAnimations = CAAnimationGroup()
bothAnimations.duration = 0.5
bothAnimations.animations = [colorAnimation, widthAnimation]
bothAnimations.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)

view.layer.add(bothAnimations, forKey: "color and width")

1
你是指self.view.layer.borderColor而不是'backgroundColor'吗? - John Doe
1
你应该使用 #keyPath(CALayer.borderColor) 代替 "borderColor"。同样的,对于宽度也是如此... - Dean Kelly
@DeanKelly 为什么CALayer.borderColor比"borderColor"更受欢迎? - Eric
1
@Eric 我不完全记得推理的原因。我认为更多的是使用 #keyPath 部分,因为它是编译安全的,如果属性名称发生变化,它将无法编译。 - Dean Kelly

17

如果你只想让边框淡入,你也可以这样做:

UIView.transition(with: self, duration: 0.3, options: .transitionCrossDissolve, animations: {

        self.layer.borderColor = UIColor.green.cgColor
        self.layer.borderWidth = 3

}, completion: nil)

在UIViewPropertyAnimator中也无法使用。 - Jaap Weijland
@JaapWeijland 你需要使用 UIView.transition 而不是 UIViewPropertyAnimator - inf1783
如果我没记错的话,同样的代码应该在两种方法中都能工作。 - Jaap Weijland
@JaapWeijland 这段时间过去了,但实际上我认为我选择了 UIView.transition,因为它与 UIView.animate/UIViewPropertyAnimator 不兼容。也许你可以试试 ;) - inf1783
当在包含UIPickerViews的视图上使用时,这会导致一些跳动/丑陋。 - A. L. Strine

2
你可以创建一个关键帧动画并将其添加到你的视图中。
//Create animation
        CAKeyframeAnimation *colorsAnimation = [CAKeyframeAnimation animationWithKeyPath:@"borderColor"];
        colorsAnimation.values = [NSArray arrayWithObjects: (id)[UIColor greenColor].CGColor,
                                  (id)[UIColor yellowColor].CGColor, nil];
        colorsAnimation.keyTimes = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.25],[NSNumber numberWithFloat:1.0], nil];
        colorsAnimation.calculationMode = kCAAnimationLinear;
        colorsAnimation.removedOnCompletion = NO;
        colorsAnimation.fillMode = kCAFillModeForwards;
        colorsAnimation.duration = 3.0f;

    //Create animation
    CAKeyframeAnimation *sizeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"borderWidth"];
    sizeAnimation.values = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0],
                             [NSNumber numberWithFloat:5.0], nil];
    sizeAnimation.keyTimes = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0],[NSNumber numberWithFloat:3.0], nil];
    sizeAnimation.calculationMode = kCAAnimationLinear;
    sizeAnimation.removedOnCompletion = NO;
    sizeAnimation.fillMode = kCAFillModeForwards;
    sizeAnimation.duration = 3.0f;

    //Add animation
    [yoursubview.layer   addAnimation:colorsAnimation forKey:nil];
    [yoursubview.layer   addAnimation:sizeAnimation forKey:nil];

3
如果您打算在回答中使用 removedOnCompletion = NO,那么您需要向提问者解释其后果。 - David Rönnqvist
1
另外,你为什么要使用只有两个值的关键帧动画呢?当你阅读代码时,这一点并不明显,而且过于复杂了。 - David Rönnqvist

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接