Переход оригами с использованием перспективы CATransform3D - PullRequest
9 голосов
/ 28 марта 2011

Я пытаюсь добиться своего рода перехода оригами на двух 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 ]

21 голосов
/ 04 апреля 2011

Наконец, вот несколько решений для анимации с тремя рукавами с добавлением простых теней. Ключом к решению этого вида анимации является использование нескольких хорошо организованных подслоев, а также некоторых 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];
}
3 голосов
/ 25 августа 2015

Swift версия ответа Phil

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 голос
/ 03 апреля 2011

Сначала я думал, что линейное преобразование положения Y не подразумевает линейное преобразование вращения, но, похоже, это так.

Ошибка очень проста, значение перспективы неверно, перспектива моделируется путем расположения обсерватории на оси Z на отрицательном расстоянии. тогда вам нужно отрицать значение перспективы:

transform.m34 = 1.0/(-700.0);

И он работает как ожидалось.

Просто для записи, преобразование не является линейным для углов. но артефакт спрятан zbuffer.

На середине пути угол будет 60 градусов, но с линейной анимацией мы получим 45 градусов. Но если смотреть с правой стороны, с отрицательного положения оси Z, буфер скрывает пересечение плоскостей.

0 голосов
/ 31 мая 2018

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)
}
}
0 голосов
/ 10 марта 2016

Для иллюстрации ответов.

Я не поместил все анимации и перспективную проекцию (perspectiveLayer.sublayerTransform на его CATransformLayer подслоях).Поиграйте со значением поля матрицы проекции m34, чтобы увидеть, как оно влияет на точку схода.

Stack of layers

...