使用 UICollectionView 实现首页卡片轮播效果

网友投稿 343 2022-08-24

使用 UICollectionView 实现首页卡片轮播效果

前言

今天跟大家来聊聊一个强大的 UI 控件: UICollectionView。UICollectionView 是 iOS6 之后引入的一个新的 UI 控件,与 UITableView 有着很多相似的地方,在开发过程中我们都会选择使用它们俩来为 App 的整个页面进行布局,比如说淘宝的首页;相比 UITbleView,UICollectionView 的功能比它要强大的多,它支持水平与垂直俩种方向的布局,开发者可以完全自定义一套 layout 布局方案,实现出意想不到的效果。

废话不多说,接下来,咱就步入正题吧!如何使用 UICollectionView 实现网易云首页卡片轮播效果。

思路分析

通过观察上面的图我们可以得出,这个网易云的轮播控件有三个特点,分别是:

支持图片手动横向滚动支持图片自动的滚动播放底部的分页控件会高亮显示出当前的图片是哪一张

好了,既然已经分析出来了它的特点,那接下来就进入到编程环节吧!

JUST DO IT

想到滚动,大家首先想到的肯定是用 UIScrollView + UIImageView 的方式来实现,但是 UICollectionView 给我们提供了更好的选择,因为它本身继承自 UIScrollView 然后又支持横向滚动,所以使用 UICollectionView 来实现横向滚动效果是最好不过的。

代码片段如下:

// 布局 private var collectionViewFlowLayout: UICollectionViewFlowLayout! // collection private var collectionView: UICollectionView! // 构建 UI private func configUI() { collectionViewFlowLayout = UICollectionViewFlowLayout() collectionViewFlowLayout.scrollDirection = .horizontal collectionViewFlowLayout.minimumLineSpacing = 0 collectionViewFlowLayout.minimumInteritemSpacing = 0 collectionViewFlowLayout.sectionInset = UIEdgeInsets.zero collectionViewFlowLayout.itemSize = CGSize(width: self.frame.size.width, height: self.frame.size.height) collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height), collectionViewLayout: collectionViewFlowLayout) collectionView.register(JJNewsImageViewCell.self, forCellWithReuseIdentifier: JJScrollBannerCellID) collectionView.isPagingEnabled = true collectionView.showsVerticalScrollIndicator = false collectionView.showsHorizontalScrollIndicator = false collectionView.delegate = self collectionView.dataSource = self collectionView.backgroundColor = UIColor.clear self.addSubview(collectionView) } // MARK: - UICollectionViewDelegate, UICollectionViewDataSourceextension JJNewsBanner :UICollectionViewDelegate, UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return self.totalItemCount } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { if self.imageUrlStrArray != nil { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: JJScrollBannerCellID, for: indexPath) as! JJNewsImageViewCell cell.setupUI(imageName: nil, imageUrl: (self.imageUrlStrArray != nil ? self.imageUrlStrArray![indexPath.row].pic : nil), placeholderImage: self.placeholderImage, contentMode: self.myContentMode) return cell } else { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: JJScrollBannerCellID, for: indexPath) return cell } } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath){ if self.itemDidClickedBlock != nil { self.itemDidClickedBlock!(indexPath.row % self.sourceCount) } }}

然后,支持图片的自动播放与分页控件高亮就比较简单了,我们可以使用定时器 Timer 与 UIPageController 控件来实现。

代码片段如下:

// 定时器 private var scrollTimer: Timer? // 是否自动轮播 public var autoScroll = true { didSet { self.invalidateTimer() if autoScroll { self.setupTimer() } } } // 轮播时间间隔 public var autoScrollTimeInterval: TimeInterval = 2.0 { didSet { self.invalidateTimer() if autoScrollTimeInterval > 0 { self.setupTimer() } } } // 分页控件 private var pageControl: UIPageControl? // 轮播次数 private var loopTimes = 100 // 分页控件位置 public var pageControlAliment: PageControlAligment = .center // 分页控件类型 public var pageControlType: PageControlType = .classic // 当前分页控件颜色 public var currentPageDotColor = UIColor.white // 默认分页控件颜色 public var pageDotColor = UIColor.gray // 分页控件默认距离的边距 public var pageControlMargin: CGFloat = 10 // 分页控件大小,注意:当PageControlType不等于自定义类型时,只能影响当前分页控件的大小,不能影响分页控件原点的大小 public var pageControlDotSize: CGSize = CGSize(width: 10, height: 10) // 设置定时器 public func setupTimer() { self.invalidateTimer() if self.autoScroll { self.scrollTimer = Timer.scheduledTimer(timeInterval: self.autoScrollTimeInterval, target: self, selector: #selector(automaticScroll), userInfo: nil, repeats: true) RunLoop.main.add(self.scrollTimer!, forMode: .common) } } // 使定时器失效 public func invalidateTimer() { if self.scrollTimer != nil { self.scrollTimer?.invalidate() self.scrollTimer = nil } } @objc private func automaticScroll(){ if self.totalItemCount == 0 { return } var targetIndex = self.currentIndex() + 1 self.scrollToIndex(targetIndex: &targetIndex) }

到这里这个轮播控件的功能已经初步完成了,但是如果要正式在 app 中使用,并且达到很好的用户体验还是有很大的优化空间的。

首先第一点,我们要对 UIPageControl 的样式进行调整,加上约束,并提供一个获取当前页索引的接口,代码如下:

extension JJNewsBanner { override func layoutSubviews() { super.layoutSubviews() if self.collectionView.contentOffset.x == 0 && self.totalItemCount > 0 { var targetIndex = 0 if self.loopTimes > 0 { targetIndex = 0 } if self.collectionView.numberOfItems(inSection: 0) == self.totalItemCount && self.loopTimes > 1 { self.startScrollToItem(targetIndex: targetIndex, animated: false) } } if self.pageControl != nil { var pSize: CGSize = CGSize(width: 0, height: 0) if self.pageControl!.isKind(of: UIPageControl.self) { pSize = CGSize(width: CGFloat(self.sourceCount) * self.pageControlDotSize.width, height: self.pageControlDotSize.height) } let pX: CGFloat = 0 let pY = self.frame.height - margin - pSize.height - pageControlMargin let pageControlFrame = CGRect(x: pX, y: pY, width: self.frame.width, height: pSize.height) self.pageControl!.frame = pageControlFrame if #available(iOS 14.0, *) { self.pageControl?.backgroundStyle = .automatic } } } // 设置滚动分页控件 private func setupPageControl() { if self.imageUrlStrArray == nil { return } if self.pageControl != nil { self.pageControl?.removeFromSuperview() } switch self.pageControlType { case .none: self.pageControl = nil case .classic: let tmpPageControl = UIPageControl() tmpPageControl.numberOfPages = self.sourceCount tmpPageControl.currentPageIndicatorTintColor = self.currentPageDotColor tmpPageControl.pageIndicatorTintColor = self.pageDotColor tmpPageControl.isUserInteractionEnabled = false tmpPageControl.currentPage = self.pageControlIndex(cellIndex: self.currentIndex()) self.addSubview(tmpPageControl) self.pageControl = tmpPageControl case .custom: self.pageControl = nil } } // 页转换 private func pageControlIndex(cellIndex: Int) -> Int { if self.sourceCount > 0 { return cellIndex % self.sourceCount } else { return 0 } } // 当前页面索引 private func currentIndex() -> Int { if collectionView.frame.width == 0 || collectionView.frame.height == 0 { return 0 } var index = 0 index = Int((self.collectionView.contentOffset.x + self.collectionViewFlowLayout.itemSize.width * 0.5) / self.collectionViewFlowLayout.itemSize.width) return max(0, index) }}

第二点,由于这个轮播图滚动支持手动滚动与自动滚动俩种方式,所以要加上控制的逻辑,当我们手动滚动查看图片的时候,定时器就失效,当我们手势拖拽动画结束的时候再重新开启定时器,实现代码如下:

override func willMove(toSuperview newSuperview: UIView?) { if newSuperview == nil { self.invalidateTimer() } } // 拖拽动画开始 public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { self.invalidateTimer() } // 拖拽动画停止 public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { self.setupTimer() }

对以上俩点进行优化处理后,我们的轮播控件就否就可以披挂上阵了呢!万事具备,只欠东风啊(数据),最后还得给轮播控件提供一个对外的数据加载接口,代码如下:

// 网络图片URL private var imageUrlStrArray: [BannerModel]?{ didSet{ self.collectionView.reloadData() self.setupPageControl() self.invalidateTimer() if autoScroll { self.setupTimer() } self.layoutIfNeeded() } } // 更新 UI public func updateUI(imageUrlStrArray: [AnyObject]?, placeholderImage: UIImage?){ self.imageUrlStrArray = imageUrlStrArray as? [BannerModel] self.placeholderImage =

结尾

今天文章的到这里就结束了,内容相对来说比较简单,里面阐述的文字部分比较少,代码比较多(比较乱),有的同学可能看的不是很明白,那是因为我展示的代码只是局部的代码片段,主要是想给大家简单的讲述一下我的实现思路,因为代码太多,对于大家的阅读体验是非常不好的,所以我打算在最下方留下代码的链接,如果大家感兴趣的话,可以直接通过这个链接去获取全部代码,最后看一下实现后的效果吧!

全部代码链接: ​​github.com/ShenJieSuzh…​​

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:某房企发布五一“黄金周”营销计划,鼓励全员卖房,真的可行吗?
下一篇:手撸二叉树之第二小的节点
相关文章

 发表评论

暂时没有评论,来抢沙发吧~