Smooth horizontal flip using CATransform3DMakeRota

2019-04-12 09:31发布

问题:

I have set up the following animation to rotate between views of different sizes. The midpoint of the animation seems to have a flicker as the new, taller view comes into view. Is there anything I can do to smoothen the transition.

newView.layer.transform = CATransform3DMakeRotation(M_PI_2, 0.0, 1.0, 0.0);

[UIView animateWithDuration:0.5
                          delay:0
                        options:UIViewAnimationOptionCurveLinear
                     animations:^{oldView.layer.transform = CATransform3DMakeRotation(M_PI_2, 0.0, -1.0, 0.0);}
                     completion:^(BOOL finished) {

                         [oldView removeFromSuperview];
                         [UIView animateWithDuration:0.5
                                               delay:0
                                             options:UIViewAnimationOptionCurveLinear
                                          animations:^{newView.layer.transform = CATransform3DMakeRotation(M_PI_2, 0.0, 0.0, 0.0);}
                                          completion:nil];

    }];

回答1:

Got this working thanks to this thread, so I thought I'd share my to-from 3D transform using the m34 matrix.

    UIView *toView = // show this
    UIView *fromView = // hide this one

    // set up from
    CATransform3D fromViewRotationPerspectiveTrans = CATransform3DIdentity;
    fromViewRotationPerspectiveTrans.m34 = -0.003; // 3D ish effect
    fromViewRotationPerspectiveTrans = CATransform3DRotate(fromViewRotationPerspectiveTrans, M_PI_2, 0.0f, -1.0f, 0.0f);

    // set up to
    CATransform3D toViewRotationPerspectiveTrans = CATransform3DIdentity;
    toViewRotationPerspectiveTrans.m34 = -0.003;
    toViewRotationPerspectiveTrans = CATransform3DRotate(toViewRotationPerspectiveTrans, M_PI_2, 0.0f, 1.0f, 0.0f);

    toView.layer.transform = toViewRotationPerspectiveTrans;

    [UIView animateWithDuration:1.0
                          delay:0
                        options:UIViewAnimationOptionCurveLinear
                     animations:^{fromView.layer.transform = fromViewRotationPerspectiveTrans; }
                     completion:^(BOOL finished) {

                         [fromView removeFromSuperview];
                         [UIView animateWithDuration:1.0
                                               delay:0
                                             options:UIViewAnimationOptionCurveLinear
                                          animations:^{toView.layer.transform = CATransform3DMakeRotation(M_PI_2, 0.0, 0.0, 0.0);}
                                          completion:nil];

                     }];


回答2:

I was halfway there, but the missing piece, setting the m34 cell value of the transformation matrix, did the trick.



回答3:

As David pointed out, your code doesn't make sense as written. You're setting the final rotation of your newView to a rotation around nothing, which will PROBABLY be equivalent to the identity matrix, but I'm not sure.

Here's what I would try (I'm tired, so let's see if I can explain this coherently...)

Animate the oldView from 0 to pi/2 as animation step 1. Set the newView to -pi/2 before beginning the second animation (rotated 90 degrees the other way.)

In the completion method, remove the old view and start an animation to set the new view's rotation back to zero. That will cause the new view to look like it's continuing to flip around in a 180 degree flip.

Here's the tricky part. Calculate the difference in size (horizontal and vertical) between the old and new views. Add (concatenate) a scale transform along with the rotation, so that when the first part of the rotation is finished, it is scaled to the average of the old and new size. Pseudocode might look like this:

//Scale to apply to oldView for the first part of the animation:
scale height  = ((oldView.size.height+newView.size.height)/2) / oldView.size.height
scale width  = ((oldView.size.width+newView.size.width)/2) / oldView.size.width

/*
Before beginning the second part of the animation, rotate newView to -pi/2, 
and scale it by an amount that makes it the same size that oldView will be 
at the end of the first animation (the average of the sizes of both views)
*/

newView scale height  = ((oldView.size.height+newView.size.height)/2) / 
  newView.size.height
newView scale width  = ((oldView.size.width+newView.size.width)/2) / 
  newView.size.width

in the completion block, remove oldView from it's superview, and animate newView back to the identity transform.

If my approach is right, at the end of the first animation, oldView should be scaled to a size halfway between the sizes of oldView and newView.

The second animation, triggered in the completion block of the first, will start out with newView being the same size that oldView was scaled to at the end of the first animation. The second animation will end with the new view rotating into place and scaling back to it's original size.