本帖最后由 Ding 于 2016-11-2 16:58 编辑

最近项目又来了新需求,要像这样展示轮播图:

灵活的轮播

灵活的轮播 RingedPages - 敏捷大拇指 - 灵活的轮播


所以封装了一个RingedPages控件。

灵活的轮播

灵活的轮播 RingedPages - 敏捷大拇指 - 灵活的轮播


灵活的轮播

灵活的轮播 RingedPages - 敏捷大拇指 - 灵活的轮播


灵活的轮播

灵活的轮播 RingedPages - 敏捷大拇指 - 灵活的轮播


这次充分考虑了灵活性:

首先,page control(就是指示页面总数及当前页面的控件)除了系统默认的小圆点,还要支持任意图片,所以写了个ImagePageControl;ImagePageControl内部水平方向支持左、中、右对齐,垂直方向支持上、中、下对齐,也有一些其他属性可以设置,如各个指示图(圆点)之间的距离等。

其次,就是主角PagesCarousel(除了page controll之外的那部分)。我们先来看看对外开放的部分:
[Swift] 纯文本查看 复制代码
public protocol PagesCarouselDataSource {
    func numberOfItems(inCarousel carousel: PagesCarousel) -> Int
    func carousel(_ carousel: PagesCarousel, pageForItemAt index: Int) -> UIView
}

public protocol PagesCarouselDelegate {
    func carousel(_ carousel: PagesCarousel, didScrollTo index: Int)
    func didSelectCurrentPage(in carousel: PagesCarousel)
}
public extension PagesCarouselDelegate {
    func carousel(_ carousel: PagesCarousel, didScrollTo index: Int) {}
    func didSelectCurrentPage(in carousel: PagesCarousel) {}
}

open class PagesCarousel: UIView, UIScrollViewDelegate {
    open var mainPageSize = CGSize.zero
    open var pageScale: CGFloat = 1.0
    open var autoScrollInterval: TimeInterval = 5.0 // is <= 0, will not scroll automatically
    
    open var dataSource: PagesCarouselDataSource?
    open var delegate: PagesCarouselDelegate?
    
    open var currentIndex: Int {
        get {
            return p_currentIndex
        }
    }
    open func reloadData() {
        needsReload = true
        for view in scrollView.subviews {
            view.removeFromSuperview()
        }
        removeTimer()
        setNeedsLayout()
    }
    open func dequeueReusablePage() -> UIView? {
        let page = reusablePages.last
        if page != nil {
            reusablePages.removeLast()
        }
        return page
    }
    open func scroll(to index: Int) {
        if index < pageCount {
            removeTimer()
            indexForTimer = index + orginPageCount
            let point = CGPoint(x: mainPageSize.width * CGFloat(index + orginPageCount), y: 0)
            scrollView.setContentOffset(point, animated: true)
            setPages(at: scrollView.contentOffset)
            refreshVisiblePageAppearance()
            addTimer()
        }
    }
}


可以看到,这是一个容器视图,通过代理的方式为您服务。轮播的可以是任意UIView而不只是图片。可以设置最中间的page的大小(mainSize)、中间滑倒两边后的缩放比例(pageScale)等,如果把mainSize设置成整个容器的大小,其实就跟一般的轮播一个样子了。另外可以注意到考虑了视图的重用,有一个deque方法可用,很像UITableView。考虑到轮播的一般都是同种类型的View,不像table view的cell那样可能有多种,所以无论从接口到实现都要简单点。

ImagePageControl和PagesCarousel完全独立,可以分开使用。

最后来一个RingedPages包装一下上边提到的page controll和pages carousel,增加一些便利属性,如pageControll的位置可以在轮播视图的上方、内部顶部、内部底部或下方之类。同样写好代理协议方便使用。

[Swift] 纯文本查看 复制代码
public protocol RingedPagesDataSource {
    func numberOfItems(in ringedPages: RingedPages) -> Int
    func ringedPages(_ pages: RingedPages, viewForItemAt index: Int) -> UIView
}
public protocol RingedPagesDelegate {
    func didSelectCurrentPage(in pages: RingedPages)
    func ringedPages(_ pages: RingedPages, didScrollTo index: Int)
}
extension RingedPagesDelegate {// This extension makes the protocal optional~
    func didSelectCurrentPage(in pages: RingedPages) {}
    func ringedPages(_ pages: RingedPages, didScrollTo index: Int) {}
}


如果对具体实现有兴趣,可以看源码。

GitHub链接:RingedPages

也写了Objective-C版的:RPRingedPages


GitHub上看到一个issue,做了解释,也放在这吧:

灵活的轮播 RingedPages

灵活的轮播 RingedPages - 敏捷大拇指 - 灵活的轮播 RingedPages