今日职言:每一次金融危机都是蓄谋已久的精确定向爆破,熠熠夺目的崭新金融大厦总是建筑在成千上万破产者的废墟之上。
承接上一章的内容,我们继续实现下如何使用SwiftUI构建一个Banner轮播图。
上一章,我们使用HStack横向视图和Gestures手势做一个Banner轮播图,完成了基础的交互,但还不算全部完成。
这一章,我们将学习Banner轮播图的交互,包含移动Banner轮播图的动画,以及点击Banner轮播图进入详情页。
那么,我们开始吧。
我们通过GeometryReader几何视图的outerView设置了CardView卡片的大小,但它是固定的。
.frame(width: outerView.size.width, height: outerView.size.height)
我们了解下Banner轮播图的展示逻辑,它是当前显示的CardView卡片会大一些,切换的时候,另外的会小一些,当我们将卡片滑动到中间展示时,它又会放大。
我们要做的就是这个效果。
.frame(width: outerView.size.width, height: self.currentIndex == index ? 250 : 200)
我们可以尝试根据currentIndex当前索引位置来控制CardView卡片的高度,如果它在当前,那么height高度为250,如果不是,height高度为200。
为了效果好看,我们还可以调整CardView卡片的透明度,不在当前展示的卡片,我们让它“模糊”一点,突出中间的卡片。
.opacity(self.currentIndex == index ? 1.0 : 0.7)
最后,我们把动画效果加到整个GeometryReader几何视图中。
.animation(.interpolatingSpring(mass: 0.6, stiffness: 100, damping: 10, initialVelocity: 0.3),value: offset)
我们开启了动画,动画呈现的方式为interpolatingSpring弹性旋转动画。
我们运行下模拟器预览下效果。
恭喜你,完成了Banner轮播图的动画效果!
下面,我们来完成下点击Banner轮播图进入DetailView详情页的交互。
首先创建一个新的页面,我们命名为DetailView.swift。
下面,我们完成下DetailView页面的设计,它由一个标题、内容和按钮组成。
struct DetailView: View {
let imageName: String
var body: some View {
GeometryReader { geometry in
ScrollView {
VStack(alignment: .leading, spacing: 5) {
// 图片名称
Text(self.imageName)
.font(.system(.title, design: .rounded))
.fontWeight(.heavy)
.padding(.bottom, 30)
// 描述文字
Text("要想在一个生活圈中生活下去,或者融入职场的氛围,首先你要学习这个圈子的文化和发展史,并尝试用这个圈子里面的“话术”和他们交流,这样才能顺利地融入这个圈子。")
.padding(.bottom, 40)
// 按钮
Button(action: {
}) {
Text("知道了")
.font(.system(.headline, design: .rounded))
.fontWeight(.heavy)
.foregroundColor(.white)
.padding()
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.blue)
.cornerRadius(8)
}
}
.padding()
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .topLeading)
.background(Color.white)
.cornerRadius(15)
}
}
}
}
首先,我们先实现点击CardView打开DetailView详情页。
我们使用GeometryReader几何视图和ScrollView滚动视图搭建了一个DetailView详情页。
然后我们回到ContentView首页,创建一个点击状态。
@State var isShowDetailView = false
当我们点击CardView卡片时,进入到对应的详情页。和之前的章节一样我们在CardView卡片视图上添加点击事件,然后用ZStack层叠视图将DetailView详情页和ContentView首页叠加在一起。
//详情页
if self.isShowDetailView {
DetailView(imageName: imageModels[currentIndex].imageName)
.offset(y: 200)
.transition(.move(edge: .bottom))
.animation(.interpolatingSpring(mass: 0.5, stiffness: 100, damping: 10, initialVelocity: 0.3),value: offset)
}
当我们点击CardView卡片视图的时候,展示DetailView详情页。
当然,还远远不够,我们希望展示的效果是,Banner图片轮播在展示详情的时候,背景部分可以看到原先Banner轮播的图片,我们可以根据isShowDetailView的状态再调整下样式。
//如果点击就图片就移上去
.offset(y: self.isShowDetailView ? -innerView.size.height * 0.3 : 0)
//如果点击图片两边就不留边距
.padding(.horizontal, self.isShowDetailView ? 0 : 20)
//如果点击就图片调整大小
.frame(width: outerView.size.width, height: self.currentIndex == index ? (self .isShowDetailView ? outerView.size.height : 250) : 200)
我们发现一个交互问题,现在我们尝试拖动Banner图片轮播,它也是可以拖动的,这不是我们想要的效果。
我们可以按照上面的逻辑,再用isShowDetailView判断一下。
//如果没有被点击
!self.isShowDetailView ?
//代码块
:nil
好了,这样,我们在展示DetailView详情页时,就不用担心Banner轮播图被拖动了。
我们最后再加上一个关闭按钮,用于关闭DetailView详情页。
//关闭按钮
Button(action: {
self.isShowDetailView = false
}) {
Image(systemName: "xmark.circle.fill")
.font(.system(size: 30))
.foregroundColor(.black)
.opacity(0.7)
.contentShape(Rectangle())
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topTrailing)
.padding(.trailing)
恭喜你,完成了所有的编程!
我们回顾一下,上篇我们完成了ScrollView滚动视图创建Banner轮播图,后来我们发现不太可行。然后,中篇我们尝试使用HStack横向视图和Gestures手势做一个Banner轮播图,并完成了基本的交互。下篇我们继续完成了整个Banner轮播图的交互逻辑。
真心不容易啊。
struct ContentView: View {
@State var currentIndex = 0
@GestureState var dragOffset: CGFloat = 0
@State private var offset: CGFloat = .zero
@State var isShowDetailView = false
var body: some View {
ZStack {
//首页轮播图
GeometryReader { outerView in
HStack(spacing: 0) {
ForEach(imageModels.indices, id: \.self) { index in
GeometryReader { innerView in
CardView(image: imageModels[index].image, imageName: imageModels[index].imageName)
//如果点击就图片就移上去
.offset(y: self.isShowDetailView ? -innerView.size.height * 0.3 : 0)
}
//如果点击图片两边就不留边距
.padding(.horizontal, self.isShowDetailView ? 0 : 20)
.opacity(self.currentIndex == index ? 1.0 : 0.7)
//如果点击就图片调整大小
.frame(width: outerView.size.width, height: self.currentIndex == index ? (self .isShowDetailView ? outerView.size.height : 250) : 200)
//点击进入详情页
.onTapGesture {
self.isShowDetailView = true
}
}
}
.frame(width: outerView.size.width, height: outerView.size.height, alignment: .leading)
.offset(x: -CGFloat(self.currentIndex) * outerView.size.width)
.offset(x: self.dragOffset)
// 拖动事件
.gesture(
//如果没有被点击
!self.isShowDetailView ?
DragGesture()
.updating(self.$dragOffset, body: { value, state, transaction in
state = value.translation.width
})
.onEnded({ value in
let threshold = outerView.size.width * 0.65
var newIndex = Int(-value.translation.width / threshold) + self.currentIndex
newIndex = min(max(newIndex, 0), imageModels.count - 1)
self.currentIndex = newIndex
})
: nil
)
}
.animation(.interpolatingSpring(mass: 0.6, stiffness: 100, damping: 10, initialVelocity: 0.3),value: offset)
//详情页
if self.isShowDetailView {
DetailView(imageName: imageModels[currentIndex].imageName)
.offset(y: 200)
.transition(.move(edge: .bottom))
.animation(.interpolatingSpring(mass: 0.5, stiffness: 100, damping: 10, initialVelocity: 0.3),value: offset)
//关闭按钮
Button(action: {
self.isShowDetailView = false
}) {
Image(systemName: "xmark.circle.fill")
.font(.system(size: 30))
.foregroundColor(.black)
.opacity(0.7)
.contentShape(Rectangle())
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topTrailing)
.padding(.trailing)
}
}
}
}
快来动手试试吧!
如果本专栏对你有帮助,不妨点赞、评论、关注~
阅读量:2012
点赞量:0
收藏量:0