3D卡片
想要实现这个效果,需要处理以下几点:
-
手势处理
-
手势转换为3D角度
-
角度影响视图
1. 手势处理
这种3D卡片本身没有什么技术难度,也就是多层的图像堆积,想要实现这种跟随手势的3D效果,需要获取手势移动的距离,同时需要注意在手势终止之后需要将视图恢复到初始状态。
-
手势监听 手势监听只需要将手势的移动距离保存起来,在SwiftUI中使用一个@State变量保存即可
-
手势终止后将视图恢复到初始状态 手势终止后将移动距离变量恢复为0即可
具体代码如下:
.gesture( DragGesture() .onChanged { value in offset = value.translation } .onEnded { _ in withAnimation(.interactiveSpring(response: 0.6, dampingFraction: 0.32, blendDuration: 0.32)) { offset = .zero } } ) 复制代码
2. 将手势转换为3D角度
当手指在页面上向下拖动时,卡片对应的向下方旋转(对应旋转轴是x);当手指在视图上左右拖动时,卡片对应左右旋转(对应旋转轴是y);
至于拖动的距离与旋转角度的关系就随你定义。这里取 10/屏幕长宽。
func offset2Angle(_ isVertical: Bool = false) -> Angle { let progress = (isVertical ? -offset.height : offset.width) / (isVertical ? screenSize.height : screenSize.width) return Angle(degress: progress * 10) } 复制代码
这里因为需要计算两个维度的角度,然后通过组合来实现立体的旋转,所以增加一个条件以区别两个角度。
这里还有一个辅助函数用于获取屏幕尺寸,就不展开说了。
var screenSize: CGSize = { guard let window = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return .zero } return window.screen.bounds.size }() 复制代码
3. 角度影响视图
最后就是最简单的一个步骤:给视图添加3D旋转
.rotation3DEffect(offset2Angle(true), axis: (x: 1, y: 0, z: 0)) // 旋转轴为x .rotation3DEffect(offset2Angle(), axis: (x: 0, y: 1, z: 0)) // 旋转轴为y 复制代码
为了使得3D效果更明显,还可以在前景视图上添加offset位移,让卡片旋转时带动前景移动
.offset(x: offset2Angle().degress * 5, y: -offset2Angle(true).degress * 5) 复制代码
总结
这种3D效果看起来很厉害,实际上运用到的知识却很简单,关键在于空间理解能力。
完整代码
struct ContentView: View { @State var offset: CGSize = .zero var body: some View { GeometryReader { proxy in let size = proxy.size let imageSize = size.width * 0.7 VStack { Image("img") .resizable() .aspectRatio(contentMode: .fit) .frame(width: imageSize) .zIndex(1) .offset(x: offset2Angle().degrees * 5, y: -offset2Angle(true).degrees * 5) } .padding(.top, 65) .padding(.horizontal, 15) .padding(.bottom, 30) .frame(width: imageSize) .background { ZStack { Rectangle() .fill(.blue) } .clipShape(RoundedRectangle(cornerRadius: 25, style: .continuous)) } .rotation3DEffect(offset2Angle(true), axis: (x: 1, y: 0, z: 0)) .rotation3DEffect(offset2Angle(), axis: (x: 0, y: 1, z: 0)) .scaleEffect(0.9) .frame(maxWidth: .infinity, maxHeight: .infinity) .contentShape(Rectangle()) .gesture( DragGesture() .onChanged { value in offset = value.translation } .onEnded { _ in withAnimation(.interactiveSpring(response: 0.6, dampingFraction: 0.32, blendDuration: 0.32)) { offset = .zero } } ) } } func offset2Angle(_ isVertical: Bool = false) -> Angle { let progress = (isVertical ? -offset.height : offset.width) / (isVertical ? screenSize.height : screenSize.width) return .init(degrees: progress * 10) } var screenSize: CGSize = { guard let window = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return .zero } return window.screen.bounds.size }() } 复制代码
- 随机文章
- 热门文章
- 热评文章
- 工作中常用的设计模式--适配器模式
- GenServer上的Elixir非阻塞线程?
- Echarts遇见的小问题方法解决
- 2022年全球及中国抗氧剂245行业头部企业市场占有率及排名调研报告
- 数据结构初阶--单链表(讲解+类模板实现)
- ppt如何实现抽奖人名滚动
- Python3中的真正私有变量
- cad图形如何进行颜色填充