这两天使用Reveal工具查看"手机淘宝"App的UI层次时,发现其图片轮播使用了三个UIButton的复用来实现的图片循环无缝滚动。于是乎就有了今天这篇博客,看到“手机淘宝”这个幻灯片的UI层级时,就想要动手使用三个Button来实现一下,当然本篇博客使用是Swift语言,思路就是使用三个Button进行图片无限轮播。之前发过两篇关于图片轮播的博客,一个是没有使用ImageView复用的,一个是使用两张ImageView进行复用来实现图片轮播的,都是使用的Objective-C实现的,并在github上进行了代码分享。
本篇博客就是介绍如何去一步步的封装这个三个Button的无限轮播的组件的。在实现时将该无限轮播的组件进行了封装,需要你在实例化该组件后,传入一个存放图片地址的数组,数组中可以是本地图片的名字,也可以是一个网络图片的地址。如果是网络图片的地址,组件中会使用NSRULSession并采用合适的缓存策略从网络加载图片,加载完后在我们的组件上进行显示。废话少说,进入我们今天的主题。
今天的博客完全是个人兴趣爱好,也不是公司中项目要使用的缘故才去实现的。就是看到了淘宝中的轮播图,从UI层级上感觉这样可以实现,于是乎就打开Xcode,创建个Swift工程实现一把,过程还是蛮愉快的。当然今天封装组件的名称是以公司的名称首字母做的前缀了,这也便于在以后的工作中遇到该问题直接拿过来就用呢。
一、对“手机淘宝”UI层次的简单分析
下方截图就是当时我用Reveal工具查看的手机淘宝App的UI层级,下方只是幻灯片的部分。下方整个页面是使用UICollectionView来实现的了,下方的幻灯片所在的UIScrollView就放在UICollectionView上的一个Cell上。当然这不是我们今天的重点,我们将目光转移到左边红框中的数学层级上。可以明显的看出UIScrollView上贴了三个UIButton,而每个UIButton上又贴了一个UIImageView。
需要注意的一点是你看到UIButton是三个Button的中间一个,从下面UI视图中我们不难分析出,无论当前显示的是第几张图片,你看到的永远是中间Button上显示的图片。也就是将要显示的图片放到中间的Button上,左边的Button存放上一张图片,右边的Button存放下一张图片。当用户左划或者右划后,我们要做的事情就是将中间按钮放到可视区,并且设置成要显示的图片,当然左右两个Button都得设置成相应的图片,便于用户下次移动。当然这只是我的个人猜测,上面这种思路使用代码来实现是完全可行的,并且可以实现无循环无缝滚动了。
当然,上面说的原理比较简单,具体使用代码实现起来还是需要许多细节的。比如用户滑动时定时器的挂起和唤醒,用户左右滑动方向的记录,便于自动轮播时的方向与用户上次滑动的方向一致,异步加载网络请求图片,加载后显示在相应的Button上,通过Closure回调出用户点击事件可当前图片的索引信息等等需要注意的细节。敲代码也有几年时间了,有一点感触挺深的就是“说和做”完全是两码事,也就是你有思路和你把你的思路用代码实现,这是完全不同的概念。当然正确的思路固然重要,但是不去实现再好的思路也是枉费,实践出真知吗。说这么多,有回到了说与做的问题上,这话题就不多讨论了,高执行力,高自制力好处是多多的。言归正传,回到今天的主题。
二、从“原理图”中来看今天轮播的主题
下方就是今天要实现的轮播组件的原理图,思路就是这么个思路,要实现起来还得靠代码往上摞呢。下方解析图是以轮播5张图片为例。初始化状态是你看到的是第一张图片,第一张图片位于三个Button的中间,然后左边就是最后一张图片,右边是第二张图片。如果用户想右滑动显示的第二张图片(对应着下方的第一步),当第二张图片已经完全显示出来后,我们要做的第一件事情就是将用户移动的位置进行复位,也就是将第二个Button移动到显示的位置,然后设置按钮上显示相应的图片。第一个按钮就显示当前图片的前一张图片,如果当前显示的是第一张图片,那么第一个按钮上就显示最后一张图片。第三个按钮就显示当前图片的下一张图片,如果当前显示的图片是最后一张图片的话,那么第三个按钮上就显示第一张图片。这样就可以图片轮播了。
三、该自定义控件的视图层级
下图就是我们按照上面的思路,使用ScrollView上添加3个Button来实现的图片轮播,无缝滚动的效果还是蛮OK的。在下方运行效果中美女图片是从本地加载的,而风景图片是使用NSURLSession和GCD的东西并行异步的从网络获取的,获取完后再加载到相应Button的ImageView上。下方就是我们本篇博客相关Demo的运行效果,单从效果上来看,与之前所发布的图片轮播没有什么区别,但是其内部实现机制还是有较大区别的。接下来就逐步的看一下代码实现。
上面是运行效果,下方是UI的层级。当然也是用我们强大的Reveal来查看的了,下方红框中就是我们图片轮播的视图层级。该视图层级与“手机淘宝”上是一致的,都是在UIScrollView上贴了三个Button,然后在每个Button上贴了一个ImageView。层级比较简单,而你看到的当然是中间Button上的ImageView了,更多细节请看下图。
四、代码实现
运行效果看完了,UI层级结构也看完了,那么接下来就到了我们代码实现的时刻了。下方从组件的调用方式入手,逐步的去看一下上述效果是如何实现的。
1.上述组件的调用方式
下方代码段就是上述组件的实例化和调用方式,首先进行初始化,然后将该组件添加到父视图上。之后将要显示的图片数组imagesNameArray传入组件中,最后设置一下组件的闭包回调即可,该回调将每个按钮点击的时间回调给组件的使用者,该Closure的参数是当前点击按钮上所显示的Image的索引,说白了也就是你当前点击的第几张图片。
在调用组件时,传入给组件的参数是一个数组,下方代码就是我们初始化imagesNameArray的函数。从该代码段中我们不难看出,该数组中存储的有本地图片的名字,也有UIImage的对象,也有网络图片的URL。将含有三种元素的数组传给我们的组件实例,这些数组中的资源就可以按照数组中的顺序依次的循环轮播了。
2.上述组件的核心代码
组件调用方式还是蛮简单的,看完调用方式,接下来来看一下具体的代码实现。下方是我们组件中比较核心的实现部分。
(1).异步加载网络图片
下方代码段就是遍历我们的ImagesNameArray,找出该数组中所有的网络图片的URL,每找到一个URL就开启一个线程来请求该地址中的图片。下方的queue是并行队列,此处当然是并行队列的异步执行了。关于GCD的东西,请参考之前的博客《Grand Central Dispatch详解》。
下方代码段就是requestImage()方法中的内容了,该方法中使用NSSession并配置相应的缓存策略来请求网络图片。加载完图片后,将ImagesNameArray中相应的URL替换成相应的UIImage对象,然后在主线程中更新UI显示相应的图片,具体代码如下所示。
(2)moveImageView()方法的实现
该方法负责将按钮归位与赋值,该方法对应着上述原理图的第二和第三步。上面网络请求完一张图片后就会调用该方法更新UI。该方法的具体实现如下所示。下方代码中首先获取当前显示的页数,也就是当前显示的Image的索引,然后将第二个Button移动到可视区,最后调用setButtonImage()函数将每个Button上的ImageView设置成相应的Image。
(3)、定时器的实现
图片要隔段时间自动轮播,此处我们使用的是dispatch_source中的定时器类型来实现的自动轮播。下方就是我们定时器的实现,如下所示,代码比较简单,在此就不做过多的赘述了,创建完定时器我们不要忘了对定时器的source进行唤醒即可。
当用户进行手动切换时,我们要对定时器进行相应的挂起和唤醒操作。也就是说当用户开始滑动时我们要对定时器进行挂起,当用户滑动结束后要对定时器进行唤醒。当然这些都是在ScrollView相应的代理方法中进行处理的,具体如下所示:
(4)、滑动结束后更新按钮的位置和图片
无论是手动滑动,还是使用定时器滑动,滑动结束后我们都需要更新一下按钮的位置和按钮上要显示的图片。所以我们还需要使用到UIScrollView上的一个代理方法,那就是scrollViewDidScroll(),在该代理方法中我们调用了moveImage()方法来更新Button的位置和Button上ImageView要显示的图片信息。没有这个方法,图片就动不起来了,具体代码如下所示:
上述是该Demo的核心代码,更为详细的代码请移步于github: https://github.com/lizelu/CEImagesScrollDisplay, 因本篇博客篇幅有限,更具体的细节在此就不做过多赘述了,Demo已在上述github上进行分享。
请发表评论