如何围绕对角线旋转 CALayer?
我正在尝试实现一个翻转动画,用于像 iPhone 应用程序这样的棋盘游戏。动画应该看起来像一个旋转并改变其背面颜色的游戏片段(有点像 黑白棋棋子)。我已经成功创建了一个动画,可以围绕其正交轴翻转该部件,但是当我尝试通过更改围绕 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];
}
I'm trying to implement a flip animation to be used in board game like iPhone-application. The animation is supposed to look like a game piece that rotates and changes to the color of its back (kind of like an Reversi piece). I've managed to create an animation that flips the piece around its orthogonal axis, but when I try to flip it around a diagonal axis by changing the rotation around the z-axis the actual image also gets rotated (not surprisingly). Instead I would like to rotate the image "as is" around a diagonal axis.
I have tried to change layer.sublayerTransform
but with no success.
Here is my current implementation. It works by doing a trick to resolve the issue of getting a mirrored image at the end of the animation. The solution is to not actually rotate the layer 180 degrees, instead it rotates it 90 degrees, changes image and then rotates it back.
Final version: Based on Lorenzos suggestion to create a discrete keyed animation and calculate the transformation matrix for each frame. This version instead tries to estimate number of "guiding" frames needed based on the layer size and then uses a linear keyed animation. This version rotates with a arbitrary angle so to rotate around diagonal line use a 45 degree angle.
Example usage:
[someclass flipLayer:layer image:image angle:M_PI/4]
Implementation:
- (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];
}
Original code:
+ (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];
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
你可以这样伪造:创建一个仿射变换,沿着对角线折叠图层:
更改图像,然后将 CALayer 变换回另一个动画中。
这将产生图层绕其对角线旋转的错觉。
如果我没记错数学的话,矩阵应该是:
更新:
好吧,CA 并不真的喜欢使用退化变换,但你可以这样近似:
在我在模拟器上的测试中仍然存在问题,因为旋转发生得比平移快,所以使用实心黑色方块时效果是有点奇怪。我想如果你有一个居中的精灵,周围有透明区域,那么效果将接近预期。然后,您可以调整 t3 矩阵的值,看看是否会获得更有吸引力的结果。
经过更多研究,似乎应该通过关键帧为其自己的过渡设置动画,以获得对过渡本身的最大控制。假设你要在一秒钟内显示这个动画,你应该使用 kCAAnimationDiscrete 制作十个矩阵,每隔十分之一秒显示一次,而不进行插值;这些矩阵可以通过下面的代码生成:
其中关键帧的每一个的animationStepValue取自这个过程:
也就是说:你正在生成十个不同的变换矩阵(实际上是9个),将它们作为关键帧在每十分之一处显示一秒钟,然后使用“不插值”参数。您可以调整动画数量以平衡平滑度和性能*
*很抱歉可能出现错误,最后一部分是在没有拼写检查的情况下编写的。
you can fake it this way: create an affine transform that collapse the layer along it's diagonal:
change the image, and trasform the CALayer back in another animation.
This will create the illusion of the layer rotating around its diagonal.
the matrix for that should be if I remember math correctly:
Update:
ok, CA doen't really likes to use degenerate transforms, but you can approximate it this way:
in my tests on the simulator there still was a problem because the rotations happens faster than te translation so with a solid black square the effect was a bit weird. I suppose that if you have a centered sprite with transparent area around it the effect will be close to what expected. You can then tweak the value of the t3 matrix to see if you get a more appealing result.
after more research, it appears that one should animate it's own transition via keyframes to obtaim the maximum control of the transition itself. say you were to display this animation in a second, you should make ten matrix to be shown at each tenth of a second withouot interpolation using kCAAnimationDiscrete; those matrix can be generated via the code below:
where animationStepValue for ech of the keyFrame is taken from this progression:
that is: you're generating ten different transformation matrix (actually 9), pushing them as keyframes to be shown at each tenth of a second, and then using the "don't interpolate" parameter. you can tweak the animation number for balancing smoothness and performance*
*sorry for possible errors, this last part was written without a spellchecker.
我解决了。您可能也已经有了解决方案,但这是我发现的。这真的很简单......
您可以使用 CABasicAnimation 进行对角线旋转,但它需要是两个矩阵的串联,即图层的现有矩阵加上 CATransform3DRotate。 “技巧”是,在 3DRotate 中,您需要指定要旋转的坐标。
代码如下所示:
这将进行旋转,看起来好像正方形的左上角绕轴 Y=X 旋转,并移动到右下角。
动画代码如下所示:
就是这样!该代码将在正方形 (theLayer) 旋转 90 度时将其旋转至不可见状态,并以与屏幕正交的方式呈现。然后,您可以更改颜色,并执行完全相同的动画以使其恢复正常。相同的动画之所以有效,是因为我们连接了矩阵,因此每次您想要旋转时,只需执行两次,或将
M_PI/2
更改为M_PI
。最后,应该指出的是,这让我发疯,完成后,图层将恢复到其原始状态,除非您明确将其设置为结束动画状态。这意味着,在
[theLayer addAnimation:ani1 forKey@"atoryKey"];
行之前,您需要添加以在动画完成后设置其值。这将防止恢复到原始状态。
希望这有帮助。如果不是你,那么也许还有其他人像我们一样用头撞墙! :)
干杯,
克里斯
I got it solved. You probably already have a solution as well, but here is what I have found. It is quite simple really...
You can use a CABasicAnimation to do the diagonal rotation, but it needs to be the concatenation of two matrices, namely the existing matrix of the layer, plus a CATransform3DRotate. The "trick" is, in the 3DRotate you need to specify the coordinates to rotate around.
The code looks something like this:
This will make a rotation that appears as though the upper-left corner of the square is rotating around the axis Y=X, and travelling to the lower-right corner.
The code to animate looks like this:
That's it! That code will rotate the square (theLayer) to invisibility as it travels 90-degrees and presents itself orthogonally to the screen. You can then change the color, and do the exact same animation to bring it back around. The same animation works because we are concatenating the matrices, so each time you want to rotate, just do this twice, or change
M_PI/2
toM_PI
.Lastly, it should be noted, and this drove me nuts, that upon completion, the layer will snap back to its original state unless you explicitly set it to the end-animation state. This means, just before the line
[theLayer addAnimation:ani1 forKey@"arbitraryKey"];
you will want to addto set its value for after the animation completes. This will prevent the snapping back to original state.
Hope this helps. If not you then perhaps someone else who was banging their head against the wall like we were! :)
Cheers,
Chris
这是一个 Xamarin iOS 示例,我用它来拍打方形按钮的角,就像狗耳朵一样(很容易移植到 obj-c):
方法 1: 使用
x
和y
轴均使用1
旋转动画(Xamarin.iOS 中的示例,但可以轻松移植到 obj-c):方法 2: 只需将
x 轴
旋转和y 轴
旋转添加到CAAnimationGroup
中,以便它们同时运行时间:Here is a Xamarin iOS example I use to flap the corner of a square button, like a dog ear (easily ported to obj-c):
Method 1: use a rotation animation with
1
for bothx
andy
axes (examples in Xamarin.iOS, but easily portable to obj-c):Method 2: just add an
x-axis
rotation, andy-axis
rotation to aCAAnimationGroup
so they run at the same time: