前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用 UICollectionView 实现首页卡片轮播效果

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

作者头像
HelloWorld杰少
发布2022-08-04 14:02:04
1.9K0
发布2022-08-04 14:02:04
举报
文章被收录于专栏:HelloWorld杰少

前言

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

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

思路分析

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

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

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

JUST DO IT

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

代码片段如下:

代码语言:javascript
复制
    // 布局
    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, UICollectionViewDataSource
extension 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 控件来实现。

代码片段如下:

代码语言:javascript
复制
// 定时器
    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 的样式进行调整,加上约束,并提供一个获取当前页索引的接口,代码如下:

代码语言:javascript
复制
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)
    }
}
    }

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

代码语言:javascript
复制
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()

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

代码语言:javascript
复制
// 网络图片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 = placeholderImage
    }

结尾

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

全部代码链接:https://github.com/ShenJieSuzhou/SwiftScrollBanner

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-01-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 HelloWorld杰少 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 思路分析
  • JUST DO IT
  • 结尾
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档