Custom transition between two UIViews

2020-02-10 07:58发布

问题:

I have two views "A" and "B". A floats in the middle of the window (it's not full screen). B, the view which will replace A, is full screen. I'd like to write a custom transition that flips A to reveal B on the back side, but simultaneously scales the flipping rectangle so that when the flip is finished, B is full screen.

I've tried using the flip transitions available with transitionFromView:toView:duration:options:completion, but I can only get it to flip the entire screen instead of starting the flip with A's frame and ending with B's frame.

I know I can do 3D-like transforms to the view's layers, but I'm not sure which set of animation APIs I should use to accomplish this. One thing I tried is to modify the view's layer properties in the animations block of transitionWithView:duration:options:animations:completion: but that didn't work as it only seems to honor view property modifications.

Can someone point me in the right direction? I'd much appreciate it.

UPDATE: Here is my code thus far for this effect. You can see a video of what it does here: http://www.youtube.com/watch?v=-xNMD2fGRwg

CGRect frame = [[UIApplication easybookDelegate] rootViewController].view.bounds ;
float statusHeight = [UIApplication sharedApplication].statusBarFrame.size.height ;
frame.origin.y = statusHeight ;
frame.size.height -= statusHeight ;
self.view.frame = frame ; // self.view is view "B"

// Put the snapshot as thet topmost view of our view
UIImageView *imageView = [[UIImageView alloc] initWithImage:image] ; // image is a snapshot of view "A"
imageView.frame = self.view.bounds ;
[self.view addSubview:imageView] ;
[self.view bringSubviewToFront:imageView] ;

// Pre-transform our view (s=target/current, d=target-current)
CGAffineTransform transform = CGAffineTransformIdentity ;

// Translate our view
CGPoint center  = CGPointMake(screenOrigin.x + (image.size.width/2.0), screenOrigin.y + (image.size.height/2.0)) ;
float dX = center.x - self.view.center.x ;
float dY = center.y - self.view.center.y ;
NSLog( @"dx: %f, dy: %f" , dX, dY ) ;

transform = CGAffineTransformTranslate(transform, dX, dY) ;

// Scale our view
float scaleWFactor = image.size.width / self.view.frame.size.width ;
float scaleHFactor = image.size.height / self.view.frame.size.height ;
transform = CGAffineTransformScale(transform,scaleWFactor, scaleHFactor) ;

self.view.transform = transform ;

[[[UIApplication easybookDelegate] rootViewController].view addSubview:self.view] ;

// Perform the animation later since implicit animations don't seem to work due to
// view "B" just being added above and hasn't had a chance to become visible. Right now
// this is just on a timer for debugging purposes. It'll probably be moved elsewhere, probably
// to view "B"'s -didMoveToSuperview
double delayInSeconds = 0.3;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [UIView transitionWithView:self.view duration:3 options:UIViewAnimationOptionTransitionFlipFromRight| UIViewAnimationOptionCurveEaseOut animations:^(void) 
        {
        [imageView removeFromSuperview] ;
        self.view.transform = CGAffineTransformIdentity ;
        } 
        completion:^(BOOL finished){
                [self.view removeFromSuperview] ;
                [navController presentModalViewController:self animated:NO] ;
        }] ;
});

[imageView release];

回答1:

I've done this before. Here's the gist of how I did it:

  1. View A is on screen
  2. Create View B, give it a frame that makes it fullscreen, but don't add it to the screen yet
  3. View B has a UIImageView as it's topmost view
  4. Capture a UIImage of View A, and set it as the image of View B's image view

    UIGraphicsBeginImageContext(view.bounds.size); 
    [viewA.layer renderInContext:UIGraphicsGetCurrentContext()]; 
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    viewB.imageView.image = image;
    
  5. Set View B's transform (scale and translation) so that it is shrunk to the size of View A and is positioned where View A is on the screen

  6. Add View B to the screen
  7. At this point, the user hasn't noticed anything change, because View B looks exactly like View A
  8. Now start an animation block, in which you
    • Set [UIView setAnimationTransition: UIViewAnimationTransitionFlipFromLeft forView:ViewB
    • set View B's transform to CGAffineTransformIdentity
    • remove View B's imageview from View B

The result is an animation that looks like View A is flipping over and zooming to fill the screen with View B as the opposite side.



回答2:

Checkout this svn repository:

http://boondoggle.atomicwang.org/lemurflip/

This does flip two views.



回答3:

This is an easy example that i made for you :-) Start with it... Try to understand it... Read Documentation and then you will be able to do what you want ;-)

- (void)moveTable:(UITableView *)table toPoint:(CGPoint)point excursion:(CGFloat)excursion {

[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.5];
[table setCenter:point];

// scaling
CABasicAnimation *scalingAnimation = (CABasicAnimation *)[table.layer animationForKey:@"scaling"];
if (!scalingAnimation)
{
    scalingAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
    scalingAnimation.duration=0.5/2.0f;
    scalingAnimation.autoreverses=YES;
    scalingAnimation.removedOnCompletion = YES;
    scalingAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    scalingAnimation.fromValue=[NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0.0, 0.0, 0.0)];
    scalingAnimation.toValue=[NSValue valueWithCATransform3D:CATransform3DMakeTranslation(excursion, 0.0, 0.0)];
}
[table.layer addAnimation:scalingAnimation forKey:@"scaling"];
[UIView commitAnimations];

}



回答4:

Try something like this. As long as A's frame isn't fullscreen to start with then the animation should just flip A. When you add a subview to a view in a flip transition then it is added to the back of that view, so view A's frame is the one you want to modify.

[UIView beginAnimations:@"flipopen" context:nil];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self cache:TRUE];
[a addSubview:b];
a.frame = b.frame;
[UIView commitAnimations];


回答5:

I'm assuming you just want to change the image view's image while you flip and scale it. Following code flips the image view using default animation, scales it to the other view's frame and sets the image:

[UIView beginAnimations:nil context:nil];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.imageViewA cache:NO];
[UIView setAnimationDuration:2];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(animationFinished:)];
self.imageViewA.frame = self.imageViewB.frame;
self.imageViewA.image = self.imageViewB.image;
[UIView commitAnimations];

if you wish to show the A view as your tiny floating view again with some other image you can just set it's frame and image without the animation block in AnimationDidStopSelector method which you can set

-(void)animationFinished:(id)context
{
    self.imageViewA.frame = tinyRect;
    self.imageViewA.image = [UIImage imageNamed:@"a.jpg"];
}

where tinyRect is the floating A view's original frame.



回答6:

It's a little rough around the edges, but here's what I did using block animations. The following flips the view, centers it and goes fullscreen:

// the view on the other side of the card
UIView* redView = [[UIView alloc]initWithFrame:CGRectMake(0.0, 0.0, viewToFlip.frame.size.width, viewToFlip.frame.size.height)];
redView.backgroundColor = [UIColor redColor];

// add a green square to the red view so we have something else to look at
UIView* greenSquare = [[UIView alloc]initWithFrame:CGRectMake(10.0, 10.0, 20.0, 20.0)];
greenSquare.backgroundColor = [UIColor greenColor];    
[redView addSubview:greenSquare];

// the flip animation
[UIView animateWithDuration:0.25
                      delay:0.0
                    options:UIViewAnimationCurveLinear
                 animations:^{

                     // rotate 90 degrees
                     [viewToFlip.layer setTransform: CATransform3DRotate(viewToFlip.layer.transform, -M_PI/2, 0.0, 1.0, 0.0)];

                 } 
                 completion:^(BOOL finished){

                     // now that we're 90 degrees, swap one view for the other
                     // but if we add it now, the view will be backwards, so flip the original view back
                     [viewToFlip.layer setTransform: CATransform3DRotate(viewToFlip.layer.transform, M_PI, 0.0, 1.0, 0.0)];
                     [viewToFlip addSubview:redView];

                     // now let's continue our rotation
                     [UIView animateWithDuration:0.25
                                           delay:0.0
                                         options:UIViewAnimationCurveLinear
                                      animations:^{

                                          // rotate the last 90 degrees
                                          [viewToFlip.layer setTransform: CATransform3DRotate(viewToFlip.layer.transform, -M_PI/2, 0.0, 1.0, 0.0)];

                                      } 
                                      completion:^(BOOL finished){

                                          [UIView animateWithDuration:0.25
                                                                delay:0.0
                                                              options:UIViewAnimationCurveLinear
                                                           animations:^{

                                                               // animate the views to the center of the screen and adjust their bounds to fill up the screen
                                                               [viewToFlip setCenter:CGPointMake(self.view.center.x, self.view.center.y)];
                                                               [viewToFlip setBounds:CGRectMake(0.0, 0.0, self.view.frame.size.width, self.view.frame.size.height)];
                                                               [redView setCenter:CGPointMake(self.view.center.x, self.view.center.y)];
                                                               [redView setBounds:CGRectMake(0.0, 0.0, self.view.frame.size.width, self.view.frame.size.height)];
                                                           }
                                                           completion:nil];


                                      }];

                 }];