How do i rotate a CALayer around a diagonal line?

前端 未结 3 1743
旧巷少年郎
旧巷少年郎 2021-01-31 21:42

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 c

相关标签:
3条回答
  • 2021-01-31 22:06

    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 both x and y axes (examples in Xamarin.iOS, but easily portable to 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);
    

    Method 2: just add an x-axis rotation, and y-axis rotation to a CAAnimationGroup so they run at the same time:

    // 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);
    
    0 讨论(0)
  • 2021-01-31 22:11

    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:

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

    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:

    
    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"];
    

    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 to M_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 add

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

    to 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

    0 讨论(0)
  • 2021-01-31 22:12

    you can fake it this way: create an affine transform that collapse the layer along it's diagonal:

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

    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:

    0.5 0.5 0
    0.5 0.5 0
    0   0   1
    

    Update: ok, CA doen't really likes to use degenerate transforms, but you can approximate it this way:

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

    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:

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

    where animationStepValue for ech of the keyFrame is taken from this progression:

    {1 0.7 0.5 0.3 0.1 0.3 0.5 0.7 1}
    

    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.

    0 讨论(0)
提交回复
热议问题