使用CATransform3D透视来进行折纸转换

10
我正在尝试使用仅层功能实现两个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];
}
5个回答

22

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

非常感谢让我注意到CATransformLayer。经过长时间的努力,我终于获得了结果。 - fzwo
当我尝试垂直方向时,我得到了反转的视图动画。 我已经发布了链接。你能给些帮助吗? - Sasi
你能解释一下不同层之间的关系吗?为什么会这样工作? - 2cupsOfTech

4

Phil的答案的Swift版本

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)
}

1

起初我认为Y轴位置的线性变换不会导致旋转的线性变换,但事实证明确实如此。

错误非常简单,透视值是错误的,透视是通过在Z轴上定位一个观测站并将其距离设为负来建模的。然后您需要取反透视值:

transform.m34 = 1.0/(-700.0);

它按预期工作。

仅供记录,角度的变换不是线性的。但是这些伪影被z缓冲隐藏了。

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


1
为了说明答案。我没有放置所有的动画和透视投影(在其CATransformLayer子层上的perspectiveLayer.sublayerTransform)。尝试调整投影矩阵m34字段值,看看它如何影响消失点。

Stack of layers


1

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)
}
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接