使用 CATransform3D 透视的折纸过渡

发布于 2024-10-27 09:20:53 字数 3293 浏览 2 评论 0原文

我试图仅使用图层功能在两个 UIView 上实现一种折纸过渡。这个想法是折叠两个具有透视效果的视图。两个视图都有一个透视图,过渡是通过每个视图上的旋转以及一个视图上的平移来定义的,使得该视图似乎附加到另一个视图上。

问题在于视图在转换过程中彼此重叠。我不想使用 zPosition 在视觉上避免这种重叠,我真的希望这两个视图表现得好像它们通过共享的一面绑定在一起。这是转换的代码。

有什么想法,或者有其他解决方案吗?

过渡期间重叠视图

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

I'm trying to achieve a kind of origami transition on two UIView using only layer capabilities. The idea is to fold two views with a perspective effect. Both views have a perspective, the transition is defined by a rotation on each view, as well as a translation on one view such that this view seems to be attached to the other one.

The issue is that the view overlaps one another in the middle of the transition. I don't want to use a zPosition to visually avoid this overlapping, I really want these two views to act as if they were bound together by their shared side. Here is the code for the transition.

Any idea, or any other solution?

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

To simplify the problem, let's get rid of any perspective transform. Here is a simpler code with the same kind of issue:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

久随 2024-11-03 09:20:54

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

Finally, here is some solution for a three-sleeves animation with simple shadows added. The key to solve this kind of animation is to use several well organized sublayers and also some 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];
}
贪恋 2024-11-03 09:20:54

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

Swift version of Phil's answer

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)
}
↘紸啶 2024-11-03 09:20:54

起初我认为 Y 位置的线性变换并不意味着旋转的线性变换,但似乎确实如此。

错误很简单,透视值错误,透视是通过在Z轴负距离处放置一个天文台来建模的。那么你需要否定透视值:

transform.m34 = 1.0/(-700.0);

它确实按预期工作。

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

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

At first I though that the linear transformation of the Y position would not imply a linear transformation of the rotation, but it seems that it is the case.

The error is very simple, the perspective value is wrong, the perspective is modeled by positioning an observatory on the Z axis at a negative distance. then you need to negate the perspective value :

transform.m34 = 1.0/(-700.0);

And it does work like expected.

Just for the record, the transformation is not linear for the angles. but the artifact are hidden by the zbuffer.

At mid path the angle would be 60 degree but with the linear animation we get 45 degree. But looking from the right side, from negative Z axis position, the buffer hide the planes intersection.

妳是的陽光 2024-11-03 09:20:54

为了说明答案。

我没有放置所有动画和透视投影(perspectiveLayer.sublayerTransform 位于其 CATransformLayer 子层上)。使用投影矩阵 m34 字段值来查看它如何影响消失点。

堆栈层

To illustrate the answers.

I did not put all the animations and the perspective projection (the perspectiveLayer.sublayerTransform on its CATransformLayer subLayers). Play with the projection matrix m34 field value to see how it affects the vanishing point.

Stack of layers

晒暮凉 2024-11-03 09:20:54

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

Swift 4 updated answer:

updated version of Vojtec's answer.

    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)
}
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文