使用CATransform3D透视的Origami转换

时间:2011-03-28 08:55:03

标签: iphone animation calayer perspective catransform3d

我正在尝试仅使用图层功能在两个UIView上实现一种折纸转换。我们的想法是用透视效果折叠两个视图。两个视图都有透视,过渡由每个视图上的旋转定义,以及一个视图上的翻译,使得该视图似乎附加到另一个视图。

问题是视图在转换过程中彼此重叠。我不想使用zPosition来直观地避免这种重叠,我真的希望这两个视图就好像它们被共享端绑定在一起一样。以下是转换的代码。

任何想法或任何其他解决方案?

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

为了简化问题,让我们摆脱任何透视变换。这是一个更简单的代码,具有相同的问题:

- (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];
}

4 个答案:

答案 0 :(得分:21)

最后,这里有一个三袖动画的解决方案,添加了简单的阴影。解决这种动画的关键是使用几个组织良好的子层,还有一些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];
}

答案 1 :(得分:1)

起初我认为Y位置的线性变换并不意味着旋转的线性变换,但似乎是这种情况。

错误非常简单,透视值错误,透视图通过在Z轴上以负距离定位天文台来建模。那么你需要否定透视值:

transform.m34 = 1.0/(-700.0);

它确实像预期的那样工作。

仅为记录,角度的变换不是线性的。但是这个工件被zbuffer隐藏了。

在中间路径,角度为60度,但线性动画得到45度。但从右侧看,从负Z轴位置看,缓冲区隐藏了平面交叉点。

答案 2 :(得分:0)

说明答案。

我没有将所有动画和透视投影(perspectiveLayer.sublayerTransform放在其CATransformLayer子图层上)。使用投影矩阵m34字段值来观察它如何影响消失点。

Stack of layers

答案 3 :(得分:0)

Swift 4更新了答案:

Vojtec答案的更新版本。

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

    mainView = UIView(frame:CGRect(x: 50, y: 50, width: width, height: height*3))
    mainView.backgroundColor = UIColor.yellow
    view.addSubview(mainView)

    perspectiveLayer = CALayer()
    perspectiveLayer.frame = CGRect(x: 0, y: 0, width: width, height: height*2)
    mainView.layer.addSublayer(perspectiveLayer)

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

    topSleeve = CALayer()
    topSleeve.frame = CGRect(x: 0, y: 0, width: width, height: height)
    topSleeve.anchorPoint = CGPoint(x: 0.5, y: 0)
    topSleeve.backgroundColor = UIColor.red.cgColor
    topSleeve.position = CGPoint(x: width/2, y: 0)
    firstJointLayer.addSublayer(topSleeve)
    topSleeve.masksToBounds = true

    secondJointLayer = CATransformLayer()
    secondJointLayer.frame = mainView.bounds;
    secondJointLayer.frame = CGRect(x: 0, y: 0, width: width, height: height*2)
    secondJointLayer.anchorPoint = CGPoint(x: 0.5, y: 0)
    secondJointLayer.position = CGPoint(x: width/2, y: height)
    firstJointLayer.addSublayer(secondJointLayer)

    middleSleeve = CALayer()
    middleSleeve.frame = CGRect(x: 0, y: 0, width: width, height: height);
    middleSleeve.anchorPoint = CGPoint(x: 0.5, y: 0)
    middleSleeve.backgroundColor = UIColor.blue.cgColor
    middleSleeve.position = CGPoint(x: width/2, y: 0)
    secondJointLayer.addSublayer(middleSleeve)
    middleSleeve.masksToBounds = true

    bottomSleeve = CALayer()
    bottomSleeve.frame = CGRect(x: 0, y: height, width: width, height: height)
    bottomSleeve.anchorPoint = CGPoint(x: 0.5, y: 0)
    bottomSleeve.backgroundColor = UIColor.gray.cgColor
    bottomSleeve.position = CGPoint(x: width/2, y: height)
    secondJointLayer.addSublayer(bottomSleeve)

    firstJointLayer.anchorPoint = CGPoint(x: 0.5, y: 0)
    firstJointLayer.position = CGPoint(x: width/2, y: 0)

    topShadow = CALayer()
    topSleeve.addSublayer(topShadow)
    topShadow.frame = topSleeve.bounds
    topShadow.backgroundColor = UIColor.black.cgColor
    topShadow.opacity = 0

    middleShadow = CALayer()
    middleSleeve.addSublayer(middleShadow)
    middleShadow.frame = middleSleeve.bounds
    middleShadow.backgroundColor = UIColor.black.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*Double.pi/180
    firstJointLayer.add(animation, forKey: nil)

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

    animation = CABasicAnimation(keyPath: "transform.rotation.x")
    animation.duration = 2
    animation.autoreverses = true
    animation.repeatCount = 1000
    animation.fromValue = 0
    animation.toValue = -90*Double.pi/180
    bottomSleeve.add(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.add(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.add(animation, forKey: nil)

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

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