两层UIView绕X/Y轴3D旋转翻转动画显示异常问题咨询
解决3D旋转视图切换时始终显示顶层的问题
我之前也遇到过一模一样的问题!核心原因是Core Animation默认的图层绘制顺序是基于平面的层级(也就是subviews的添加顺序),而不是3D空间中的实际位置,再加上如果没开启透视效果,旋转后的图层深度不会被正确计算。下面给你几个具体的解决办法:
1. 先给容器视图开启3D透视效果
这是基础,没有透视的话,3D旋转只是平面上的形变,系统不会根据图层的3D位置调整绘制顺序。给承载两个子视图的容器视图设置subLayerTransform:
// Objective-C 代码 CATransform3D perspectiveTransform = CATransform3DIdentity; // m34 控制透视强度,值越小(绝对值越大)透视越明显,500是比较适中的数值 perspectiveTransform.m34 = -1.0 / 500.0; self.containerView.layer.sublayerTransform = perspectiveTransform;
// Swift 代码 var perspectiveTransform = CATransform3DIdentity perspectiveTransform.m34 = -1.0 / 500.0 containerView.layer.sublayerTransform = perspectiveTransform
2. 在旋转到π/2时手动交换图层的zPosition
当旋转角度超过90度(π/2)时,顶层视图在3D空间中已经转到了底层视图的后方,但系统还是会按原来的层级绘制它。这时候我们需要在动画的中间点交换两个视图的layer.zPosition:
方法一:用CAKeyframeAnimation+代理监听进度
CABasicAnimation *rotateAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"]; rotateAnimation.fromValue = @0; rotateAnimation.toValue = @M_PI; rotateAnimation.duration = 0.6; rotateAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; rotateAnimation.fillMode = kCAFillModeForwards; rotateAnimation.removedOnCompletion = NO; rotateAnimation.delegate = self; // 添加动画到容器视图或者顶层视图的layer [self.topView.layer addAnimation:rotateAnimation forKey:@"flipAnimation"]; // 在代理方法中监听动画进度,交换zPosition - (void)animationDidStart:(CAAnimation *)anim { CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateZPosition:)]; [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; // 把link存起来,动画结束后移除 self.displayLink = link; } - (void)updateZPosition:(CADisplayLink *)link { // 获取当前旋转角度 CGFloat currentAngle = [(NSNumber *)[self.topView.layer valueForKeyPath:@"transform.rotation.y"] floatValue]; if (currentAngle > M_PI_2 && !self.hasSwappedZPosition) { // 交换两个视图的zPosition CGFloat tempZ = self.topView.layer.zPosition; self.topView.layer.zPosition = self.bottomView.layer.zPosition; self.bottomView.layer.zPosition = tempZ; self.hasSwappedZPosition = YES; } else if (currentAngle < M_PI_2 && self.hasSwappedZPosition) { // 如果是反向旋转,再交换回来 CGFloat tempZ = self.topView.layer.zPosition; self.topView.layer.zPosition = self.bottomView.layer.zPosition; self.bottomView.layer.zPosition = tempZ; self.hasSwappedZPosition = NO; } } - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { [self.displayLink invalidate]; self.displayLink = nil; // 动画结束后,把视图的transform设置为最终状态,避免动画移除后回到初始状态 self.topView.layer.transform = CATransform3DRotate(CATransform3DIdentity, M_PI, 0, 1, 0); }
方法二:用UIView动画块+UIViewPropertyAnimator(Swift)
如果用Swift的话,用UIViewPropertyAnimator可以更方便地监控动画进度:
let animator = UIViewPropertyAnimator(duration: 0.6, curve: .easeInOut) { self.topView.transform = CGAffineTransform(rotationAngle: .pi) } animator.addAnimations({ // 交换zPosition的逻辑 let tempZ = self.topView.layer.zPosition self.topView.layer.zPosition = self.bottomView.layer.zPosition self.bottomView.layer.zPosition = tempZ }, delayFactor: 0.5) // 延迟0.5倍时长,也就是动画到一半的时候执行交换 animator.addCompletion { _ in // 确保最终状态正确 self.topView.transform = .identity // 如果是循环旋转的话,或者设置到最终旋转状态 } animator.startAnimation()
3. 额外优化:关闭顶层视图的双面渲染(可选)
如果你的顶层视图不需要显示背面内容,可以设置它的layer的doubleSided为NO,这样当它转到背面时会自动隐藏,也能间接解决问题:
self.topView.layer.doubleSided = NO;
topView.layer.doubleSided = false
不过这个方法只适合顶层视图背面不需要显示内容的场景,如果你的顶层视图背面有内容要展示,还是用前面的交换zPosition的方法更合适。
内容的提问来源于stack exchange,提问作者cyanide
相关产品推荐
相关产品推荐

