XZCarouselView:iOS 轮播图、仿微信大图查看界面转场效果

说明

轮播图在 iOS 开发中属于常见需求,虽然第三方实现很多,但是真正让人满意的并不多,为了满足业务需求,于是本人就研发了 XZCarouselView 这款组件,支持无限轮播、缩放、自动轮播、垂直滚动、横向滚动等功能。

效果

XZImageCarouselViews示例 XZImageViewer示例 XZCarouselViewController示例

安装

推荐使用 CocoaPods 安装到项目中:

pod 'XZKit/CarouselView'

XZCarouselViewXZKit 框架的子模块,可以单独引用,无其它依赖。

特性

XZCarouselViewCarouselView 模块中的基础类,提供轮播图机制,方便再封装和自定义,当然也可以直接使用。CarouselView 中的图片轮播图 XZImageCarouselView 组件、大图查看器 XZImageViewer 组件、多控制器容器 XZCarouselViewController 都是基于类 XZCarouselView 来实现的。
XZCarouselView 除了支持无限轮播、自动轮播、轮播方向、缩放等常用功能外,还支持内容自适应、获取轮播进度、自适应布局方向(针对阿拉伯语等自右向左布局等系统)、重用机制、添加自定义切换动画等功能,基本能满足大部分业务中对轮播图的需求。
XZCarouselView 最大的特点应该就是支持切换进度事件的获取,特别方便做联动效果。比如大部分资讯 App 首页的多栏目结构,内容列表与菜单的联动,用 UIPageViewController 做列表虽然好用,但是难以获取轮播进度,联动效果不方便处理。所以某些资讯 App 的首页,联动效果处理的就不够自然。比如比较知名 CSDN、IT之家的 App 首页,在栏目页面连续切换时,栏目菜单无法正常滚动,点击栏目菜单,栏目页面切换也没有动画效果。而 XZCarouselView 在处理这个问题上,就得心应手的多,在子组件 XZCarouselViewController示例代码中,就展示了如何用 XZCarouselView 来实现更自然的联动效果,即上面的效果图3。
另外 XZCarouselView 使用中文注释,大部分属性或方法都有详细的注释文案,更多特性请移步阅读源代码

如何使用

1. XZCarouselView 轮播视图

轮播视图组件 XZCarouselView 是 UIView 子类,像 UIView 一样使用即可。因为属于 XZKit 框架,如果使用 CocoaPods 引用的话,在 Swift 中类名就不需要加前缀 XZ ,使用 CarouselViewXZKit.CarouselView 来代替它在 ObjectiveC 中 XZCarouselView 命名。

let carouselView = CarouselView.init(frame: UIScreen.main.bounds);
self.addSubview(carouselView)

通过属性可以控制轮播图的各种特性,下面是 XZCarouselView 的主要属性说明。

/// 当前正在显示的视图的索引。
open var currentIndex: Int
/// 视图在XZCarouselView中的适配方式。
open var contentMode: UIView.ContentMode
/// 是否无限轮播。
open var isWrapped: Bool
/// 设置缩放倍数。
open func setMinimumZoomScale(_ minimumZoomScale: CGFloat, maximumZoomScale: CGFloat)
/// 在缩放状态下,是否禁止左右滚动。
open var isZoomingLockEnabled: Bool
/// 是否记住视图的缩放状态。
open var remembersZoomingState: Bool
/// 设置自动轮播的时间间隔。
open var timeInterval: TimeInterval
/// 自动轮播方向。
open var pagingDirection: CarouselView.ScrollDirection
/// 轮播图的滚动方向。
open var pagingOrientation: CarouselView.PagingOrientation
/// 重载轮播图视图。
open func reloadData()
/// 是否启用内置的重用池
open var isReusingModeEnabled: Bool
/// 设置视图间距。
open var interitemSpacing: CGFloat
/// 是否保持左右视图始终显示。
open var keepsTransitioningViews: Bool

2. XZImageCarouselView 图片轮播视图

图片轮播图 XZImageCarouselView 继承自 XZCarouselView ,主要提供了直接使用 UIImage 数组或者图片 URL 数组作为数据源的功能,不用重复的去实现数据源代理协议。

let carouselView = ImageCarouselView.init(frame: UIScreen.main.bounds);
// 以图片链接作为数据源。
carouselView.imageURLs = self.imageURLs 
// 以 UIImage 对象作为数据源,轮播图此数据源图片。
carouselView.images = self.images
// 如果同时设置两种数据源,推荐使用下面的方法。
carouselView.setImages(self.images, imageURLs: self.imageURLs)

3. XZImageViewer 仿微信全屏大图查看控制器

图片大图查看器 XZImageViewer 在内部管理了一个 XZCarouselView ,用来展示图片,其自身主要是处理数据源及转场逻辑。
转场效果仿微信朋友圈大图查看的效果,并在优化了其中几点:微信朋友圈大图查看界面入场时,背景不是渐变的;拖拽退出时显示的背景,实际上是屏幕截图,且普通屏幕在 in-call 状态(来电或共享热点)下,背景向下偏移了

let viewer = ImageViewer.init()
viewer.delegate     = self
viewer.dataSource   = self
viewer.currentIndex = indexPath.item // 设置 imageVier 默认显示的图片。
viewer.maximumZoomScale = 3.0
viewer.contentMode = .scaleAspectFit
viewer.isZoomingLockEnabled = false
self.present(viewer, animated: true, completion: nil); // 展示大图查看器,self 为当前控制器。

为了实现类似微信大图查看的转场效果,代理协议 XZImageViewerDelegate 除了必须实现的方法外,需实现下面两个可选方法才能达到效果。

// 实现这个方法,XZImageViewer 才能知道原始视图在屏幕上位置,也就是转场动画的起点。
func imageViewer(_ imageViewer: ImageViewer, sourceRectForItemAt index: Int) -> CGRect {
    guard let cell = collectionView.cellForItem(at: IndexPath.init(item: index, section: 0)) as? Example2CollectionViewCell else { return .zero }
    return cell.convert(cell.bounds, to: self.view)
}							
// 这个方法主要是来优化转场效果的,告诉 XZImageViewer 原始视图的大小与图片展示方式,才能作出完美的转场动画。
func imageViewer(_ imageViewer: ImageViewer, sourceContentModeForItemAt index: Int) -> UIView.ContentMode {
    return .scaleAspectFill
}

4. XZCarouselViewController 多控制器轮播容器

使用 XZCarouselView 实现的多控制器容器,用来替代 UIPageViewController 的最佳选择。借助于 XZCarouselView 特性,可以方便的处理控制器与菜单的动画效果,让切换效果看起来更自然。

// 菜单的点击事件。比如用 UICollectionView 实现的菜单,被点击时,只需要两行代码。
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true) // 滚动选中的菜单到中间
    carouselViewController.setCurrentIndex(indexPath.item, animated: true) // 设置当前页面
}
// 列表页面切换事件。
func carouselViewController(_ carouselViewController: CarouselViewController, didShow viewController: UIViewController, at index: Int) {
    self.collectionView.scrollToItem(at: IndexPath(item: index, section: 0), at: .centeredHorizontally, animated: true) // 滚动菜单到中间
}
// 过渡效果只需要在一个方法就行了。menuTranstion(to:transition:) 方法为根据当前 menuIndex、目标 newIndex、transition 来处理过渡效果的方法,具体参考示例代码。
func carouselViewController(_ carouselViewController: CarouselViewController, didTransition transition: CGFloat, animated: Bool) {
    let newIndex = carouselViewController.currentIndex
    if menuIndex == newIndex { // menuIndex 为菜单当前已选中的 index 
        if transition > 0 { // 滚往下一个。
            menuTranstion(to: menuIndex + 1, transition: transition)
        } else if transition < 0 {
            menuTranstion(to: menuIndex - 1, transition: -transition)
        } else { // 滚动取消
            menuTranstion(to: menuIndex, transition: 0)
            collectionView.reloadData() // 重置上面目标菜单的转场进度。
        }
    } else { // 页面已跳转到新的 index 。
        if (transition == 0) { // 完成跳转
            menuIndex = newIndex
            menuTranstion(to: menuIndex, transition: 0)
            collectionView.reloadData()
        } else { // 跳转中。
            menuTranstion(to: newIndex, transition: 1.0 - abs(transition))
        }
    }
}

XZCarouselViewController 已实现了更精确的机制,来控制子控制器的生命周期,可以更准确的把握子控制器的状态。因此与 UIPageViewController 不同,XZCarouselViewController 中的子控制器,只有在 XZCarouselViewController 处于显示状态时,才会触发 viewDidAppear 方法,即子控制器的 viewDidAppear 调用时,表示其确已完全显示在屏幕上(如果 XZCarouselViewController 触发 viewDidAppear 时在屏幕上)。

// 无示例代码。

一般来说,控制器重用的可能性比较小,但是并不是完全不需要,对于资讯类 App 来说尤其如此,因为大部分栏目共用的是同一个控制器类型。基于 XZCarouselView 的设计优势,使用 XZCarouselViewController 实现重用也变得很简单。

// 因为示例所用的子控制器都是同一类型,只有单一类型时,设置 isReusingModeEnabled 属性为 true 就可以实现重用。
// 这里为了模拟自定义重用机制,假定前 5 个栏目是专栏,使用的控制器类型互不相同,其它栏目使用相同类型的控制器。
func carouselViewController(_ carouselViewController: CarouselViewController, viewControllerFor index: Int, reusing reusingViewController: UIViewController?) -> UIViewController? {
    if index < 5 {
        if let viewController = indexedViewControllers[index] {
            return viewController
        }
        let webViewController = Example3ContentViewController.init(index: index) // 创建不可重用控制器
        webViewController.title = pages[index].title
        webViewController.load(url: pages[index].url)
        indexedViewControllers[index] = webViewController
        return webViewController
    }
    if reusableViewControllers.isEmpty {
        let webViewController = Example3ContentViewController.init(index: index) // 创建可重用控制器
        webViewController.title = pages[index].title
        webViewController.load(url: pages[index].url)
        return webViewController
    }
    let webViewController = reusableViewControllers.removeLast() // 复用可重用控制器
    webViewController.title = pages[index].title
    webViewController.load(url: pages[index].url)
    return webViewController
}
// XZCarouselViewController 询问已移除的控制器是否可以走内部的重用机制,返回 false 。
// 注意:内部重用机制,强引用的是控制器的视图,因此如果使用的话,需要对控制器强引用。
func carouselViewController(_ carouselViewController: CarouselViewController, shouldEnqueue viewController: UIViewController, at index: Int) -> Bool {
    guard index >= 5 else {
        return false
    }
    reusableViewControllers.append(viewController as! Example3ContentViewController) // 回收可重用控制器
    return false
}

实现机制

XZCarouselView 使用三图实现轮播,但是三图只是为了方便计算,一般情况下,轮播图上只有两图参与轮播(在 keepsTransitioningViews 属性为 true 时,因为要显示前后的图片,需要同时加载 4 张图片)。

示例代码

XZKit 框架开源项目中,就有完整的 XZCarouselView 测试代码,分别就三种常用场景分别写了示例,欢迎前往 Star&Fork

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值