Work with arrays and data models to create a loop of components in SwiftUI.
在 SwiftUI 中使用数组和数据模型创建系列组件。
为了实现课程卡片在滚动时的三维动画,需要知道卡片的具体位置,然后根据位置确定如何动画。
(1)使用 GeometryReader 获取滚动的位置
在 ForEach 循环中的调用的 SectionView 上使用⌘ 点击,在快捷菜单中选择 VStack。这是前面我们多次使用的方法了。然后将 VStack 改为 GeometryReader。GeometryReader 可以获取到位置值 geometry,可以看见卡片都堆叠起来了。我们需要将其展开,让每张卡片都有自己的一个框架。所以给 GeometryReader 加上 frame 修饰,卡片都分开了。
ForEach(sectionData) { item in // 遍历数组
GeometryReader { geometry in // 获取卡片的位置
SectionView(section: item) // 子视图
}
.frame(width: 275, height: 275) // 让卡片分开
}
(2)使用 GeometryReader 获取到的值为 SectionView 添加 3D 动画修饰。
GeometryReader 可以获得每个框架的位置值,包括宽度、高度、各个顶点的位置等等。现在为 SectionView 组件添加 3D 动画修饰 rotation3DEffect,并且设定参数。
.rotation3DEffect(
Angle(degrees: Double(geometry.frame(in: .global).minX - 30) / -20),
axis: (x: 0, y: 10, z: 0)
)
第一个参数是通过 geometry 的值计算的旋转角度,大家可以自己试着改变值来测试,比如 Angle(degress: 90)。这里使用了 geometry 的 frame 的值中的 minX 。minX、minY、maxX、maxY 都是 CGRect 的属性,表示左、上、右、下四条边的位置。这说明,如果是垂直方向的滚动动画,那就不能选择 minX,而是应该选择 minY。CGRect 还有很多属性,当然也包括我们最熟悉的 width 和 height,都可以通过文档查到。这里看一下各个函数的返回值类型。
geometry → GeometryProxy
frame -> CGRect
minX -> CGFloat
可以看出最终到 minX 执行后的返回值类型是 CGFloat,再经过 Double 强转后成为 Double 类型以满足 degrees 参数的需要。minX 就是卡片起始距离屏幕左侧的值,我们在padding中设置的 30,所以要减去 30 来保证起始动画的角度为 0。而除以 -20 则是为了让旋转的变化幅度减少,其中负数是为了让起始位置更符合视觉效果习惯。
第二个参数 axis 是对三个轴的规定,具体数值大小的含义我也还没有完全明白。只是知道 0 值为禁用。因为我们的卡片旋转的方向是绕着 y 轴(屏幕的纵向),所以只给 y 轴设定了参数。
(3)调整堆栈的间距
现在看起来卡片在滚动时三维动画效果还是不错的,但是卡片相互距离有些远。修改 HStack 的 spacing 来调整卡片间的距离。
- GeometryReader 可以获取到位置,通过 .frame 方法获得类型为 CGRect 的值。
- CGRect 包括很多属性:minX,minY,maxX,maxY,width,height……都是 CGFloat 类型的值
- 水平滚动初始位置要使用 minX,禁用 x 和 z 轴;垂直滚动初始位置要选择 minY,禁用 y 和 z 轴。
- 起始位置记得要减去 padding 等修饰设置的量,保证默认值是从 0 开始
- 角度设的越大,旋转的越“疯狂”,可以用除以某个常数让动画“温和”一些。
- 再次用到了强转
模态显示