iOS开发之——图片无限循环

引子

  iOS应用中,头部循环视图非常常见,所以完全有必要做成公用的轮子,当然,这轮子网上有很多现成的,不过考虑到还是自己动手造一下,了解背后的实现原理比较好(其实真正的原因是女朋友追问我,博客怎么好几天都没更新……)。话不多说,进入正题。

概述

  实现图片无限循环大致有两种方法:

  1. 序列加2法(自己起的)。
    原理:图片序列的前后各再加一个,假如要显示[1,2,3,4,5]五张图片,就建立[5,1,2,3,4,5,1]这样一种序列。当向右滑动(手动、自动均可),滑动到第二个1时,马上无动画替换到第一个1,接着循环,造成无限的假象。同理,当向左滑动时,滑动第一个5时,马上无动画替换到第二个5,接着循环,造成无限的假象。
    说明:这种理解起来不复杂,也便于实现,不过当循环的图片过多时,会有性能问题,所以有了第二种方法;
  2. 三格法(也是自己起的)。
    原理:创建3个图片视图,leftImageView、middleImageView、rightImageView,然后始终让屏幕显示middleImageView即可。两种特殊情况:当显示第1张图片时,leftImageView应存储最后一张图片;当显示最后一张图片时,rightImageView应存储第一张图片。
    说明:就3个视图,所以即使图片过多,性能不再是问题。
  3. 两格法(还是自己起的)。终极方法,比着上一个方法更加追求性能,创建两个图片视图。不过暂时还未实现。

详细介绍

1. 序列加2法

创建视图、初始化之类的略过不说,我后边会给Demo。

1.1 创建图片名称数组

通过一个可变数组保存图片名称,将最后一张图片的名字插入到第一个位置,第一张图片的名字添加到最后,完成第一步。

1.2 滑动判断(scrollView代理方法)

代码:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGPoint offset = scrollView.contentOffset;
CGFloat width = self.scrollViewFrame.size.width;
//[5,1,2,3,4,5,1]
//向左滑动中,如果显示最后一张图片(第一个5)之后,依然向左滑,就无动画切换到第二张最后的图片(第二个5)
if (offset.x <= 0)
{
scrollView.contentOffset = CGPointMake(self.imageNum * width, 0);
}
//[5,1,2,3,4,5,1]
//向右滑动中,如果显示第一张图片(第二个1)之后,依然向右滑,就无动画切换到(第一个1)
else if (offset.x >= (self.imageNum+1)*width-1)
{
scrollView.contentOffset = CGPointMake(width, 0);
}
//根据偏移量计算出当前页码数
self.imageIndex = self.rootScrollView.contentOffset.x / width + 0.5;
self.imagePage.currentPage = self.imageIndex - 1;
}

说明:这里边判断向左滑动的条件是if (offset.x <= 0),网上有的是if (offset.x == 0),后者不太好的一点是:假如你滑动速度过快,if后边的代码将不执行,就会出现图片没有被替换,滑到尽头的现象。同理,向右滑动的条件
if (offset.x >= (self.imageNum+1)*width-1),也是为了避免这种情况。其实,上述代码放入滑动停止的代理方法
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
中也能实现无限循环,不过当视图一直滑动(例如两只手交替滑动,不让视图停止),也会出现图片没有替换的现象。我写的这段代码无论滑动速度快慢,还是两只手交替搓都不会出现没有替换的现象,大家可以下载代码之后尝试一下。

1.3 添加定时器,完成自动切换

//定时器事件,只考虑向右滑动一种情况即可
- (void)nextImage
{
//[5,1,2,3,4,5,1]
//如果显示的是最后一张图片(第二个5),则无动画切换到第一个5
if (self.imageIndex == self.imageNum)
{
self.imageIndex = 0;
self.rootScrollView.contentOffset = CGPointMake(self.imageIndex * self.scrollViewFrame.size.width, 0);
}
//加1循环
self.imageIndex++;

//动画切换
[self.rootScrollView setContentOffset:CGPointMake(self.imageIndex * self.scrollViewFrame.size.width, 0) animated:YES];
}

1.4 Demo下载:github地址

2. 三格法

2.1 滑动判断(scrollView代理方法)

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
CGFloat offsetX = scrollView.contentOffset.x;

//始终显示中间,所以超过一个屏幕则为向右滑动
if(offsetX > self.screenWidth)
{
self.currentPage++;
//滑动到最后一张之后切换到第一张
if (self.currentPage >= self.imageNum)
{
self.currentPage = 0;
}
}
//始终显示中间,所以不超过一个屏幕则为向左滑动
else if(offsetX < self.screenWidth)
{
self.currentPage--;
//向左滑动到第一张之后进入最后一张
if (self.currentPage < 0)
{
self.currentPage = self.imageNum - 1;
}
}
//更新图片
[self updateImage];

//无动画切换到middleImageView
self.rootScrollView.contentOffset = CGPointMake(self.screenWidth, 0);
}

2.2 更新图片

- (void)updateImage
{
UIImageView *leftImageView = self.rootScrollView.subviews[0];
UIImageView *middleImageView = self.rootScrollView.subviews[1];
UIImageView *rightImageView = self.rootScrollView.subviews[2];

NSInteger leftIndex, rightIndex;

NSInteger currentPage = self.currentPage;

//如果当前显示第一张图片,设置leftImageView显示最后一张
if (currentPage == 0)
{
leftIndex = self.imageNum-1;
rightIndex = currentPage + 1;
}
//如果当前显示最后一张图片,设置rightImageView显示第一张
else if (currentPage == (self.imageNum-1))
{
leftIndex = currentPage - 1;
rightIndex = 0;
}
//正常情况下的左、右显示设置
else
{
rightIndex = currentPage + 1;
leftIndex = currentPage - 1;
}

leftImageView.image = [UIImage imageNamed:self.imageNames[leftIndex]];
middleImageView.image = [UIImage imageNamed:self.imageNames[currentPage]];
rightImageView.image = [UIImage imageNamed:self.imageNames[rightIndex]];

//设置页码
self.imagePage.currentPage = currentPage;
}

2.3 添加自动切换

- (void)autoNextPage
{
//首先应设置到显示leftImageView,否则下边的动画效果出不来,执行很快速,所以并不会真的显示出来
self.rootScrollView.contentOffset = CGPointMake(0, 0);
self.currentPage++;

//到最后一张之后切换到第一张
if (self.currentPage >= self.imageNum)
{
self.currentPage = 0;
}
[self updateImage];

//动画切换到middleImageView
[self.rootScrollView setContentOffset:CGPointMake(self.screenWidth, 0) animated:YES];
}

说明:一开始手动和自动的切换imageView都是在更新图片里边,但是两者会冲突,所以后来分别放到各自的事件中进行处理。另外,因为是在停止滚动的代理方法中处理滑动判断,所以这个方法也会出现当滑动速度过快时,视图没有切换的小bug。

2.4 Demo下载:github地址