iphone - 如何围绕对角线旋转 CALayer?

标签 iphone animation core-animation calayer flip

我正在尝试实现一个翻转动画,用于像 iPhone 应用程序这样的棋盘游戏。该动画应该看起来像一个旋转并改变其背面颜色的游戏片段(有点像 Reversi piece )。我已经成功创建了一个动画,可以围绕其正交轴翻转该部件,但是当我尝试通过更改围绕 z 轴的旋转来围绕对角轴翻转它时,实际图像也会旋转(毫不奇怪)。相反,我想“按原样”绕对角轴旋转图像。

我尝试更改layer.sublayerTransform但没有成功。

这是我当前的实现。它通过一个技巧来解决在动画结束时获得镜像图像的问题。解决方案是实际上不将图层旋转 180 度,而是将其旋转 90 度,更改图像,然后将其旋转回来。

最终版本:根据 Lorenzos 建议创建离散关键帧动画并计算每一帧的变换矩阵。此版本尝试根据图层大小估计所需的“引导”帧数量,然后使用线性键控动画。此版本以任意角度旋转,因此要绕对角线旋转,请使用 45 度角。

使用示例:

[someclass flipLayer:layer image:image angle:M_PI/4]

实现:

- (void)animationDidStop:(CAAnimationGroup *)animation
                finished:(BOOL)finished {
  CALayer *layer = [animation valueForKey:@"layer"];

  if([[animation valueForKey:@"name"] isEqual:@"fadeAnimation"]) {
    /* code for another animation */
  } else if([[animation valueForKey:@"name"] isEqual:@"flipAnimation"]) {
    layer.contents = [animation valueForKey:@"image"];
  }

  [layer removeAllAnimations];
}

- (void)flipLayer:(CALayer *)layer
            image:(CGImageRef)image
            angle:(float)angle {
  const float duration = 0.5f;

  CAKeyframeAnimation *rotate = [CAKeyframeAnimation
                                 animationWithKeyPath:@"transform"];
  NSMutableArray *values = [[[NSMutableArray alloc] init] autorelease];
  NSMutableArray *times = [[[NSMutableArray alloc] init] autorelease];
  /* bigger layers need more "guiding" values */
  int frames = MAX(layer.bounds.size.width, layer.bounds.size.height) / 2;
  int i;
  for (i = 0; i < frames; i++) {
    /* create a scale value going from 1.0 to 0.1 to 1.0 */
    float scale = MAX(fabs((float)(frames-i*2)/(frames - 1)), 0.1);

    CGAffineTransform t1, t2, t3;
    t1 = CGAffineTransformMakeRotation(angle);
    t2 = CGAffineTransformScale(t1, scale, 1.0f);
    t3 = CGAffineTransformRotate(t2, -angle);
    CATransform3D trans = CATransform3DMakeAffineTransform(t3);

    [values addObject:[NSValue valueWithCATransform3D:trans]];
    [times addObject:[NSNumber numberWithFloat:(float)i/(frames - 1)]];
  }
  rotate.values = values;
  rotate.keyTimes = times;
  rotate.duration = duration;
  rotate.calculationMode = kCAAnimationLinear;

  CAKeyframeAnimation *replace = [CAKeyframeAnimation
                                  animationWithKeyPath:@"contents"];
  replace.duration = duration / 2;
  replace.beginTime = duration / 2;
  replace.values = [NSArray arrayWithObjects:(id)image, nil];
  replace.keyTimes = [NSArray arrayWithObjects:
                      [NSNumber numberWithDouble:0.0f], nil];
  replace.calculationMode = kCAAnimationDiscrete;

  CAAnimationGroup *group = [CAAnimationGroup animation];
  group.duration = duration;
  group.timingFunction = [CAMediaTimingFunction
                          functionWithName:kCAMediaTimingFunctionLinear];
  group.animations = [NSArray arrayWithObjects:rotate, replace, nil];
  group.delegate = self;
  group.removedOnCompletion = NO;
  group.fillMode = kCAFillModeForwards;
  [group setValue:@"flipAnimation" forKey:@"name"];
  [group setValue:layer forKey:@"layer"];
  [group setValue:(id)image forKey:@"image"];

  [layer addAnimation:group forKey:nil];
}

原始代码:

+ (void)flipLayer:(CALayer *)layer
          toImage:(CGImageRef)image
        withAngle:(double)angle {
  const float duration = 0.5f;

  CAKeyframeAnimation *diag = [CAKeyframeAnimation
                               animationWithKeyPath:@"transform.rotation.z"];
  diag.duration = duration;
  diag.values = [NSArray arrayWithObjects:
                 [NSNumber numberWithDouble:angle],
                 [NSNumber numberWithDouble:0.0f],
                 nil];
  diag.keyTimes = [NSArray arrayWithObjects:
                   [NSNumber numberWithDouble:0.0f],
                   [NSNumber numberWithDouble:1.0f],
                   nil];
  diag.calculationMode = kCAAnimationDiscrete;

  CAKeyframeAnimation *flip = [CAKeyframeAnimation
                               animationWithKeyPath:@"transform.rotation.y"];
  flip.duration = duration;
  flip.values = [NSArray arrayWithObjects:
                 [NSNumber numberWithDouble:0.0f],
                 [NSNumber numberWithDouble:M_PI / 2],
                 [NSNumber numberWithDouble:0.0f],
                 nil];
  flip.keyTimes = [NSArray arrayWithObjects:
                   [NSNumber numberWithDouble:0.0f],
                   [NSNumber numberWithDouble:0.5f],
                   [NSNumber numberWithDouble:1.0f],
                   nil];
  flip.calculationMode = kCAAnimationLinear;

  CAKeyframeAnimation *replace = [CAKeyframeAnimation
                                  animationWithKeyPath:@"contents"];
  replace.duration = duration / 2;
  replace.beginTime = duration / 2;
  replace.values = [NSArray arrayWithObjects:(id)image, nil];
  replace.keyTimes = [NSArray arrayWithObjects:
                      [NSNumber numberWithDouble:0.0f], nil];
  replace.calculationMode = kCAAnimationDiscrete;

  CAAnimationGroup *group = [CAAnimationGroup animation];
  group.removedOnCompletion = NO;
  group.duration = duration;
  group.timingFunction = [CAMediaTimingFunction
                          functionWithName:kCAMediaTimingFunctionLinear];
  group.animations = [NSArray arrayWithObjects:diag, flip, replace, nil];
  group.fillMode = kCAFillModeForwards;

  [layer addAnimation:group forKey:nil];
}

最佳答案

你可以这样伪造它:创建一个仿射变换,沿着对角线折叠图层:

A-----B           B
|     |          /
|     |   ->   A&D
|     |        /
C-----D       C

更改图像,并将 CALayer 转换回另一个动画。 这将产生图层绕其对角线旋转的错觉。

如果我没记错数学的话,矩阵应该是:

0.5 0.5 0
0.5 0.5 0
0   0   1

更新: 好吧,CA 并不真的喜欢使用退化变换,但你可以这样近似:

CGAffineTransform t1 = CGAffineTransformMakeRotation(M_PI/4.0f);
CGAffineTransform t2 = CGAffineTransformScale(t1, 0.001f, 1.0f);
CGAffineTransform t3 = CGAffineTransformRotate(t2,-M_PI/4.0f);  

在我对模拟器的测试中,仍然存在问题,因为旋转发生得比平移快,因此对于纯黑色方 block ,效果有点奇怪。我想如果你有一个居中的 Sprite ,周围有透明区域,那么效果将接近预期。然后,您可以调整 t3 矩阵的值,看看是否获得更有吸引力的结果。

经过更多研究,似乎应该通过关键帧为其自己的过渡设置动画,以获得对过渡本身的最大控制。假设你要在一秒钟内显示这个动画,你应该让十个矩阵每隔十分之一秒显示一次,而不使用 kCAAnimationDiscrete 进行插值;这些矩阵可以通过以下代码生成:

CGAffineTransform t1 = CGAffineTransformMakeRotation(M_PI/4.0f);
CGAffineTransform t2 = CGAffineTransformScale(t1, animationStepValue, 1.0f);
CGAffineTransform t3 = CGAffineTransformRotate(t2,-M_PI/4.0f);  

其中关键帧的每个animationStepValue取自此进程:

{1 0.7 0.5 0.3 0.1 0.3 0.5 0.7 1}

也就是说:您生成十个不同的变换矩阵(实际上是 9 个),将它们作为关键帧推送到每隔十分之一秒显示一次,然后使用“不插值”参数。您可以调整动画数量以平衡平滑度和性能*

*很抱歉可能出现错误,最后一部分是在没有拼写检查的情况下编写的。

关于iphone - 如何围绕对角线旋转 CALayer?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2394807/

相关文章:

iphone - UITableView背景图片

iphone - 根据设备 iPhone 或 iPod touch 关闭功能

iphone - iOS 4 GCD 问题

ios - UIButtons 在动画期间没有响应 - iOS Swift 3

objective-c - CALayer 未在 UIImageView 中显示

iphone - UIFont 类别的单元测试用例编写问题

javascript - 如何在 react 中悬停时在图像上添加过渡时间?

xaml - 有没有办法在 Metro 风格的应用程序中为 Margin 属性设置动画

iphone - "transform"和 "affine transform"之间的区别在哪里?

objective-c - 如何在 Mac 上与 layer-backed views 交互