【问题标题】:Animate a CALayer's bounds w/redraws使用重绘为 CALayer 的边界设置动画
【发布时间】:2011-09-27 23:43:34
【问题描述】:

我想知道如何为 CALayer 的边界设置动画,因此,在每次边界更改时,图层都会调用 drawInContext:。我在我的 CALayer 子类上尝试了以下两种方法:

  • needsDisplayOnBoundsChange 设置为YES
  • 为 + (BOOL)needsDisplayForKey:(NSString*)keybounds 键返回 YES

都不行。 CALayer 似乎决定使用图层的原始内容并根据contentsGravity 简单地缩放它们(我认为这是为了性能。)他们是解决这个问题的方法还是我遗漏了一些明显的东西?

编辑:而且,顺便说一句,我注意到我的自定义 CALayer 子类没有调用 initWithLayer: 来创建 presentationLayer - 很奇怪。

提前致谢, 山姆

【问题讨论】:

  • 另一件不起作用的事情:继承和覆盖setFramesetBoundssetPosition。动画期间不会调用它们。
  • 我不太了解你。你想动画什么?只是CALayer的界限还是别的什么?边界动画是非常简单的任务,帧动画 - 更复杂。
  • 想象您的图层包含类似按钮的东西,具有复杂但与大小无关的边框图形。如果您将其设置为双倍宽度,它将使用位图缩放进行动画处理,在整个动画中变得拉伸和像素化,即使您有needsDisplayOnBoundsChange YES。只有最后一帧会被drawInContext:正确渲染。

标签: iphone objective-c core-animation calayer bounds


【解决方案1】:

您可以使用technique outlined here:覆盖CALayer的+needsDisplayForKey:方法,它会在动画的每一步重绘其内容。

【讨论】:

  • 这是一个很好的答案。提供有关该链接的更多信息,以便我们在点击之前了解。
  • 据我所知,这种方法确实使每一帧都调用了重绘方法,尽管奇怪的是 CALayer 不显示这些重绘的结果,而是继续它提前生成的拉伸内容.尽管如此,这是向前迈出的重要一步。
  • @AlexanderLjungberg:这个方法应该在动画的每一步重绘内容。我创建了一个测试项目来确认它是否有效。我不确定是什么原因导致您的图层无法重​​新显示。
  • @titaniumdecoy:我也试过......它适用于自定义属性,但不适用于bounds(以及其他CALayer 属性)。 actionForKey: 方法是为自定义属性调用的,但不是为CALayer 属性调用的。您是在为自定义属性或CALayer 属性设置动画吗?
  • 那么如果+needsDisplayForKey: 不能与bounds 一起使用,那又如何呢?
【解决方案2】:

我不确定设置 viewFlags 是否有效。第二种解决方案肯定行不通:

默认实现返回 NO。子类应该 * 调用 super 对于超类定义的属性。 (例如,* 不要尝试 为 CALayer 实现的属性返回 YES,* 做将 有未定义的结果。)

您需要将视图的内容模式设置为 UIViewContentModeRedraw:

UIViewContentModeRedraw,    //redraw on bounds change (calls -setNeedsDisplay)

查看Apple's documentation on providing content with CALayer's。他们建议使用 CALayer 的委托属性而不是子类,这可能比您现在尝试的要容易得多。

【讨论】:

  • UIViewContentModeRedraw 不会导致层的内容在动画的每一步都被重绘。
  • 真的吗?嗯……你知道动画过程中是否调用了CALayerDelegate方法吗?
  • 除非您使用我在答案中链接的技术(即覆盖 CALayer 的 +needsDisplayForKey: 方法)
  • 它不起作用,因为在动画期间没有不断更新边界。它只在动画开始时改变一次。
【解决方案3】:

我不知道这是否完全可以作为您问题的解决方案,但能够让它发挥作用。

我首先将我的内容图像预绘制成CGImageRef

然后我覆盖了我的层的-display 方法INSTEAD OF -drawInContext:。在其中我将contents 设置为预渲染的CGImage,并且它起作用了。

最后,您的图层还需要将默认的contentsGravity 更改为@"left" 之类的东西,以避免内容图像被缩放。

我遇到的问题是传递给-drawInContext: 的上下文是图层的起始大小,而不是最终的动画后大小。 (您可以使用CGBitmapContextGetWidthCGBitmapContextGetHeight 方法进行检查。)

我的方法仍然只为整个动画调用一次,但是直接使用-display 方法设置层的内容允许您传递大于可见边界的图像。 drawInContext: 方法不允许这样做,因为您不能在 CGContext 上下文的范围之外进行绘制。

更多关于不同图层绘制方式的区别,见http://www.apeth.com/iOSBook/ch16.html

【讨论】:

    【解决方案4】:

    我最近遇到了同样的问题。这是我发现的:

    有一个技巧可以做到,那就是为边界的影子副本设置动画,例如:

    var shadowBounds: CGRect {
      get { return bounds }
      set { bounds = newValue}
    }
    

    然后覆盖 CALayer 的 +needsDisplayForKey:.

    但是,如果您的绘图依赖于边界,这可能不是您想要做的。正如您已经注意到的那样,核心动画只是将图层的内容缩放到动画边界。即使您使用上述技巧也是如此,也就是说,即使在动画过程中边界发生变化,内容也会被缩放。结果是您的绘图动画看起来不一致。

    如何解决?由于内容是缩放的,您可以通过反向缩放它们来计算确定您的绘图的自定义变量的值,以便您在最终但缩放的内容上的绘图看起来与原始且未缩放的内容相同,然后将 fromValues 设置为这些值, toValues 到它们的旧值,同时用边界为它们设置动画。如果要更改最终值,请将 toValues 设置为这些最终值。您必须为至少一个自定义变量设置动画,以便导致重绘。

    【讨论】:

      【解决方案5】:

      这是您的自定义类:

      @implementation MyLayer
      
      -(id)init
      {
          self = [super init];
          if (self != nil)
              self.actions = [NSDictionary dictionaryWithObjectsAndKeys:
                              [NSNull null], @"bounds",
                              nil];
          return self;
      }
      
      -(void)drawInContext:(CGContextRef)context
      {
          CGContextSetRGBFillColor(context,
                                   drand48(),
                                   drand48(),
                                   drand48(),
                                   1);
          CGContextFillRect(context,
                            CGContextGetClipBoundingBox(context));
      }
      
      +(BOOL)needsDisplayForKey:(NSString*)key
      {
          if ([key isEqualToString:@"bounds"])
              return YES;
          return [super needsDisplayForKey:key];
      }
      
      @end
      

      这些是对 xcode 4.2 默认模板的补充:

      -(BOOL)application:(UIApplication*)application
      didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
      {
          self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
              // Override point for customization after application launch.
          self.window.backgroundColor = [UIColor whiteColor];
          [self.window makeKeyAndVisible];
      
              // create and add layer
          MyLayer *layer = [MyLayer layer];
          [self.window.layer addSublayer:layer];
          [self performSelector:@selector(changeBounds:)
                     withObject:layer];
      
          return YES;
      }
      
      -(void)changeBounds:(MyLayer*)layer
      {
              // change bounds
          layer.bounds = CGRectMake(0, 0,
                                    drand48() * CGRectGetWidth(self.window.bounds),
                                    drand48() * CGRectGetHeight(self.window.bounds));
      
              // call "when idle"
          [self performSelector:@selector(changeBounds:)
                     withObject:layer
                     afterDelay:0];
      }
      

      -----------------已编辑:

      好吧...这不是你要求的:) 抱歉:|

      -----------------已编辑(2):

      你为什么需要这样的东西?可以使用(void)display,但文档说它可以用于设置self.contents...

      【讨论】:

      • 这不起作用:+ needsDisplayForKey 不会强制重绘“边界”
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-18
      • 1970-01-01
      • 2011-04-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多