2011-03-28 9 views
9

Estoy tratando de lograr un tipo de transición de origami en dos UIView utilizando solo capacidades de capa. La idea es doblar dos vistas con un efecto de perspectiva. Ambas vistas tienen una perspectiva, la transición se define mediante una rotación en cada vista, así como una traducción en una vista, de modo que esta vista parece estar unida a la otra.Transición de Origami usando perspectiva CATransform3D

El problema es que la vista se superpone entre sí en el medio de la transición. No quiero usar zPosition para evitar visualmente esta superposición, realmente quiero que estas dos vistas actúen como si estuvieran unidas por su lado compartido. Aquí está el código para la transición.

¿Alguna idea o alguna otra solución?

Overlapping views during transition

- (void)animateWithPerspective 
{ 
    CGFloat rotationAngle = 90; 
    CATransform3D transform = CATransform3DIdentity; 
    UIView *topView; 
    UIView *bottomView; 
    UIView *mainView; 
    CGRect frame; 
    CGFloat size = 200; 

    mainView = [[UIView alloc] initWithFrame:CGRectMake(10,10, size, size*2)]; 
    [self.view addSubview:mainView]; 
    bottomView = [[UIView alloc] initWithFrame:CGRectZero]; 
    bottomView.layer.anchorPoint = CGPointMake(0.5, 1); 
    bottomView.frame = CGRectMake(0, size, size, size); 
    bottomView.backgroundColor = [UIColor blueColor]; 
    [mainView addSubview:bottomView]; 

    topView = [[UIView alloc] initWithFrame:CGRectZero]; 
    topView.layer.anchorPoint = CGPointMake(0.5, 0); 
    topView.frame = CGRectMake(0, 0, size, size); 
    topView.backgroundColor = [UIColor redColor]; 
    [mainView addSubview:topView]; 

    transform.m34 = 1.0/700.0; 
    topView.layer.transform = transform; 
    bottomView.layer.transform = transform; 

    [UIView beginAnimations:nil context:nil]; 
    [UIView setAnimationDuration:2]; 
    [UIView setAnimationRepeatAutoreverses:YES]; 
    [UIView setAnimationRepeatCount:INFINITY]; 
    [UIView setAnimationCurve:UIViewAnimationCurveLinear]; 
    frame = bottomView.frame; 
    frame.origin.y = bottomView.frame.origin.y - bottomView.frame.size.height - topView.frame.size.height; 
    bottomView.frame = frame; 
    topView.layer.transform = CATransform3DRotate(transform, rotationAngle * M_PI/180, 1, 0, 0); 
    bottomView.layer.transform = CATransform3DRotate(transform, -rotationAngle * M_PI/180, 1, 0, 0); 
    [UIView commitAnimations]; 
} 

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 
    [self animate]; 
} 

Para simplificar el problema, vamos a deshacernos de cualquier perspectiva de transformar. Aquí es un código más simple con el mismo tipo de problema:

- (void)animateWithoutPerspective 
{ 
    CGFloat rotationAngle = 90; 
    UIView *topView; 
    UIView *bottomView; 
    UIView *mainView; 
    CGRect frame; 
    CGFloat size = 200; 

    mainView = [[UIView alloc] initWithFrame:CGRectMake(10,10, size, size*2)]; 
    [self.view addSubview:mainView]; 
    bottomView = [[UIView alloc] initWithFrame:CGRectMake(0, size, size, size)]; 
    bottomView.backgroundColor = [UIColor blueColor]; 
    [mainView addSubview:bottomView]; 

    topView = [[UIView alloc] initWithFrame:CGRectZero]; 
    topView.layer.anchorPoint = CGPointMake(0.5, 0); 
    topView.frame = CGRectMake(10, 0, size-20, size); 
    topView.backgroundColor = [UIColor redColor]; 
    [mainView addSubview:topView]; 

    [UIView beginAnimations:nil context:nil]; 
    [UIView setAnimationDuration:2]; 
    [UIView setAnimationRepeatAutoreverses:YES]; 
    [UIView setAnimationRepeatCount:INFINITY]; 
    [UIView setAnimationCurve:UIViewAnimationCurveLinear]; 
    frame = bottomView.frame; 
    frame.origin.y = bottomView.frame.origin.y - bottomView.frame.size.height; 
    bottomView.frame = frame; 
    topView.layer.transform = CATransform3DMakeRotation(rotationAngle * M_PI/180, 1, 0, 0); 
    [UIView commitAnimations]; 
} 

Respuesta

21

Finalmente, aquí hay alguna solución para una animación de tres mangas con sombras simples añadidas. La clave para resolver este tipo de animación es utilizar varias subcapas bien organizadas y también algunas CATransformLayer.

- (void)animate 
{ 
    CATransform3D transform = CATransform3DIdentity; 
    CALayer *topSleeve; 
    CALayer *middleSleeve; 
    CALayer *bottomSleeve; 
    CALayer *topShadow; 
    CALayer *middleShadow; 
    UIView *mainView; 
    CGFloat width = 300; 
    CGFloat height = 150; 
    CALayer *firstJointLayer; 
    CALayer *secondJointLayer; 
    CALayer *perspectiveLayer; 

    mainView = [[UIView alloc] initWithFrame:CGRectMake(50, 50, width, height*3)]; 
    mainView.backgroundColor = [UIColor yellowColor]; 
    [self.view addSubview:mainView]; 

    perspectiveLayer = [CALayer layer]; 
    perspectiveLayer.frame = CGRectMake(0, 0, width, height*2); 
    [mainView.layer addSublayer:perspectiveLayer]; 

    firstJointLayer = [CATransformLayer layer]; 
    firstJointLayer.frame = mainView.bounds; 
    [perspectiveLayer addSublayer:firstJointLayer]; 

    topSleeve = [CALayer layer]; 
    topSleeve.frame = CGRectMake(0, 0, width, height); 
    topSleeve.anchorPoint = CGPointMake(0.5, 0); 
    topSleeve.backgroundColor = [UIColor redColor].CGColor; 
    topSleeve.position = CGPointMake(width/2, 0); 
    [firstJointLayer addSublayer:topSleeve]; 
    topSleeve.masksToBounds = YES; 

    secondJointLayer = [CATransformLayer layer]; 
    secondJointLayer.frame = mainView.bounds; 
    secondJointLayer.frame = CGRectMake(0, 0, width, height*2); 
    secondJointLayer.anchorPoint = CGPointMake(0.5, 0); 
    secondJointLayer.position = CGPointMake(width/2, height); 
    [firstJointLayer addSublayer:secondJointLayer]; 

    middleSleeve = [CALayer layer]; 
    middleSleeve.frame = CGRectMake(0, 0, width, height); 
    middleSleeve.anchorPoint = CGPointMake(0.5, 0); 
    middleSleeve.backgroundColor = [UIColor blueColor].CGColor; 
    middleSleeve.position = CGPointMake(width/2, 0); 
    [secondJointLayer addSublayer:middleSleeve]; 
    middleSleeve.masksToBounds = YES; 

    bottomSleeve = [CALayer layer]; 
    bottomSleeve.frame = CGRectMake(0, height, width, height); 
    bottomSleeve.anchorPoint = CGPointMake(0.5, 0); 
    bottomSleeve.backgroundColor = [UIColor grayColor].CGColor; 
    bottomSleeve.position = CGPointMake(width/2, height); 
    [secondJointLayer addSublayer:bottomSleeve]; 

    firstJointLayer.anchorPoint = CGPointMake(0.5, 0); 
    firstJointLayer.position = CGPointMake(width/2, 0); 

    topShadow = [CALayer layer]; 
    [topSleeve addSublayer:topShadow]; 
    topShadow.frame = topSleeve.bounds; 
    topShadow.backgroundColor = [UIColor blackColor].CGColor; 
    topShadow.opacity = 0; 

    middleShadow = [CALayer layer]; 
    [middleSleeve addSublayer:middleShadow]; 
    middleShadow.frame = middleSleeve.bounds; 
    middleShadow.backgroundColor = [UIColor blackColor].CGColor; 
    middleShadow.opacity = 0; 

    transform.m34 = -1.0/700.0; 
    perspectiveLayer.sublayerTransform = transform; 

    CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.x"]; 
    [animation setDuration:2]; 
    [animation setAutoreverses:YES]; 
    [animation setRepeatCount:INFINITY]; 
    [animation setFromValue:[NSNumber numberWithDouble:0]]; 
    [animation setToValue:[NSNumber numberWithDouble:-90*M_PI/180]]; 
    [firstJointLayer addAnimation:animation forKey:nil]; 

    animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.x"]; 
    [animation setDuration:2]; 
    [animation setAutoreverses:YES]; 
    [animation setRepeatCount:INFINITY]; 
    [animation setFromValue:[NSNumber numberWithDouble:0]]; 
    [animation setToValue:[NSNumber numberWithDouble:180*M_PI/180]]; 
    [secondJointLayer addAnimation:animation forKey:nil]; 

    animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.x"]; 
    [animation setDuration:2]; 
    [animation setAutoreverses:YES]; 
    [animation setRepeatCount:INFINITY]; 
    [animation setFromValue:[NSNumber numberWithDouble:0]]; 
    [animation setToValue:[NSNumber numberWithDouble:-90*M_PI/180]]; 
    [bottomSleeve addAnimation:animation forKey:nil]; 

    animation = [CABasicAnimation animationWithKeyPath:@"bounds.size.height"]; 
    [animation setDuration:2]; 
    [animation setAutoreverses:YES]; 
    [animation setRepeatCount:INFINITY]; 
    [animation setFromValue:[NSNumber numberWithDouble:perspectiveLayer.bounds.size.height]]; 
    [animation setToValue:[NSNumber numberWithDouble:0]]; 
    [perspectiveLayer addAnimation:animation forKey:nil]; 

    animation = [CABasicAnimation animationWithKeyPath:@"position.y"]; 
    [animation setDuration:2]; 
    [animation setAutoreverses:YES]; 
    [animation setRepeatCount:INFINITY]; 
    [animation setFromValue:[NSNumber numberWithDouble:perspectiveLayer.position.y]]; 
    [animation setToValue:[NSNumber numberWithDouble:0]]; 
    [perspectiveLayer addAnimation:animation forKey:nil]; 

    animation = [CABasicAnimation animationWithKeyPath:@"opacity"]; 
    [animation setDuration:2]; 
    [animation setAutoreverses:YES]; 
    [animation setRepeatCount:INFINITY]; 
    [animation setFromValue:[NSNumber numberWithDouble:0]]; 
    [animation setToValue:[NSNumber numberWithDouble:0.5]]; 
    [topShadow addAnimation:animation forKey:nil]; 

    animation = [CABasicAnimation animationWithKeyPath:@"opacity"]; 
    [animation setDuration:2]; 
    [animation setAutoreverses:YES]; 
    [animation setRepeatCount:INFINITY]; 
    [animation setFromValue:[NSNumber numberWithDouble:0]]; 
    [animation setToValue:[NSNumber numberWithDouble:0.5]]; 
    [middleShadow addAnimation:animation forKey:nil]; 
} 
+0

+1 Wonderful stuff! – haroldcampbell

+0

Muchas gracias por poner CATransformLayer en mi radar. Después de mucho rechinar de dientes, finalmente estoy obteniendo resultados. – fzwo

+0

http://stackoverflow.com/questions/13559243/vertical-fold-animation .. Cuando intenté lo mismo para la vertical, me estoy invirtiendo la vista de la animación ... He publicado el enlace ... ¿Pueden darme alguna ayuda? –

1

Al principio pensé que la transformación lineal de la posición Y no implicaría una transformación lineal de la rotación, pero parece que es el caso.

El error es muy simple, el valor de la perspectiva es incorrecto, la perspectiva se modela colocando un observatorio en el eje Z a una distancia negativa. entonces necesita negar el valor de la perspectiva:

transform.m34 = 1.0/(-700.0); 

Y funciona como se esperaba.

Solo para el registro, la transformación no es lineal para los ángulos. pero el artefacto está oculto por el zbuffer.

En la mitad del camino, el ángulo sería de 60 grados, pero con la animación lineal obtenemos 45 grados. Pero mirando desde el lado derecho, desde la posición negativa del eje Z, el buffer oculta la intersección de los planos.

3

versión de Swift respuesta Phil 's

func animate() { 
    var transform:CATransform3D = CATransform3DIdentity; 
    var topSleeve:CALayer 
    var middleSleeve:CALayer 
    var bottomSleeve:CALayer 
    var topShadow:CALayer 
    var middleShadow:CALayer 
    var mainView:UIView 
    var width:CGFloat = 300 
    var height:CGFloat = 150 
    var firstJointLayer:CALayer 
    var secondJointLayer:CALayer 
    var perspectiveLayer:CALayer 

    mainView = UIView(frame:CGRectMake(50, 50, width, height*3)) 
    mainView.backgroundColor = UIColor.yellowColor() 
    view.addSubview(mainView) 

    perspectiveLayer = CALayer() 
    perspectiveLayer.frame = CGRectMake(0, 0, width, height*2) 
    mainView.layer.addSublayer(perspectiveLayer) 

    firstJointLayer = CATransformLayer() 
    firstJointLayer.frame = mainView.bounds; 
    perspectiveLayer.addSublayer(firstJointLayer) 

    topSleeve = CALayer() 
    topSleeve.frame = CGRectMake(0, 0, width, height); 
    topSleeve.anchorPoint = CGPointMake(0.5, 0) 
    topSleeve.backgroundColor = UIColor.redColor().CGColor; 
    topSleeve.position = CGPointMake(width/2, 0) 
    firstJointLayer.addSublayer(topSleeve) 
    topSleeve.masksToBounds = true 

    secondJointLayer = CATransformLayer() 
    secondJointLayer.frame = mainView.bounds; 
    secondJointLayer.frame = CGRectMake(0, 0, width, height*2) 
    secondJointLayer.anchorPoint = CGPointMake(0.5, 0) 
    secondJointLayer.position = CGPointMake(width/2, height) 
    firstJointLayer.addSublayer(secondJointLayer) 

    middleSleeve = CALayer() 
    middleSleeve.frame = CGRectMake(0, 0, width, height); 
    middleSleeve.anchorPoint = CGPointMake(0.5, 0) 
    middleSleeve.backgroundColor = UIColor.blueColor().CGColor 
    middleSleeve.position = CGPointMake(width/2, 0) 
    secondJointLayer.addSublayer(middleSleeve) 
    middleSleeve.masksToBounds = true 

    bottomSleeve = CALayer() 
    bottomSleeve.frame = CGRectMake(0, height, width, height) 
    bottomSleeve.anchorPoint = CGPointMake(0.5, 0) 
    bottomSleeve.backgroundColor = UIColor.grayColor().CGColor 
    bottomSleeve.position = CGPointMake(width/2, height) 
    secondJointLayer.addSublayer(bottomSleeve) 

    firstJointLayer.anchorPoint = CGPointMake(0.5, 0) 
    firstJointLayer.position = CGPointMake(width/2, 0) 

    topShadow = CALayer() 
    topSleeve.addSublayer(topShadow) 
    topShadow.frame = topSleeve.bounds 
    topShadow.backgroundColor = UIColor.blackColor().CGColor 
    topShadow.opacity = 0 

    middleShadow = CALayer() 
    middleSleeve.addSublayer(middleShadow) 
    middleShadow.frame = middleSleeve.bounds 
    middleShadow.backgroundColor = UIColor.blackColor().CGColor 
    middleShadow.opacity = 0 

    transform.m34 = -1/700 
    perspectiveLayer.sublayerTransform = transform; 

    var animation = CABasicAnimation(keyPath: "transform.rotation.x") 
    animation.duration = 2 
    animation.autoreverses = true 
    animation.repeatCount = 1000 
    animation.fromValue = 0 
    animation.toValue = -90*M_PI/180 
    firstJointLayer.addAnimation(animation, forKey: nil) 

    animation = CABasicAnimation(keyPath: "transform.rotation.x") 
    animation.duration = 2 
    animation.autoreverses = true 
    animation.repeatCount = 1000 
    animation.fromValue = 0 
    animation.toValue = 180*M_PI/180 
    secondJointLayer.addAnimation(animation, forKey: nil) 

    animation = CABasicAnimation(keyPath: "transform.rotation.x") 
    animation.duration = 2 
    animation.autoreverses = true 
    animation.repeatCount = 1000 
    animation.fromValue = 0 
    animation.toValue = -90*M_PI/180 
    bottomSleeve.addAnimation(animation, forKey: nil) 

    animation = CABasicAnimation(keyPath: "bounds.size.height") 
    animation.duration = 2 
    animation.autoreverses = true 
    animation.repeatCount = 1000 
    animation.fromValue = perspectiveLayer.bounds.size.height 
    animation.toValue = 0 
    perspectiveLayer.addAnimation(animation, forKey: nil) 


    animation = CABasicAnimation(keyPath: "position.y") 
    animation.duration = 2 
    animation.autoreverses = true 
    animation.repeatCount = 1000 
    animation.fromValue = perspectiveLayer.position.y 
    animation.toValue = 0 
    perspectiveLayer.addAnimation(animation, forKey: nil) 

    animation = CABasicAnimation(keyPath: "opacity") 
    animation.duration = 2 
    animation.autoreverses = true 
    animation.repeatCount = 1000 
    animation.fromValue = 0 
    animation.toValue = 0.5 
    topShadow.addAnimation(animation, forKey: nil) 

    animation = CABasicAnimation(keyPath: "opacity") 
    animation.duration = 2 
    animation.autoreverses = true 
    animation.repeatCount = 1000 
    animation.fromValue = 0 
    animation.toValue = 0.5 
    middleShadow.addAnimation(animation, forKey: nil) 
} 
0

Para ilustrar las respuestas.

No puse todas las animaciones y la proyección en perspectiva (el perspectiveLayer.sublayerTransform en sus subtransmisores CATransformLayer). Juega con el valor de campo de la matriz de proyección m34 para ver cómo afecta el punto de fuga.

Stack of layers

Cuestiones relacionadas