在移动应用程序中,图片瀑布流布局是一种常见的设计模式,它不仅能够以网格的形式显示大量图片,还能允许用户点击图片进行全屏查看。
在下文中,将详细介绍如何使用 SwiftUI 构建一个带有点击动画效果的图片瀑布流布局,并通过 matchedGeometryEffect
实现图片从列表到全屏的平滑过渡。
效果概述
用户可以看到一个由多张图片组成的瀑布流布局,图片按列排列。当用户点击其中一张图片时,图片会在全屏模式下显示,并带有平滑的动画过渡。用户可以再次点击全屏图片,返回瀑布流视图。
核心技术点
- 瀑布流布局:使用
HStack
和VStack
创建两列图片。 - 点击事件和动画过渡:通过
onTapGesture
实现点击事件,并使用matchedGeometryEffect
实现动画效果。 - 全屏图片显示:使用
GeometryReader
调整全屏图片的布局,并在背景使用模糊效果。
实现步骤
1. 项目初始化
首先,在 Xcode 中创建一个新的 SwiftUI 项目。为了简单起见,我们在 ContentView
中实现所有功能。在实际项目中,你可以根据需求将代码拆分到多个视图中。
2. 构建基础的图片数据
假设我们有一组图片,命名为 "351" 到 "379"(假设这些图片资源已包含在项目中)。我们将这组图片存储在一个数组中。
struct ContentView: View {
// 图片名称数组
var imageNames = ["351", "352", "353", "354", "355", "356", "357", "358", "359", "360", "370", "371", "372", "373", "374", "375", "376", "377", "378", "379"]
// 控制全屏展示图片的状态
@State var show = false
// 使用 @Namespace 修饰符定义动画作用域,用于 `matchedGeometryEffect` 共享动画效果
@Namespace var animation
// 当前被点击的图片名称
@State var cItem = ""
imageNames
:一个数组,用于存储图片的名称。@State var show
:一个布尔状态,控制是否显示全屏图片。@Namespace var animation
:命名空间,用于matchedGeometryEffect
动画效果。@State var cItem
:存储当前被点击的图片名称。
3. 布局瀑布流图片
为了实现图片的瀑布流布局,我们将图片分成两列。使用 HStack
创建一个水平的图片容器,在其中嵌入两个 VStack
用于垂直排列图片。
var body: some View {
// 使用 ScrollView 允许垂直滚动,隐藏滚动指示器
ScrollView(showsIndicators: false) {
// 两列图片的水平布局,使用 HStack 进行布局
HStack(alignment: .top, spacing: 5) {
// 第一列图片,使用数组的前一半
createColumn(with: imageNames.prefix(imageNames.count / 2))
// 第二列图片,使用数组的后一半
createColumn(with: imageNames.suffix(imageNames.count / 2))
}
}
.padding(.horizontal, 5) // 为 ScrollView 添加一些填充
.overlay {
if show {
fullScreenImageOverlay // 全屏图片展示
}
}
.edgesIgnoringSafeArea(.all) // 使图片铺满全屏
}
在这里我们使用 ScrollView
允许图片垂直滚动,并隐藏滚动指示器。HStack
将两列图片横向排列,padding(.horizontal, 5)
为整体视图添加左右间距。
4. 创建图片列
为了避免代码重复,我们将创建图片列的代码提取到一个函数中,这个函数通过传递图片名称数组的一部分来生成列布局。
// 创建列视图的函数,将图片数组传入,并通过 VStack 进行竖直排列
func createColumn(with items: ArraySlice<String>) -> some View {
VStack(spacing: 5) {
// 遍历图片名称数组,并为每张图片创建一个视图
ForEach(items, id: \.self) { item in
Image(item) // 显示图片
.resizable() // 使图片可以调整大小
.scaledToFill() // 保持图片比例并填充视图
.matchedGeometryEffect(id: item, in: animation) // 绑定几何动画效果
.frame(minWidth: 0, maxWidth: .infinity) // 最大宽度为可用空间的宽度
.frame(maxHeight: .infinity) // 最大高度设为无限,允许视图扩展
.cornerRadius(5) // 为图片添加圆角
.onTapGesture {
// 当图片被点击时,更新 cItem 为当前图片名称,并触发动画切换 show 状态
cItem = item
withAnimation(.spring()) {
show = true // 切换全屏展示状态
}
}
}
}
}
主要说明:
ForEach
用于遍历传入的图片数组并为每张图片创建视图。Image(item)
加载图片,并使用.resizable()
和.scaledToFill()
保证图片适应视图大小。matchedGeometryEffect
实现点击后图片动画的平滑过渡。onTapGesture
:当用户点击图片时,记录当前点击的图片名称,并将show
状态改为true
以显示全屏图片。
5. 全屏图片显示
当用户点击图片时,全屏展示该图片,并带有动画效果。我们使用 GeometryReader
以适应设备屏幕大小,并设置 matchedGeometryEffect
来确保图片从列表过渡到全屏时有平滑的动画效果。
// 全屏图片展示的视图,当 show 为 true 时显示
var fullScreenImageOverlay: some View {
GeometryReader { geometry in
// 通过几何读取器获取设备尺寸并动态调整图片大小
Image(cItem) // 显示当前被点击的图片
.resizable() // 使图片可以调整大小
.scaledToFit() // 保持图片比例并适应全屏
.matchedGeometryEffect(id: cItem, in: animation) // 绑定几何动画效果
.frame(width: geometry.size.width, height: geometry.size.height) // 使用设备的宽高进行布局
.background(.thinMaterial) // 使用薄模糊背景效果
.onTapGesture {
// 当全屏图片被点击时,切换 show 状态,退出全屏
withAnimation(.spring()) {
show = false
}
}
}
}
在这里,我们使用 GeometryReader
读取设备的宽高,以调整全屏图片的大小,并确保其按比例适应屏幕。background(.thinMaterial)
为全屏图片设置了模糊背景效果,使用户的注意力集中在图片上。
6. 运行效果
最终实现的效果是:用户可以看到两列垂直排列的图片,点击任意图片时,该图片会平滑地过渡到全屏显示,并且背景会变为模糊效果,给用户带来沉浸式的视觉体验。再次点击全屏图片,图片会返回到原来的位置,并恢复到瀑布流布局。
结论
通过 SwiftUI 强大的布局和动画功能,我们可以轻松实现复杂的 UI 交互效果。matchedGeometryEffect
允许我们在不同视图之间创建共享的几何效果,保证动画的流畅性和一致性。而 GeometryReader
则让我们能够动态适应不同设备的屏幕大小。在这个示例中,完整地展示了如何构建一个功能强大且具有吸引力的图片瀑布流布局。
通过进一步优化代码和添加更多功能(如图片缩放、滑动关闭全屏等),你可以创建出更加丰富的用户体验。