【问题标题】:How do i rotate a CALayer around a diagonal line?如何围绕对角线旋转 CALayer?
【发布时间】:2011-01-24 14:05:24
【问题描述】:

我正在尝试实现一个翻转动画,以便在 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];
}

【问题讨论】:

  • 什么是对角轴?
  • 对角轴我的意思是旋转应该围绕图层的对角线。就像你抓住图层的两个对角并旋转它一样。
  • 喜欢,绕直线旋转y = x?

标签: iphone animation core-animation calayer flip


【解决方案1】:

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

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);  

在我在模拟器上的测试中仍然存在问题,因为旋转发生的速度比 te 平移发生得快,所以使用纯黑色方块时效果有点奇怪。我想如果你有一个围绕它的透明区域的居中精灵,效果将接近预期的效果。然后,您可以调整 t3 矩阵的值,看看是否得到更有吸引力的结果。

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

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

keyFrame 的 ech 的 animationStepValue 取自这个进程:

{1 0.7 0.5 0.3 0.1 0.3 0.5 0.7 1}

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

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

【讨论】:

  • 感谢您的回答。 ASCII艺术是否应该显示剪切变换?如果我尝试从身份动画,然后到你的矩阵,然后他们回到身份我根本没有图像:(我查找了如何制作剪切矩阵,它看起来像这样:{{1,sheary,0},{ hearx, 1, 0}, {0, 0, 1}} 如果我在动画中使用它,它会接缝工作一点,但我需要剪切很多,然后图像也会放大很多。
  • 没有剪切会形成平行四边形,在这里我们试图得到一个退化的菱形,A&D 角向中心移动,而不是 A&B 角向左移动我必须试试这个明天设备获取实际矩阵参数
  • 好的。我一直在阅读仿射变换实际上是如何工作的,你用图表描述的变换应该像你说的那样是仿射的,保留一条线上的点和比率(只要 A 和 D 是“相反的”我猜?)。但我还没有真正想出如何考虑矩阵中的数字。
  • 就复合标准变换而言,它是逆时针旋转 45 度,然后是沿水平轴缩放(折叠中心的 a&c 顶点,然后顺时针旋转 45 度 - 正如我所说,那应该是结果矩阵,但我会尝试它并发布一些实际代码(我只是用背后的理论进行更新)
  • 在你评论了复合变换之后,我昨天在摆弄非常相似的代码,但是动画表现得很奇怪。也许核心动画在计算平滑过渡的值时会做一些奇怪的事情?
【解决方案2】:

我解决了。您可能也已经有了解决方案,但这是我发现的。真的很简单……

你可以使用CABasicAnimation来做对角线旋转,但它需要是两个矩阵的串联,即层的现有矩阵,加上一个CATransform3DRotate。 “诀窍”是,在 3DRotate 中,您需要指定要旋转的坐标。

代码如下所示:


CATransform3DConcat(theLayer.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0));

这将使旋转看起来好像正方形的左上角围绕轴 Y=X 旋转,并移动到右下角。

动画代码如下所示:


CABasicAnimation *ani1 = [CABasicAnimation animationWithKeyPath:@"transform"];

// set self as the delegate so you can implement (void)animationDidStop:finished: to handle anything you might want to do upon completion
[ani1 setDelegate:self];

// set the duration of the animation - a float
[ani1 setDuration:dur];

// set the animation's "toValue" which MUST be wrapped in an NSValue instance (except special cases such as colors)
ani1.toValue = [NSValue valueWithCATransform3D:CATransform3DConcat(theLayer.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0))];

// give the animation a name so you can check it in the animationDidStop:finished: method
[ani1 setValue:@"shrink" forKey:@"name"];

// finally, apply the animation
[theLayer addAnimation:ani1 forKey@"arbitraryKey"];

就是这样!该代码会将正方形(theLayer)旋转 90 度并垂直于屏幕呈现自身,使其不可见。然后,您可以更改颜色,并执行完全相同的动画将其恢复。相同的动画效果是因为我们连接了矩阵,所以每次你想旋转时,只需这样做两次,或者将M_PI/2 更改为M_PI

最后,应该注意的是,这让我发疯了,完成后,除非您明确将其设置为动画结束状态,否则图层将恢复到其原始状态。这意味着,就在您想要添加的 [theLayer addAnimation:ani1 forKey@"arbitraryKey"]; 行之前


theLayer.transform = CATransform3DConcat(v.theSquare.transform, CATransform3DRotate(CATransform3DIdentity, M_PI/2, -1, 1, 0));

在动画完成后设置它的值。这将防止快速恢复到原始状态。

希望这会有所帮助。如果不是你,那么也许是其他人像我们一样用头撞墙! :)

干杯,

克里斯

【讨论】:

  • 我想我尝试了类似的解决方案,但存在旋转变换旋转实际图像内容的问题。但如果图层是对称图像,您将不会注意到它。
  • 你能解释一下v.theSquare是什么吗?
【解决方案3】:

这是一个 Xamarin iOS 示例,我用它来拍打方形按钮的角,就像一只狗耳朵(很容易移植到 obj-c):

方法 1:xy 轴使用带有 1 的旋转动画(Xamarin.iOS 中的示例,但很容易移植到 obj-c):

// add to the UIView subclass you wish to rotate, where necessary
AnimateNotify(0.10, 0, UIViewAnimationOptions.CurveEaseOut | UIViewAnimationOptions.AllowUserInteraction | UIViewAnimationOptions.BeginFromCurrentState, () =>
{
    // note the final 3 params indicate "rotate around x&y axes, but not z"
    var transf = CATransform3D.MakeRotation(-1 * (nfloat)Math.PI / 4, 1, 1, 0);
    transf.m34 = 1.0f / -500;
    Layer.Transform = transf;
}, null);

方法2:只需将x-axis 旋转和y-axis 旋转添加到CAAnimationGroup,这样它们就可以同时运行了:

// add to the UIView subclass you wish to rotate, where necessary
AnimateNotify(1.0, 0, UIViewAnimationOptions.CurveEaseOut | UIViewAnimationOptions.AllowUserInteraction | UIViewAnimationOptions.BeginFromCurrentState, () =>
{
    nfloat angleTo = -1 * (nfloat)Math.PI / 4;
    nfloat angleFrom = 0.0f ;
    string animKey = "rotate";

    // y-axis rotation
    var anim = new CABasicAnimation();
    anim.KeyPath = "transform.rotation.y";
    anim.AutoReverses = false;
    anim.Duration = 0.1f;
    anim.From = new NSNumber(angleFrom);
    anim.To = new NSNumber(angleTo);

    // x-axis rotation
    var animX = new CABasicAnimation();
    animX.KeyPath = "transform.rotation.x";
    animX.AutoReverses = false;
    animX.Duration = 0.1f;
    animX.From = new NSNumber(angleFrom);
    animX.To = new NSNumber(angleTo);

    // add both rotations to a group, to run simultaneously
    var animGroup = new CAAnimationGroup();
    animGroup.Duration = 0.1f;
    animGroup.AutoReverses = false;
    animGroup.Animations = new CAAnimation[] {anim, animX};
    Layer.AddAnimation(animGroup, animKey);

    // add perspective
    var transf = CATransform3D.Identity;
    transf.m34 = 1.0f / 500;
    Layer.Transform = transf;

}, null);

【讨论】:

  • 不知道为什么这会招来反对票。这是关于对角线的 CALayer 旋转。 “展示你的作品”,反对者。
  • 这真是太好了!正是我正在寻找的。我没有看到你在哪里分配了角落区域,在你的例子中角落很大,像狗耳朵一样移动我想要小耳朵?
  • @nikhilgohil11,大“+”区域是一个单独的按钮(UIView)。角按钮实际上是一个正在旋转的正方形(一半被大按钮覆盖)。总之,派生出 2 个 Button 子类:BigButton 和 CornerButton。将 CornerButton 实例放在 BigButton 下方(半覆盖),当 CornerButton 被按下时,调用旋转代码。
  • 谢谢@bunkerdive,我会尽快试试这个
猜你喜欢
  • 1970-01-01
  • 2021-09-13
  • 2018-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-26
  • 1970-01-01
相关资源
最近更新 更多