SwiftUI极简教程25:构建一个Banner图片轮播(中)

简介: SwiftUI极简教程25:构建一个Banner图片轮播(中)

上一章我们使用了ScrollView滚动视图的方式创建了一个类似Banner轮播图的样式,但我们如果了解过ScrollView滚动视图,我们会发现ScrollView滚动视图没有分页界限,不知道在哪个位置可以停下,另外,ScrollView滚动视图是一整个视图,这样我们也没有办法实现点击单个CardView卡片视图进入它的详情。

因此,使用ScrollView滚动视图创建Banner轮播图的方法是不对的,至少目前不太可行


那我们有没有办法自己做一个滚动视图呢?

我们在之前的章节中学过SwipeCard卡片滑动效果的使用,我们用ZStack层叠和Gestures手势做了一个可以左右滑动丢掉CardView卡片视图的案例,那个真的花了好长时间写。

我们拓展下思维,Banner轮播图是左右横向滑动的,我们是不是可以使用HStack横向视图和Gestures手势做一个Banner轮播图呢?

说干就干。


CardView卡片视图构建


首先,我们拿掉ScrollView滚动视图,这样就得到了一个只有一个CardView卡片视图。

struct ContentView: View {
    var body: some View {
        GeometryReader { outerView in
            HStack {
                ForEach(imageModels.indices, id: \.self) { index in
                    GeometryReader { innerView in
                        CardView(image: imageModels[index].image, imageName: imageModels[index].imageName)
                    }
                    .frame(width: outerView.size.width, height: outerView.size.height)
                }
            }
            .frame(width: outerView.size.width, height: outerView.size.height)
        }
    }
}


image.png

嗯?为什么图片会变成展示“图片05”了?

我们可以停止预览,点击模拟器中的CardView卡片视图,看看卡片的排布方式。

image.png

哦~,在imageModels图片数组中有9个条目图片,而每个卡片视图的宽度等于屏幕宽度,水平堆栈视图向屏幕外扩展,中间展示的就是图片05

如果我们要展示第一个图片“图片01”的话,也很简单,只需要将整个HStack横向视图的对齐方式变成.leading左边就行了,系统都是默认.center居中的。


.frame(width: outerView.size.width, height: outerView.size.height, alignment: .leading)


image.png

调整完成后,我们再看下CardView卡片视图的排布方式。

image.png

好像可以了,但又好像不行,我们发现CardView卡片视图之间有缝隙,这样可能导致我们实现左右滑动的时候,不好计算位置,这里调整HStack横向视图的间距spacing0,顺便增加下CardView卡片视图和屏幕的边距。


struct ContentView: View {
    var body: some View {
        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)
                    }
                    .padding(.horizontal)
                    .frame(width: outerView.size.width, height: outerView.size.height)
                }
            }
            .frame(width: outerView.size.width, height: outerView.size.height, alignment: .leading)
        }
    }
}


image.png

CardView卡片视图移动


上面我们构建了单张CardView卡片视图,我们怎么能让它左右移动呢?

很简单,我们用GeometryReader几何视图的特性得到了屏幕的宽度,那么一张卡片的宽度我们也知道了,我们移动卡片的时候,移动到第一个卡片的位置就行了,比如:第一张卡片的起始位置0,由于卡片宽度为375,那么第二张卡片起始位置就是375,原理就是这样。

首先,我们先定义一个卡片的索引位置,然后我们让CardView卡片视图根据我们定义的位置进行偏移看看静态效果。


struct ContentView: View {
    @State var currentIndex = 5
    var body: some View {
        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)
                    }
                    .padding(.horizontal)
                    .frame(width: outerView.size.width, height: outerView.size.height)
                }
            }
            .frame(width: outerView.size.width, height: outerView.size.height, alignment: .leading)
            .offset(x: -CGFloat(self.currentIndex) * outerView.size.width)
        }
    }
}

image.png

我们定义了当前位置currentIndex5,按照计算,它会基于第一张图片再向左移动5个位置,那么系统就会展示第6张图片。

接下来,我们来添加DragGesture拖动手势

首先,我们声明一个变量dragOffset来保存拖动偏移量:


@GestureState var dragOffset: CGFloat = 0
复制代码


然后,我们完成下DragGesture拖动的代码。


// 拖动事件
.gesture(
    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
        })
)


image.png

上面的代码中,我们在拖动卡片视图时将调用updating拖动更新函数,将水平拖动距离保存到dragOffset变量里。

当拖动结束onEnded时,我们检查拖动距离是否超过阈值(屏幕宽度的65%),并计算新的索引newIndex

计算出newIndex之后,我们验证它是否在imageModels图片数组,如果在,我们就将新的偏移量newIndex的值赋给当前偏移量currentIndex,系统就会更新UI显示下一张图片啦。

另外,我们还需要在拖动HStack横向视图中调用偏移量。


.offset(x: self.dragOffset)

这样,我们拖动的时候,就可以看到CardView左右拖动的效果啦~


image.png

至此,我们已经实现了如何使用HStack横向视图和Gestures手势做一个Banner轮播图的逻辑啦~

未完待续


但我们只完成了基本的交互逻辑,下一章,我们将学习Banner轮播图的交互,包含移动Banner轮播图的动画,以及点击Banner轮播图进入DatailView详情页。

本章ContentView完整代码如下:


import SwiftUI
struct ContentView: View {
    @State var currentIndex = 5
    @GestureState var dragOffset: CGFloat = 0
    var body: some View {
        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)
                    }
                    .padding(.horizontal)
                    .frame(width: outerView.size.width, height: outerView.size.height)
                }
            }
            .frame(width: outerView.size.width, height: outerView.size.height, alignment: .leading)
            .offset(x: -CGFloat(self.currentIndex) * outerView.size.width)
            .offset(x: self.dragOffset)
            // 拖动事件
            .gesture(
                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
                    })
            )
        }
    }
}

快来动手试试吧!



相关文章
|
4月前
|
开发框架 JavaScript 前端开发
循序渐进BootstrapVue,开发公司门户网站(4)--- 使用b-carousel-slide组件实现图片轮播以及vue-awesome-swiper实现图片滑动展示
循序渐进BootstrapVue,开发公司门户网站(4)--- 使用b-carousel-slide组件实现图片轮播以及vue-awesome-swiper实现图片滑动展示
|
6月前
|
编解码 前端开发 iOS开发
前端开发入门笔记(八)CSS3属性详解:动画详解+Flex布局图文详解+Web字体
前端开发入门笔记(八)CSS3属性详解:动画详解+Flex布局图文详解+Web字体
104 1
Hugo教程#3单页面布局
上集我们学习了如何创建一个index的布局,那么是不是所有的页面都要创建一个布局,那肯定不是的,那样的话实在太麻烦,因为只显对应页面的markdown里面的内容,所以创建一个single.html布局就可以了,所有的页面都可以使用这个布局
86 0
|
6月前
|
前端开发 JavaScript 算法
【web前端技术】响应式画廊Gallery插件-Justified-Gallery
【web前端技术】响应式画廊Gallery插件-Justified-Gallery
189 0
|
JavaScript 前端开发
仿百度排列图片预览插件-Simple Lightbox
仿百度排列图片预览插件-Simple Lightbox
56 0
|
API 开发者 索引
Flutter笔记:发布一个多功能轮播组件 awesome_carousel
awesome_carousel 模块是一个Flutter轮播图模块。可以实现包括自定义指示器、动画、滚动等等众多功能。能够指定相当多地细节(可以参考 API 部分了解)本文给出 awesome_carousel 模块的两个简单的用法示例。
299 0
|
容器
react-Ant Design框架项目中文字轮播与图片轮播的实现
在react-Ant Design框架项目中实现文字轮播和图片轮播,在这里记录一下,实现过程有一点小坑需要注意
656 0
react-Ant Design框架项目中文字轮播与图片轮播的实现
|
索引
SwiftUI极简教程26:构建一个Banner图片轮播(下)
SwiftUI极简教程26:构建一个Banner图片轮播(下)
709 0
SwiftUI极简教程26:构建一个Banner图片轮播(下)
|
Swift iOS开发
SwiftUI极简教程01:搭建一个新项目&Text文字的使用
SwiftUI极简教程01:搭建一个新项目&Text文字的使用
597 1
SwiftUI极简教程01:搭建一个新项目&Text文字的使用
|
存储 索引
SwiftUI极简教程42:使用MatchedGeometryEffect构建一个导航菜单
在本章中,你将学会使用MatchedGeometryEffect构建一个导航菜单。 在构建SwiftUI应用过程中,我们常常会使用TabView构建底部菜单,但更多的时候会由于我们定制化的需求,需要我们自己绘制底部菜单。 那么本章中,我们就来试试构建一个底部导航菜单。
452 0
SwiftUI极简教程42:使用MatchedGeometryEffect构建一个导航菜单