本文是我学习《iOS Animations by Tutorials》 笔记中的一篇。 文中详细代码都放在我的Github上 andyRon/LearniOSAnimations。

到目前为止,之前的文章只使用了二维动画——这是在平面设备屏幕上动画元素的最自然方式。 毕竟,从iOS 7扁平化后的世界中的按钮,文本字段,开关和图像没有了第三维; 这些元素存在于由X和Y轴定义的平面中:

核心动画可以帮助我们摆脱这个二维世界; 虽然它不是真正的3D框架,但核心动画有很多好的方法可以帮助我们在3D空间中描绘二维对象。

换句话说,图层和动画仍然以二维方式进行描绘,但可以在3D空间中旋转和定位每个元素的2D平面,如下所示:

上面显示的是在3D空间中旋转的两个2D图像。 透视变形使我们可以从渲染器的角度了解它们的位置。

本文将学习如何在3D空间中定位和旋转图层。CATransform3D类似于CGAffineTransform,但除了在x和y方向上缩放,倾斜和平移之外,它还带来了第三维:z。 z轴直接从设备屏幕朝向您的眼睛。

请考虑以下几个示例,以更好地了解透视的工作原理。

将相机设置得非常靠近屏幕会相应地扭曲图层的视角:

如果将相机离物体比较远时的视角:

最后,如果你在相机和屏幕之间设置了很大的距离:

预览:

24-简单的3D动画 —— 尝试新发现的有关相机距离和视角的知识。设置图层的透视图,处理图层的变换以旋转,平移和缩放三维图层。

25-中级3D动画 —— 在前一章的基础上,既然知道了m34和相机距离的秘密,就可以创建具有多个视图的各种3D动画。

24-简单3D动画

本章将尝新发现的有关相机距离和视角的知识。

开始项目 Office Buddy是一个办公室帮助应用程序,供员工访问有关日常公司生活的分类信息。这个应用很简单就是点击左上角的按钮或者左右滑到,然后左边侧栏出现。下面?将向这个开始项目中添加一些3D元素。

开始项目预览:

创造3Dtransformations

打开ContainerViewController.swiftContainerViewController在屏幕上显示菜单视图控制器和内容视图控制器。 它还处理平移手势,以便用户可以打开和关闭菜单。

您的第一个任务是构建一个类方法,该方法为侧面菜单的给定百分比“开放性”创建相应的3D变换。 将以下方法声明添加到ContainerViewController

func menuTransform(percent: CGFloat) -> CATransform3D {}
复制代码

上述方法接受菜单当前进度的单个参数,该参数由handleGesture(_ :)中的代码计算,并返回CATransform3D的实例。 您将直接将此方法的结果分配给菜单图层的transform属性。

将以下代码添加到上面方法中:

var identity = CATransform3DIdentity
identity.m34 = -1.0/1000
复制代码

这段代码可能看起来有点令人惊讶; 到目前为止,您只使用函数来创建或修改变换。 但是,这一次,您正在修改其中一个类的属性。

注意:CATransform3DCGAffineTransform分表表示4*43*3的数学矩阵,在Swift和OC中都是用结构体表示的。

属性m34指矩阵的第3行第4列,这个属性比较常用,表示透视效果,m34 = -1 / D,D可以理解为相机距离,D越小,透视效果越明显,必须在有旋转效果的前提下,才会看到透视效果。

相机距离

对于普通应用程序中的UI元素,相机距离大概可以表示:
0.1 ... 500:非常接近,透视失真。
750 ... 2,000:视角不错,内容清晰可见。
2000+:几乎没有透视失真。

对于Office Buddy应用程序,1000点的距离将为菜单提供一个非常微妙的视角。

将以下代码添加到menuTransform(percent:)的底部:

let remainingPercent = 1.0 - percent
let angle = remainingPercent * .pi * -0.5
复制代码

将以下代码添加到menuTransform(percent:)的底部:

let rotationTransform = CATransform3DRotate(identity, angle, 0.0, 1.0, 0.0)
let translationTransform = CATransform3DMakeTranslation(menuWidth * percent, 0, 0)
return CATransform3DConcat(rotationTransform, translationTransform)
复制代码

在这里,使用rotationTransform将图层绕y轴旋转。 菜单从左侧移动,因此还需要创建平移变换以沿x轴移动它,最终将菜单宽度设置为100%。 最后,连接两个转换并返回结果。

setMenu(toPercent:)中删除下面:

menuViewController.view.frame.origin.x = menuWidth * CGFloat(percent) - menuWidth
复制代码

替代为:

menuViewController.view.layer.transform = menuTransform(percent: percent)
复制代码

菜单栏的位置通过转换来控制了。

运行项目, 向右平移查看菜单如何围绕其y轴旋转:

菜单以3D形式旋转,但它围绕其水平中心旋转,菜单与内容视图控制器中间有间隙。

移动图层的锚点

默认情况下,图层的锚点的x坐标为0.5,表示它位于中心。 将锚点的x设置为1.0,就不会出现上面的那种间隙,如下所示:

所有变换都是围绕图层的锚点计算的。

viewDidLoad()中找到以下行:

menuViewController.view.frame = CGRect(x: -menuWidth, y: 0, width: menuWidth, height: view.frame.height)
复制代码

现在在该行上方插入以下代码(在设置视图帧之前插入行非常重要,否则设置锚点将偏移视图):

menuViewController.view.layer.anchorPoint.x = 1.0
复制代码

这会使菜单围绕其右边缘旋转。

运行效果:

这看起来好多了!

通过阴影创建远景

阴影为3D动画带来了很多真实感。这里不需要使用任何先进的着色技术,只要旋转时更改alpha

将以下代码添加到setMenu(toPercent:)

menuViewController.view.alpha = CGFloat(max(0.2, percent))
复制代码

0.2让菜单最小还可见,百分比让菜单越小透明度越低。

由于此应用程序的背景为黑色,因此降低菜单视图的alpha值会使菜单中显示黑色并模拟阴影效果。

运行效果:

这是一个让3D效果更加真实的小细节。

如果仔细观察,会发现第一次点击按钮时,菜单不是以3D效果展示,以后才是。这是因为第一次切换菜单之前,设置3D动画参数和图层转换。在viewDidLoad()中添加:

setMenu(toPercent: 0.0)
复制代码

光栅化的效率

让动画更加“完美”。如果在来回平移时盯着菜单足够长,会注意到菜单项的边框看起来像素化,如下所示:

核心动画不断重绘菜单视图控制器的所有内容,并在所有元素移动时重新计算所有元素的透视失真,这个过程中会出现锯齿状边缘

最好让Core Animation知道我们不会在动画期间更改菜单内容,以便它可以渲染菜单一次并简单地旋转渲染和缓存的图像。 这听起来很复杂,但很容易实现。

找到handleGesture()中的.began代码块,此代码在用户平移操作时执行。

将以下代码添加到.began代码块的末尾:

menuViewController.view.layer.shouldRasterize = true
menuViewController.view.layer.rasterizationScale = UIScreen.main.scale
复制代码

shouldRasterize让核心动画将图层内容缓存为图像。 然后设置rasterizationScale以匹配当前的屏幕比例。

运行,效果:

为避免在使用应用程序时进行任何不必要的缓存,应该在动画完成后立即关闭光栅化。 在.failed代码块找到动画完成闭包并添加以下代码:

self.menuViewController.view.layer.shouldRasterize = false
复制代码

现在,只在动画期间激活光栅化。提高了效率!?

菜单按钮的3D旋转动画

菜单展示时,菜单按钮也进行自身的旋转。具体来说,您将围绕x轴和y轴创建旋转,以使菜单按钮在其对角线上翻转。

ContainerViewControllersetMenu(toPercent:)中添加:

let centerVC = centerViewController.viewControllers.first as? CenterViewController
if let menuButton = centerVC?.menuButton {menuButton.imageView.layer.transform = buttonTransform(percent: percent)
}
复制代码

buttonTransform函数为:

func buttonTransform(percent: CGFloat) -> CATransform3D {var identity = CATransform3DIdentityidentity.m34 = -1.0/1000let angle = percent * .pilet rotationTransform = CATransform3DRotate(identity, angle, 1.0, 1.0, 0.0)return rotationTransform
}
复制代码

效果如下:

25-中级3D动画

在上一章24-简单3D动画中,学习了将透视应用到单个视图制作出简单的3D效果的动画; 事实上,一旦我们知道m34和相机距离的秘密,就可以创建各种3D动画。

本章以前面的内容为基础,学习如何使用多个视图创建有意思的3D动画。

本章的开始项目 ***ImageGallery***是一个简单的飓风图库。

探索开始项目

本章的开始项目是:

只是一个空白屏幕,顶部有两个按钮。

打开ViewController.swift,会看到一个名为images的数组,此数组就是一些图片信息。

ImagViewCard类继承自UIImageView并且有一个字符串属性title来保存飓风标题,有一个名为didSelect的属性,以便您可以轻松地在图像上设置点击处理程序。

第一个任务是将所有图像添加到视图控制器的视图中。 将以下代码添加到viewDidAppeae(_:)的末尾:

for image in images {image.layer.anchorPoint.y = 0.0image.frame = view.boundsview.addSubview(image)
}
复制代码

在上面的代码中,循环遍历所有图像,在y轴上将每个图像的锚点设置为0.0,并调整每个图像的大小,使其占据整个屏幕。 设置锚点可让图像围绕其上边缘而不是中心的默认值旋转,如下图所示:

运行只会看到最后一张图片Hurricane Irene,因为图片位置相同,叠加在一起来

显示飓风图像的名字,在viewDidAppear(_:)的末尾添加以下行:

navigationItem.title = images.last?.title
复制代码

注意,目前没有在图像上设置任何透视转换;之后将直接在视图控制器的视图上设置透视图。

在上一章中,在单个视图上调整了transform属性,然后在3D空间中旋转它。但是,由于您当前的项目有更多的个人视图,需要在3D中操作,您可以设置其父视图的透视图,从而节省大量工作。

将以下代码添加到viewDidAppear(_:)

var perspective = CATransform3DIdentity
perspective.m34 = -1.0/250.0
view.layer.sublayerTransform = perspective
复制代码

在这里,您可以使用图层属性sublayerTransform来设置视图控制器图层的所有子图层的透视图。 然后将子层转换与每个单独层的自身变换组合。

这使您可以专注于管理子视图的旋转或平移,而无需担心透视。 您将在下一节中更详细地了解它的工作原理。

改变图库

toggleGallery(_:)连接着右上方的“浏览”按钮,在此处将3D变换应用于四个图像。

将以下变量添加到toggleGallery(_:)

var imageYOffset: CGFloat = 50.0for subview in view.subviews {guard let image = subview as? ImageViewCard else {continue}
}
复制代码

由于您不只是将所有图像旋转到原位而只是移动它们以产生”扇形“动画,因此您可以使用imageYOffset来设置每个图像的偏移。 接下来,您需要遍历所有图像并运行其各自的动画。

在这里,您循环浏览视图控制器视图的所有子视图,并仅对作为ImageViewCard实例的子视图执行操作。 在上面添加的guard块之后添加以下代码,以替换此处的更多代码注释:

var imageTransform = CATransform3DIdentity
// 1
imageTransform = CATransform3DTranslate(imageTransform, 0.0, imageYOffset, 0.0)
// 2
imageTransform = CATransform3DScale(imageTransform, 0.95, 0.6, 1.0)
// 3
imageTransform = CATransform3DRotate(imageTransform, .pi/8, -1.0, 0.0, 0.0)
复制代码

首先将标识转换分配给imageTransform,然后对其添加一系列调整。 这是每个单独的调整对图像的作用:

// 1 使用CATransform3DTranslate在y轴上移动图像; 这会使图像偏离其默认的0.0 y坐标,如下所示:

之后,将要分别计算每个图像的imageYOffset,否则图片还是叠加在一起。

// 2 通过使用CATransform3DScale调整转换的比例分量来缩放图像。 可以在x轴上稍微缩小图像,但是在y轴上将其缩小到60%以丰富旋转3D效果:

// 3 最后,使用CATransform3DRotate将图像旋转22.5度,使其具有一些透视变形,如下所示:

请记住,之前已经设置了锚点,因此图像围绕其顶部边缘旋转。

现在你看到通过view.layer.sublayerTransform设置上面的m34值的值; 您的旋转变换只需重新使用子层变换中的m34值,而无需在此处应用它。 那很方便!

现在剩下的就是将转换应用于每个图像。 添加以下行(仍在for代码块中):

image.layer.transform = imageTransform
复制代码

将以下行添加到for块的末尾,修改每个图像的位置:

imageYOffset += view.frame.height / CGFloat(images.count)
复制代码

这会调整每个图像的y偏移量,具体取决于它在堆栈中的位置。 将屏幕高度除以图像数量,以便它们在屏幕上均匀分布。 运行后效果:

下面让它动起来!

动画图库

在上面的image.layer.transform = imageTransform的前面添加:

let animation = CABasicAnimation(keyPath: "transform")
animation.fromValue = NSValue(caTransform3D: image.layer.transform)
animation.toValue = NSValue(caTransform3D: imageTransform)
animation.duration = 0.33
image.layer.add(animation, forKey: nil)
复制代码

这段代码非常熟悉:在transform属性上创建一个图层动画,并将其从当前值设置为之前设计的imageTransform。 运行后, 点击“浏览”按钮,效果:

你现在已经完成了画廊; 当您在用户点击“浏览”按钮时添加关闭风扇的功能时,您将在“挑战”部分重新访问它。

更多一点交互

为图像库添加一点交互性:点击图像,变成全屏,并且位置移到最前面,以便用户可以更好地查看它。

ImageViewCard已经具有名为didSelect的闭包表达式属性,当用户点击图像,就将点击的图像视图作为输入参数给这个闭包。

首先将以下代码添加viewDidAppear()的for循环体内:

image.didSelect = selectImage
复制代码

ViewController中添加方法:

func selectImage(selectedImage: ImageViewCard) {for subview in view.subviews {guard let image = subview as? ImageViewCard else {continue}if image === selectedImage {} else {}}
}
复制代码

现在您还需要两个动画:一个用于为所选图像设置动画,另一个用于为图库中的所有其他图像设置动画。 你将反过来解决这个问题并首先淡出未选择的图像。

上面的方法还缺少两个动画,当image === selectedImage,就是所选图像的动画;或者,未选择的所有其他图像的动画,前者代码为:

UIView.animate(withDuration: 0.33, delay: 0.0, options: .curveEaseIn, animations: {image.alpha = 0.0
}, completion: { (_) inimage.alpha = 1.0image.layer.transform = CATransform3DIdentity
})
复制代码

后者代码为:

UIView.animate(withDuration: 0.33, delay: 0.0, options: .curveEaseIn, animations: {image.layer.transform = CATransform3DIdentity
}, completion: {_ inself.view.bringSubview(toFront: image)
})
复制代码

在这里,没有对动画进行3D变换,然后确保图像位于视图堆栈的顶部,以便它可见。

最后,将以下代码添加到selectImage(selectedImage:)的末尾,更新标题:

self.navigationItem.title = selectedImage.title
复制代码

切换图库

这小结工作是将使“浏览”按钮可以关闭图库视图。

ViewController添加一个isGalleryOpen的新属性,并将其初始值设置为false

需要在代码中的两个位置更新此属性的值:

  • toggleGallery(_:)结束时将其设置为true
  • selectImage(selectedImage:)结束时将其设置为false

toggleGallery()的顶部,添加一个检查以查看图库是否已打开。 如果打开,则遍历所有图像并将其转换设置为原始值。 不要忘记重置isGalleryOpen并返回,因此其余的方法代码也不会执行。

if isGalleryOpen {for subview in view.subviews {guard let image = subview as? ImageViewCard else {continue}let animation = CABasicAnimation(keyPath: "transform")animation.fromValue = NSValue(caTransform3D: image.layer.transform)animation.toValue = NSValue(caTransform3D: CATransform3DIdentity)animation.duration = 0.33image.layer.add(animation, forKey: nil)image.layer.transform = CATransform3DIdentity}isGalleryOpen = falsereturn
}
复制代码

本章的最后效果:

本文在我的个人博客中地址:系统学习iOS动画之六:3D动画

系统学习iOS动画之六:3D动画相关推荐

  1. 系统学习iOS动画之七:其它类型的动画

    本文是我学习<iOS Animations by Tutorials> 笔记中的一篇. 文中详细代码都放在我的Github上 andyRon/LearniOSAnimations. 前面学 ...

  2. CSS3动画之3D动画

    所有东西一跟3D扯上关系,复杂指数都是噌噌噌往上走.不过也正常,毕竟多了一个维度,要有三维应有的尊严. 3D Transforms要怎么写?能写翻牌效果吗?能写翻书效果吗?能写出立体书的效果吗?点进来 ...

  3. Unity动画系统学习笔记(一)动画剪辑与状态机

    一.动画系统工作流 一个完整的动画系统工作流包含如下几个部分: 动画剪辑(Animation Clips):包含某些对象如何随时间更改其位置.旋转或其他属性的信息. 状态机(Animator Cont ...

  4. iOS动画:3D动画(18)

    根据图示创建动画,当点击菜单按钮时,需要显示左侧菜单栏 效果看起来就像一个3D动画.现在我们来实现这种效果. 打开工程中的ContainerViewController.swift,创建3D变换函数: ...

  5. 第一个用计算机动画模拟的商品是,三维动画(3D动画)在各个领域的应用

    随着计算机图形学技术的不断进步,计算机在动画制作中发挥着越来越大的作用,如今已形成完整的计算机动画技术,它集计算机图形.摄影.美术.音乐.编导.剪辑为一体,为动画的制作提供了现代化的手段,使动画真正步 ...

  6. 系统学习iOS动画 —— 渐变动画

    这个是希望达成的效果: 先创建需要的控件: class ViewController: UIViewController {let timeLabel = UILabel()override func ...

  7. 系统学习iOS动画—— Flight Info(keyframe-animations)

    这是要达成的效果: 先添加所需要的部件: class ViewController: UIViewController {let screenWidth = UIScreen.main.bounds. ...

  8. android 实现3d动画,安卓3d动画的简单实现1

    Android中并没有提供直接做3D翻转的动画,所以关于3D翻转的动画效果需要我们自己实现.这里我列举一个最简单翻转动画的例子. 创建一个activity,布局就用最简单的,里面放一个textView ...

  9. CSS3动画和3D动画

    动画属性 :animation 1.animation-name (必要的)检索或设置对象所应用的动画名,必须与规则@keyframes配合使用, 定义关键帧: @keyframes mymove{} ...

最新文章

  1. Linux中shell命令的用法和技巧
  2. python 爬虫工具
  3. python【力扣LeetCode算法题库】14-最长公共前缀(列表解压)
  4. python修改列表指定位置的_Python 基础教程—列表(1)
  5. python 通信中间件_Python Web框架Sanic middleware – 中间件
  6. 条件编译宏定义_C语言学习- 预处理指令2 - 条件编译
  7. 拉电阻、下拉电阻的原理和作用
  8. Filter中获取传递参数(解决post请求参数问题)
  9. 虚幻4蓝图各颜色代表的含义
  10. 学习java第15天
  11. 2016年个人总结报告PPT(刘欣)
  12. HTML体育新闻案例
  13. VMware 配置局域网内访问
  14. InputStream输入流七牛上传图片
  15. 0CTF/TCTF 2021 Quals_Misc_singer
  16. python数学公式编辑工具_使用Python一键生成LaTeX数学公式
  17. 【uip移植】在AVR单片机ATMega16A上运行uip协议栈,网卡使用ENC28J60
  18. 读博那么辛苦,为什么还有很多人要读博士?
  19. OpenWRT GPIO口控制 WLED
  20. 放飞自我,用3个终端命令提高工作效率

热门文章

  1. c语言实现的小学生心算抢答系统
  2. java中定义坐标_Java 基础接口——坐标
  3. 实景三维在应急中的专题应用
  4. 通过JAVA自动获取Ip地址
  5. 汇编语言 -- 计时器/倒计时
  6. 【MySQL】设置MySQL字符集
  7. 斯沃服务器没有正确安装,[已解决]win10系统windows installer没有正确安装
  8. 用scratch2.0编射击游戏
  9. 知识点滴 - 关于汉语学习的网络资源
  10. spring如何排除bean的注入