在动画界的动画变化CAShapeLayer路径 [英] Animate CAShapeLayer path on animated bounds change

查看:370
本文介绍了在动画界的动画变化CAShapeLayer路径的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个CAShapeLayer(这是一个UIView子层),其路径应该更新每当视图的边界大小的变化。对于这一点,我已经覆盖层的的setBounds:方法来重置路径:

I have a CAShapeLayer (which is the layer of a UIView subclass) whose path should update whenever the view's bounds size changes. For this, I have overridden the layer's setBounds: method to reset the path:

@interface CustomShapeLayer : CAShapeLayer
@end

@implementation CustomShapeLayer

- (void)setBounds:(CGRect)bounds
{
    [super setBounds:bounds];
    self.path = [[self shapeForBounds:bounds] CGPath];
}

直到我在动画界的变化这工作得很好。我想有路径动画靠任何动画界的变化(在一个UIView动画块),使形状图层始终采用它的大小。

This works fine until I animate the bounds change. I would like to have the path animate alongside any animated bounds change (in a UIView animation block) so that the shape layer always adopts to its size.

由于属性默认不动画的路径,我想出了这一点:

Since the path property does not animate by default, I came up with this:

覆盖层的 addAnimation:forKey:方法。在这里面,找出如果一个动画界被添加到该层。如果是这样,创建第二个明确的动画复制从获取传递给方法的界限动画的所有属性的动画界旁边的路径。在code:

Override the layer's addAnimation:forKey: method. In it, figure out if a bounds animation is being added to the layer. If so, create a second explicit animation for animating the path alongside the bounds by copying all the properties from the bounds animation that gets passed to the method. In code:

- (void)addAnimation:(CAAnimation *)animation forKey:(NSString *)key
{
    [super addAnimation:animation forKey:key];

    if ([animation isKindOfClass:[CABasicAnimation class]]) {
        CABasicAnimation *basicAnimation = (CABasicAnimation *)animation;

        if ([basicAnimation.keyPath isEqualToString:@"bounds.size"]) {
            CABasicAnimation *pathAnimation = [basicAnimation copy];
            pathAnimation.keyPath = @"path";
            // The path property has not been updated to the new value yet
            pathAnimation.fromValue = (id)self.path;
            // Compute the new value for path
            pathAnimation.toValue = (id)[[self shapeForBounds:self.bounds] CGPath];

            [self addAnimation:pathAnimation forKey:@"path"];
        }
    }
}

我有这个想法从阅读大卫Rönnqvist的视图 - 层协同文章。 (这code是适用于iOS 8.在iOS 7,看来你必须检查动画的的keyPath @界限而不是`@bounds.size)。

I got this idea from reading David Rönnqvist's View-Layer Synergy article. (This code is for iOS 8. On iOS 7, it seems that you have to check the animation's keyPath against @"bounds" and not `@"bounds.size".)

调用code触发视图的动画界的变化是这样的:

The calling code that triggers the view's animated bounds change would look like this:

[UIView animateWithDuration:1.0 delay:0.0 usingSpringWithDamping:0.3 initialSpringVelocity:0.0 options:0 animations:^{
    self.customShapeView.bounds = newBounds;
} completion:nil];

问题

这主要是工作,但我有这两个问题:

Questions

This mostly works, but I am having two problems with it:


  1. 偶尔,我得到一个崩溃( EXC_BAD_ACCESS )的 CGPathApply()时触发这个动画同时previous动画仍在进行中。我不知道这是否有什么与我的具体的实施编辑:没关系。我忘了 UIBezierPath 转换为 CGPathRef 感谢@antonioviero 的!

  1. Occasionally, I get a crash on (EXC_BAD_ACCESS) in CGPathApply() when triggering this animation while a previous animation is still in progress. I am not sure whether this has anything to do with my particular implementation. never mind. I forgot to convert UIBezierPath to CGPathRef. Thanks @antonioviero!

在使用标准的UIView春天动画,视图的边界的动画和层的路径是稍微不同步。也就是说,路径动画也执行了弹性的方式,但它并没有跟随视图边界完全一样。

When using a standard UIView spring animation, the animation of the view's bounds and the layer's path is slightly out of sync. That is, the path animation also performs in a springy way, but it does not follow the view's bounds exactly.

更一般地,这是最好的办法吗看来,有一个形状图层的路径是依赖于它的边界大小和同步应该动画与任何界限的变化是什么,应该只是工作™,但我有一个困难时期。我觉得必须有一个更好的办法。

More generally, is this the best approach? It seems that having a shape layer whose path is dependent on its bounds size and which should animate in sync with any bounds changes is something that should just work™ but I'm having a hard time. I feel there must be a better way.


  • 覆盖层的 actionForKey:或视图的 actionForLayer:forKey:,以返回一个自定义动画对象在路径属性。我认为这将是preferred的方式,但我没有找到一种方式来获得的,应该由封闭动画块设置的事务属性。即使从动画块中调用 [CATransaction animationDuration ]等总是返回默认值。

Other things I have tried

  • Override the layer's actionForKey: or the view's actionForLayer:forKey: in order to return a custom animation object for the path property. I think this would be the preferred way but I did not find a way to get at the transaction properties that should be set by the enclosing animation block. Even if called from inside an animation block, [CATransaction animationDuration] etc. always return the default values.
  • 有没有一种方法,以(a)决定,你是目前动画块内,和(b)来获取已在该块设置的动画属性(时间,动画曲线等)<? / STRONG>

    Is there a way to (a) determine that you are currently inside an animation block, and (b) to get the animation properties (duration, animation curve etc.) that have been set in that block?

    下面的动画:橙色三角形状图层的路径。黑色轮廓是承载形状图层视图的框架。

    Here's the animation: The orange triangle is the path of the shape layer. The black outline is the frame of the view hosting the shape layer.

    看一看在GitHub上示例项目。 (该项目是针对iOS 8和需要X code 6跑了,不好意思。)

    Have a look at the sample project on GitHub. (This project is for iOS 8 and requires Xcode 6 to run, sorry.)

    何塞·路易斯·彼德拉伊塔指着我来的本文由尼克·洛克伍德,这表明了以下的方法:

    Jose Luis Piedrahita pointed me to this article by Nick Lockwood, which suggests the following approach:


    1. 重写视图的 actionForLayer:forKey:或图层 actionForKey:方法,并检查重点传递给此方法是要进行动画之一( @路径)。

    1. Override the view's actionForLayer:forKey: or the layer's actionForKey: method and check if the key passed to this method is the one you want to animate (@"path").

    如果是这样,调用超级上@边界图层的正常动画的属性(如之一)将隐式告诉你,如果你是一个动画块内。如果视图(层的委托)返回一个有效的对象(而不是 NSNull ),我们到了。

    If so, calling super on one of the layer's "normal" animatable properties (such as @"bounds") will implicitly tell you if you are inside an animation block. If the view (the layer's delegate) returns a valid object (and not nil or NSNull), we are.

    设置参数(时间,定时功能等)从动画的路径动画从返回 [超级actionForKey:] 键,返回路径动画

    Set the parameters (duration, timing function, etc.) for the path animation from the animation returned from [super actionForKey:] and return the path animation.

    这确实工作下的iOS 7.x的伟大forKey:但是,iOS的8下,将对象从 actionForLayer返回不是一个标准(和记录) CA ...动画但私人 _UIViewAdditiveAnimationAction 类的一个实例(一个 NSObject的子类)。因为此类的属性没有记录,我不能使用它们轻松地创建路径动画。

    This does indeed work great under iOS 7.x. However, under iOS 8, the object returned from actionForLayer:forKey: is not a standard (and documented) CA...Animation but an instance of the private _UIViewAdditiveAnimationAction class (an NSObject subclass). Since the properties of this class are not documented, I can't use them easily to create the path animation.

    _Edit:或者它可能毕竟只是工作。尼克在他的文章中提到,如的backgroundColor 一些属性仍然会返回在iOS 8标准 CA ...动画。我也来试试吧out.`

    _ Or it might just work after all. As Nick mentioned in his article, some properties like backgroundColor still return a standard CA...Animation on iOS 8. I'll have to try it out.`

    推荐答案

    我知道这是一个老问题,但我可以为您提供这似乎类似于Lockwoods先生方式的解决方案。可悲的是源$ C ​​$这里c是迅速的,所以你需要将其转换为ObjC。

    I know this is an old question but I can provide you a solution which appears similar to Mr Lockwoods approach. Sadly the source code here is swift so you will need to convert it to ObjC.

    仿佛层是可以拦截CAAction在视图本身景色的衬里层前面提到的。如果衬里层在多于一个视图中使用这不过是不方便的例子。

    As mentioned before if the layer is a backing layer for a view you can intercept the CAAction's in the view itself. This however isn't convenient for example if the backing layer is used in more then one view.

    好消息是actionForLayer:forKey:实际调用actionForKey:背层

    The good news is actionForLayer:forKey: actually calls actionForKey: in the backing layer.

    它在actionForKey:在背层,我们可以拦截这些调用提供时的路径改变为动画

    It's in the actionForKey: in the backing layer where we can intercept these calls and provide an animation for when the path is changed.

    写入迅速一个例子层是如下:

    An example layer written in swift is as follows:

    class AnimatedBackingLayer: CAShapeLayer
    {
    
        override var bounds: CGRect
        {
            didSet
            {
                if !CGRectIsEmpty(bounds)
                {
                    path = UIBezierPath(roundedRect: CGRectInset(bounds, 10, 10), cornerRadius: 5).CGPath
                }
            }
        }
    
        override func actionForKey(event: String) -> CAAction?
        {
            if event == "path"
            {
                if let action = super.actionForKey("backgroundColor") as? CABasicAnimation
                {
                    let animation = CABasicAnimation(keyPath: event)
                    animation.fromValue = path
                    // Copy values from existing action
                    animation.autoreverses = action.autoreverses
                    animation.beginTime = action.beginTime
                    animation.delegate = action.delegate
                    animation.duration = action.duration
                    animation.fillMode = action.fillMode
                    animation.repeatCount = action.repeatCount
                    animation.repeatDuration = action.repeatDuration
                    animation.speed = action.speed
                    animation.timingFunction = action.timingFunction
                    animation.timeOffset = action.timeOffset
                    return animation
                }
            }
            return super.actionForKey(event)
        }
    
    }
    

    这篇关于在动画界的动画变化CAShapeLayer路径的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆