如何在同时切换子视图的情况下沿x轴翻转UIView

9
这个问题之前已经被问过,但是以稍微不同的方式提出来,我无法得到任何满意的答案,因此我希望有很强的核心动画技能的人可以帮助我。
我有一组卡片在桌子上。当用户向上或向下滑动卡片组时,卡片会沿着桌子上下移动。屏幕上每次只显示4张卡片,但只有第二张卡片显示其正面。当用户滑动第二张卡片时,它会翻转回正面,然后下一张卡片(根据滑动方向)就会落在它的位置并显示其正面。
我已经像这样设置了我的卡片视图类:
@interface WLCard : UIView {
    UIView *_frontView;
    UIView *_backView;
    BOOL flipped;
}

我尝试使用以下代码翻转卡片:

- (void) flipCard {
    [self.flipTimer invalidate];
    if (flipped){
        return;
    }

    id animationsBlock = ^{
            self.backView.alpha = 1.0f;
            self.frontView.alpha = 0.0f;
            [self bringSubviewToFront:self.frontView];
            flipped = YES;

            CALayer *layer = self.layer;
            CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
            rotationAndPerspectiveTransform.m34 = 1.0 / 500;
            rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, M_PI, 1.0f, 0.0f, 0.0f);
            layer.transform = rotationAndPerspectiveTransform;
    };
    [UIView animateWithDuration:0.25
                          delay:0.0
                        options: UIViewAnimationCurveEaseInOut
                     animations:animationsBlock
                     completion:nil];

}

这段代码能够运行,但是存在以下问题,我无法解决:
  1. 只有卡片横轴一半被动画化了。
  2. 一旦翻转,卡片的正面是倒立和镜像的。
  3. 一旦我翻转了卡片,就再也无法运行动画了。换句话说,我可以运行多次动画块,但只有第一次会有动画效果。之后尝试运行动画都只会在子视图之间产生淡入淡出的效果。

此外,请注意我需要能够与卡片的正面进行交互。即:它上面有按钮。

如果有人遇到这些问题并找到了解决方案,那么分享一下你的解决方案将会非常棒。更好的做法是添加透视变换到动画中,使其更具真实感。

5个回答

12

这个问题的解决比我想象的要简单得多,我不需要使用任何CoreAnimation库来实现效果。感谢@Aaron Hayman提供的线索。我使用了transitionWithView:duration:options:animations:completion

我的实现在容器视图中:

    [UIView transitionWithView:self 
                      duration:0.2 
                       options:UIViewAnimationOptionTransitionFlipFromBottom
                    animations: ^{
                            [self.backView removeFromSuperview];
                            [self addSubview:self.frontView];
                    }
                    completion:NULL];

关键是使用了UIViewAnimationOptionTransitionFlipFromBottom选项。顺便提一下,苹果在他们的文档中也有这段代码。您还可以在块中添加其他动画,如调整大小和移动。


3

好的,这不是一个完整的解决方案,但我会指出一些可能有用的东西。我不是核心动画专家,但我在我的程序中做了一些3D旋转。

首先,视图没有“后面”。所以如果你将某个东西旋转180度(M_PI),你将从后面看这个视图(这就是为什么它是倒立/镜像的原因)。

我不确定你的意思是什么:

只有半张卡片沿x轴被动画化。

但是,考虑到你的锚点(旋转发生的点)可能会有所帮助。它通常在中心,但通常情况下你需要它变成其他样子。请注意,锚点是作为比例(百分比/100)表示的...因此值为0-1.0f。你只需要设置一次(除非你需要更改)。以下是访问锚点的方法:

layer.anchorPoint = CGPointMake(0.5f, 0.5f) // 这是中心

动画只运行一次的原因是因为变换是绝对的,而不是累积的。考虑到你总是从标识变换开始,然后修改它,这就有意义了...但基本上,没有动画发生,因为第二次没有什么可以动画(视图已经处于你请求它处于的状态中)。

如果你正在从一个视图动画到另一个视图(而你不能使用[UIView transitionWithView:duration:options:animations:completion:];),你将需要使用两个阶段的动画。在第一阶段的动画中,对于要翻转到背面的“卡片”,你将把要消失的视图旋转“上/下/其他”到M_PI_2(此时它将被“隐藏”或不可见,因为它被旋转了)。在第二阶段中,你将把要消失的视图的背面旋转到0(这应该是标识变换...也就是视图的正常状态)。此外,你还必须对出现的“卡片”做完全相反的操作(到正面)。你可以通过在第一个动画的完成块中实现另一个[UIView animateWithDuration:...]来实现这一点。我要警告你,这样做可能会有点复杂。特别是因为你希望视图有一个“背面”,这基本上需要动画化4个视图(要消失的视图、要出现的视图、要消失的视图的背面和要出现的视图的背面)。最后,在第二个动画的完成块中,你可以做一些清理工作(重置旋转的视图并使它们的alpha为0.0f等)。

我知道这很复杂,所以你可能想阅读一些关于核心动画的教程。


谢谢Aaron。transitionWithView方法给了我解决问题的线索。话虽如此,你确实让我更好地理解了3D动画。是时候投资一本好的核心动画书了。 - Michael Gaylord

1

@Aaron有一些你应该阅读的好信息。

最简单的解决方案是使用CATransformLayer,它将允许您放置其他CALayer并保持它们的3D层次结构。

例如,要创建一个具有正面和背面的“卡片”,您可以这样做:

CATransformLayer *cardContainer = [CATransformLayer layer];
cardContainer.frame = // some frame;

CALayer *cardFront  = [CALayer layer];
cardFront.frame     = cardContainer.bounds;
cardFront.zPosition = 2;   // Higher than the zPosition of the back of the card
cardFront.contents  = (id)[UIImage imageNamed:@"cardFront"].CGImage;
[cardContainer addSublayer:cardFront];

CALayer *cardBack  = [CALayer layer];
cardBack.frame     = cardContainer.bounds;
cardBack.zPosition = 1;
cardBack.contents  = (id)[UIImage imageNamed:@"cardBack"].CGImage; // You may need to mirror this image
[cardContainer addSublayer:cardBack];

你现在可以将这个 transform 应用到 cardContainer 上,实现翻转卡片效果。


虽然这种方法可以工作,但我需要在卡片上拥有带交互元素的UIView,因为用户需要与卡片面进行交互。仅使用CALayer只允许我处理图像等内容。 - Michael Gaylord
将此层次结构放置在包含交互的 UIView 中... - Paul.s

0

根据 Paul.s 的代码,这是针对 Swift 3 更新的,可以对卡片进行对角线翻转:

func createLayers(){
  transformationLayer = CATransformLayer(layer: CALayer())
  transformationLayer.frame = CGRect(x: 15, y: 100, width: view.frame.width - 30, height: view.frame.width - 30)

  let black = CALayer()
  black.zPosition = 2
  black.frame = transformationLayer.bounds
  black.backgroundColor = UIColor.black.cgColor

  transformationLayer.addSublayer(black)

  let blue = CALayer()
  blue.frame = transformationLayer.bounds
  blue.zPosition = 1
  blue.backgroundColor = UIColor.blue.cgColor

  transformationLayer.addSublayer(blue)

  let tgr = UITapGestureRecognizer(target: self, action: #selector(recTap))
  view.addGestureRecognizer(tgr)
  view.layer.addSublayer(transformationLayer)
}

实现完整的360度动画,但由于图层具有不同的zPosition,因此图层的不同“面”将显示出来。

func recTap(){
    let animation = CABasicAnimation(keyPath: "transform")
    animation.delegate = self
    animation.duration = 2.0
    animation.fillMode = kCAFillModeForwards
    animation.isRemovedOnCompletion = false
    animation.toValue = NSValue(caTransform3D: CATransform3DMakeRotation(CGFloat(Float.pi), 1, -1, 0))
    transformationLayer.add(animation, forKey: "arbitrarykey")
  }

0

@Paul.s

我按照您的方式使用了卡片容器,但是当我在卡片容器上应用旋转动画时,只有第一张卡片的一半会围绕自身旋转,最后整个视图才会出现。每次动画中都会缺少一侧。


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