大概的实现思路有这么几种:
1 .UIScrollView
+UIImageView
1.1. 使用比 图片数 多 2 个的UIImageView
1.2 使用三个UIImageView
实现
1.3 使用 两个UIImageView
实现
2.UICollectionView
(当然UICollectionView
也是继承自UIScrollView
)
2.1 使用UICollectionView
的复用特性,复制 多份 图片作为数据源,从中间开始显示
2.2 在 第一张图片前加 上最后一张图片,在最后一张图片前加上第一张图片
注意:
1.
记得设置scrollView.isPagingEnabled = true
2.
控制UIScrollView
回滚的时候 不要使用动画,否则会调用代理方法
1.1 使用比 图片数 多 2 个的 UIImageView
实现
思路
: 创建 比图片数多 2个UIImageView
,最一个ImageView
显示第一张图片,第一个ImageView
显示最后一张图片,从第二个到倒数第二个ImageView
依次显示第一张到最后一张图片 当滚动到最后一张图片, 即 :原始 图片顺序为 1,2,3,4,5,6
,则八个ImageView
显示图片的顺序为6,1,2,3,4,5,6,1
,依次把ImageView
添加到UIScrollView
上,最开始显现第二个ImageView
,当滚动到第一个 ImageView
的时候,控制UIScrollView
滚动到倒数第二个 ImageView
,滚动到最后一个 ImageView
的时候,控制UIScrollView
滚到第二个ImageView
,只有 中间的 是正式用来显示的.两头的用来实现一个视差效果
核心代码如下
private lazy var mainScrollView : UIScrollView = {
let scrollView = UIScrollView(frame: bounds)
let scrollVWidth = bounds.width * CGFloat(images.count + 2)
scrollView.contentSize = CGSize(width: scrollVWidth, height: bounds.height)
scrollView.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
scrollView.delegate = self
scrollView.bounces = true
scrollView.isPagingEnabled = true
for (index, imageV) in imageVs.enumerated() {
imageV.frame = CGRect(x: bounds.width * CGFloat(index), y: 0, width: bounds.width, height: bounds.height)
scrollView.addSubview(imageV)
}
return scrollView
}()
// images 为根据传 过来的图片再在头尾分别加上最后一张和第一张图片创建的新数组
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x >= (bounds.width * CGFloat(images.count - 1)) {
mainScrollView.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
}
if scrollView.contentOffset.x <= 0 {
mainScrollView.setContentOffset(CGPoint(x: bounds.width * CGFloat(images.count - 2), y: 0), animated: false)
}
}
1.2 使用三个UIImageView
实现
思路: 方法1.1
的进化版,方法1.1
在有大量图片的情况下,需要创建大量的ImageView
明显不合适,所以我们 把中间的一个ImageView
片来代替 1.1
中的 大量中间图片,即只有中间一张图片是用来正式显示图片的,每次滚动结束都需要滚回到 中间图片位置,两边的ImageView
用来实现视差效果
核心代码如下
// 这里为每个 ImageView 添加一个 tag ,用来标识当前显示的图片和后面计算将要显示的图片
private lazy var leftImageV : UIImageView = {
let imageV = UIImageView(frame: bounds)
imageV.frame = CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height)
imageV.tag = images.count - 1
imageV.image = images[images.count - 1]
return imageV
}()
private lazy var MidleImageV : UIImageView = {
let imageV = UIImageView(frame: bounds)
imageV.frame = CGRect(x: bounds.width, y: 0, width: bounds.width, height: bounds.height)
imageV.tag = 0
imageV.image = images[0]
return imageV
}()
private lazy var RightImageV : UIImageView = {
let imageV = UIImageView(frame: bounds)
imageV.frame = CGRect(x: bounds.width * 2, y: 0, width: bounds.width, height: bounds.height)
imageV.tag = 1
imageV.image = images[1]
return imageV
}()
private lazy var imageVs : Array = {
return [leftImageV,RightImageV,MidleImageV];
}()
private lazy var mainScrollView : UIScrollView = {
let scrollView = UIScrollView(frame: bounds)
let scrollVWidth = images.count >= 3 ? bounds.width * 3 : bounds.width * CGFloat(images.count)
scrollView.contentSize = CGSize(width: scrollVWidth, height: bounds.height)
scrollView.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
scrollView.delegate = self
scrollView.bounces = true
scrollView.isPagingEnabled = true
scrollView.addSubview(leftImageV)
scrollView.addSubview(MidleImageV)
scrollView.addSubview(RightImageV)
return scrollView
}()
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
//判断 滚动方向,右滚下标 +1 ,左滚下标 -1
var direction : Int = 0
if scrollView.contentOffset.x > bounds.width {
direction = 1
}else if scrollView.contentOffset.x < bounds.width{
direction = -1
}
let imageVs = [leftImageV,MidleImageV,RightImageV]
// 根据图片tag 和滚动方向 计算 要显示的 图片,更新 tag 值
for imageV in imageVs {
var index = imageV.tag + direction
if index == images.count {
index = 0
}else if index == -1{
index = images.count - 1
}
imageV.image = images[index]
imageV.tag = index
}
mainScrollView.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
}
1.3 使用 两个 UIImageView
实现
思路:
UIScrollView
的 contentSize
依然 为 三倍 屏幕宽度,创建两个 ImageView
添加到 UIScrollView
上, 没有滚动时,两个imageView
重叠,上面的ImageView
显示当前图片,发生滚动的时候,判断滚动方向,把底下的ImageView
移到 左边或者右边,滚动结束,UIScrollView
滚动 回 中间位置
核心代码如下:
private var isChange : Bool = true
let images : Array
lazy var bottomImageView : UIImageView = {
let imageView = UIImageView(image: UIImage(named: "2.png"))
imageView.frame = CGRect(x: bounds.width, y: 0, width: bounds.width, height: bounds.height)
imageView.tag = 0
return imageView
}()
lazy var topImageView : UIImageView = {
let imageView = UIImageView(image: UIImage(named: "1.png"))
imageView.frame = CGRect(x: bounds.width, y: 0, width: bounds.width, height: bounds.height)
imageView.tag = 0
return imageView
}()
lazy var scrollView : UIScrollView = {
let scroView = UIScrollView(frame: CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height))
scroView.contentSize = CGSize(width: bounds.width * 3, height: bounds.height)
scroView.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
scroView.isPagingEnabled = true
scroView.bounces = false
scroView.delegate = self
scroView.addSubview(bottomImageView)
scroView.addSubview(topImageView)
return scroView
}()
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
imageOffset = scrollView.contentOffset.x
}
// 每次滚动的时候 判断 滚动方向,调整底下的 imageview 的位置和显示的图片
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if isChange{
if scrollView.contentOffset.x < bounds.width {
imageOffset = scrollView.contentOffset.x
var tag = bottomImageView.tag - 1
if tag < 0{
tag = images.count - 1
}
bottomImageView.image = images[tag]
bottomImageView.frame = CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height)
bottomImageView.tag = tag
}else if scrollView.contentOffset.x > bounds.width {
imageOffset = scrollView.contentOffset.x
var tag = bottomImageView.tag + 1
if tag == images.count{
tag = 0
}
bottomImageView.image = images[tag]
bottomImageView.frame = CGRect(x: bounds.width * 2, y: 0, width: bounds.width, height: bounds.height)
bottomImageView.tag = tag
}
}
isChange = false
}
//滚动结束, scrollView 回滚到中间位置,更新 上面的 ImageView 的图片
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
bottomImageView.frame = CGRect(x: bounds.width, y: 0, width: bounds.width, height: bounds.height)
topImageView.frame = CGRect(x: bounds.width, y: 0, width: bounds.width, height: bounds.height)
print(bottomImageView.tag)
topImageView.image = images[bottomImageView.tag]
scrollView.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
isChange = true
}
这里 因为
scrollViewDidScroll
代理滚动过程中会一直调用,而我这里只需要判断 一下滚动方向然后调整 底下的ImageView
, 所以用的 一个BOOL
值来限制执行,当时没想到一个比较好的方案,所以实现看起来比较low
2.1 使用UICollectionView
的复用特性,复制 多份 图片作为数据源,从中间开始显示
思路:
复制 多份 需要显示的图片 作为UICollectionView
数据源,开始的时候 就把scrollView
滚动到中间那份 数据的开头位置,因为复制的多份数据,所以滚动的时候就会无限轮播的效果,这里就不贴具体代码,大家可以自行 尝试下
2.2 在 第一张图片前加 上最后一张图片,在最后一张图片前加上第一张图片
思路:
和1.1
方法类似,只是使用 UICollectionView
的话因为UICollectionView
自身的复用机制,所以没有 1.1
方法 创建大量 ImageView
的性能问题,同样的,在首尾 分别加上最后和第一张图片,当 滚动到这两个位置的时候,控制 scrollView
滚动到中间显示区的对应位置
核心代码如下:
let images : Array
//创建 UICollectionView
lazy var collectionV : UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.itemSize = CGSize(width: bounds.width, height: bounds.height)
layout.scrollDirection = .horizontal
let collectionV = UICollectionView(frame: CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height), collectionViewLayout: layout)
collectionV.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
collectionV.dataSource = self
collectionV.delegate = self
collectionV.isPagingEnabled = true
return collectionV
}()
//根据传进来的 图片数组构建 数据源数组
init(frame: CGRect,images :Array ) {
var tempImages : Array = [images[images.count - 1]]
for image in images {
tempImages.append(image)
}
tempImages.append(images[0])
self.images = tempImages
super.init(frame: frame)
addSubview(collectionV)
collectionV.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
}
extension XlXBannerCollectionV :
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.imageV = UIImageView(image: images[indexPath.row])
return cell
}
// 滚动到 首尾的时候控制回滚
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if scrollView.contentOffset.x >= (bounds.width * CGFloat(images.count - 1)) {
collectionV.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
}
if scrollView.contentOffset.x <= 0 {
collectionV.setContentOffset(CGPoint(x: bounds.width * CGFloat(images.count - 2), y: 0), animated: false)
}
}
}
无限轮播 整体 上就是 中间部分 是用来正式显示的,首尾的是 在滚动的时候让用户有种 后面还有内容的错觉,当滚动停止,立马回滚到中间显示区的对应位置,来达到无限轮播的视觉效果
自动轮播的话在
scrollView
代理方法里面合适的位置开启或暂停定时器来实现,代码中剔除了其他干扰只保留了核心部分,因为功能比较简单,所以也没注意代码卫生,实际开发中,各位小伙伴们还是要多多注意下代码习惯,抽取封装,配置工具类什么的来实现的漂亮一点
最后:写完了才想起来两张图片的情况下没做处理,可能还是会有些问题吧,道理都是一样的,实际开发中多多注意下