我正在尝试在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];
}
答案 0 :(得分:6)
你可以这样伪造它:创建一个仿射变换,沿着它的对角线折叠图层:
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在十分之一秒内显示10个矩阵。那些矩阵可以通过以下代码生成: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),将它们作为关键帧推送到每十分之一秒,然后使用“不插入”参数。您可以调整动画编号以平衡平滑度和性能*
*对不起可能的错误,最后一部分是在没有拼写检查的情况下编写的。
答案 1 :(得分:4)
我解决了。您可能已经有了解决方案,但这是我找到的。真的很简单......
您可以使用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));
在动画完成后设置其值。这样可以防止捕捉回到原始状态。
希望这会有所帮助。如果不是你那么也许就像我们一样正在撞墙的人! :)
干杯,
克里斯
答案 2 :(得分:1)
这是一个Xamarin iOS示例,用于拍打方形按钮的角落,如狗耳(轻松移植到obj-c):
方法1:对1
和x
轴使用y
的旋转动画(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);