图片无限循环

ps:文中imageView与image是划分得非常清楚的,image有时也用图片表示

  • 图片无限循环的文章网上已经有一大堆,但是我还是希望自己能写一个非常详细的解读文章出来
  • 我会先一步一步文字加图片描述实现的原理,最后再上代码
  • 在无限循环中我只使用3张ImageView,在不断的滑动过程中,我不会去移动ImageView的位置,我要做的只有两件事:
  • 每次滑动后,重新计算好imageView新的image,然后设置给imageView
  • 紧接着重新设置scrollView的contentoffsize

  • 接下来我会图解上面所说的两个步骤:

  • 先假设我有5张广告图片,由于宽度问题,我只显示三张,图片中的数字代表第几张图片,看清楚,是图片不是imageView!
    初始图片显示状态
  • 一开始,把contentoffset设置到中间的那张imageView上,也就是显示中间的imageView,并且以后每次滚动完都会进行这样的设置,这里先记住
  • 图中显示的是第0张图片,所以向左滑动显示的肯定是最后一张图片,也就是第4张,同理,右滑是第1张
  • 所以,三张imageView分别放置第4,第0,第1张图片,是非常正常的
  • 接下来,我们向右滑动:
    滑动后应该显示的状态
  • 大家注意看了,因为我们只设置了三张图片,那么经过我们向右滑动,屏幕应该显示的应该是第1张,而左边的imageView放置的应该是第0张图片,右边的imageView放置的则是第2张图片,这是理所当然的
  • 这时,大家肯定会有疑问,我们刚刚不是右滑了么,屏幕应该停留在第三张imageView上,并且右边已经没有imageView了,scrollView已经滑动到头了,而左边两张imageView依然放置的是第4张和第0张图片啊,你现在是神马情况?
  • 大家的疑问是非常正确的,那么我想请大家想一下,如果我在向右滑动这个用户操作结束后,我马上替换这三张imageView的image,让三张imageView分别显示第0,第1,第2张图片,并且,马上设置scrollView的contentoffset于中间的那张imageView上,看清楚哈,是马上!
  • 经过上面那一小段的操作,现在我们的scrollView不就是这样一个状态了么:
  • 这时候,有个关键点我必须详细说明一下,在上述步骤操作中,我的确是先把每个imageView应该显示的图片设置完毕后,再来设置contentoffset回归于中间
  • 也就是说,按照正常逻辑,屏幕曾有那么一段时间显示的应该是第3个imageView上的第2张图片,然后再经过强制设置contentoffset,再次显示第2张imageView的图片
  • 我曾经也有过这样的疑问,但是大家想想看,代码运行一行的需要的时间是多少?当前一行代码刚把界面的图片给换了,后一行代码就马上把contentoffset给重置了,你觉得我们能够看到刚替换的图片么?
  • 所以说,无限循环就是一个赤果果的障眼法!

    代码解释

  • 我先分段代码解释,再在最后上完整的代码
  • 整个无限循环功能其实重点只在接下来的三个方法里
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
//这四个方法是灵魂
//重置imageView的图片与重置contentoffset于中间的方法
- (void)updateImage
{
//1.遍历scrollview的三个imageView
for (NSUInteger i = 0; i < self.scrollView.subviews.count; i++) {
//1.1按顺序拿到imageView
UIImageView *imagV = self.scrollView.subviews[i];
//1.2拿到当前显示的页数
NSInteger index = self.pageControl.currentPage;
//1.3如果是第一张imageView,也就是最左边的那张imageView
if (i == 0) {
index--;//1.4应当显示的图片应该是当前页数(显示的图片前一张)减一
}else if(i == 2)//1.5如果是第三张imageView,也就是最右边的imageView
{
index++;//1.6应当显示的图片应该是当前页数(显示的图片后一张)加一
}
//2.1两种特殊情况
//2.2当中间的imageView显示的是第0张图片,那么currentpage就是0,左边的imageView按理来说应该显示的是最后一张图片,这案例有五张图片,所以也就是第4张,由于之前index--为-1,所以进入判断
if (index < 0) {
index = self.pageControl.numberOfPages - 1;//2.3把页数重置位最后一张
}else if(index >= self.pageControl.numberOfPages )//2.4这是另一种特殊情况,当滑动到最右边,也就是中间imageView显示的是第五张图片时,页码为5,currentpage为4,由于前面index++为5,所以进入该if判定,将右边的imageView的currentpage设为0
{
index = 0;//2.5将右边的imageView的currentpage设为0
}
imagV.tag = index;//将每一张imageView的currentpage作为其tag值,用于scrollViewDidScroll方法计算currentpage用
imagV.image = [UIImage imageNamed:self.imageNames[index]];//重置三张imageView应当显示的图片
}
//前面重新设置完imageView的image后,重置contentoffset,使scrollview再次显示中间的imageView,设置不带动画,所以用户无法感觉到界面有所重置
if (_isPortrait) {
self.scrollView.contentOffset = CGPointMake(0, self.scrollView.frame.size.height);
}else
{
self.scrollView.contentOffset = CGPointMake(self.scrollView.frame.size.width, 0);
}
}
//定时器要走的方法,跳转到下一张imageView
- (void)next
{
//通过设置contentoffset来跳转下一张,是带动画的
if (_isPortrait) {
[self.scrollView setContentOffset:CGPointMake(0, 2 * self.scrollView.frame.size.height) animated:YES];
}else
{
[self.scrollView setContentOffset:CGPointMake(2 * self.scrollView.frame.size.width, 0) animated:YES];
}
}
//在这个scrollView一动就会调用的方法里计算currentPage
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//计算的思路就是取出三张imageView,判断三个imageView的origin.x也就是imageView左上角的x与当前contentoffset.X
差值最小的,那么,这个imageView的tag值就是当前的currentPage,这个思路很简单,就不过多阐述
NSInteger page = 0;
CGFloat minDistance = MAXFLOAT;
for (int i = 0; i<self.scrollView.subviews.count; i++) {
UIImageView *imageView = self.scrollView.subviews[i];
CGFloat distance = 0;
if (_isPortrait) {
distance = ABS(imageView.frame.origin.y - scrollView.contentOffset.y);
}else
{
distance = ABS(imageView.frame.origin.x - scrollView.contentOffset.x);//计算差值,并且取绝对值
}
if (distance < minDistance) {
minDistance = distance;
page = imageView.tag;
}
}
self.pageControl.currentPage = page;//设置当前页数
}
//每次定时跳转动画或者拖拽滚动结束后,调用重置方法,把图片与位置都重置,进入新的循环
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView//timer动画结束调用
{
[self updateImage];
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView//拖拽滚动停止调用
{
[self updateImage];
}
  • 我已经将无限循环的灵魂思想与代码结合在一起,在上面的代码中详细叙述了,其他的就不过多解释了,解释多了反而会造成反效果,接下来是这个无限循环的整体代码,欢迎拿来使用

完整代码

使用

1
2
3
4
5
6
7
8
//控制器中的使用代码
/*
loopPageView *loopV = [[loopPageView alloc]init];
loopV.imageNames = @[@"img_00",@"img_01",@"img_02",@"img_03",@"img_04"];
loopV.frame = CGRectMake(0, 0, 300, 130);
// loopV.isPortrait = YES;//打开就会竖着显示
[self.view addSubview:loopV];
*/

loopPageView.h

1
2
3
4
5
6
7
8
9
10
//loopPageView.h
#import <UIKit/UIKit.h>
@interface loopPageView : UIView
@property (nonatomic ,strong)NSArray *imageNames;
/**
* 是否垂直显示
*/
@property (nonatomic,assign) BOOL isPortrait;
@end

loopPageView.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
//loopPageView.m
#import "loopPageView.h"
#define imageViewCount 3
@interface loopPageView ()<UIScrollViewDelegate>
@property (nonatomic,weak) UIScrollView *scrollView;
@property (nonatomic,weak) UIPageControl *pageControl;
@property (nonatomic,weak) NSTimer *timer;
@end
@implementation loopPageView
#pragma mark -
#pragma mark Life Cycle
-(instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
//scrollView
UIScrollView *scrollV = [[UIScrollView alloc]init];
//这里一定得设置啊,不然用来计算的scrollview的subviews这个数值不对了
scrollV.showsHorizontalScrollIndicator = NO;
scrollV.showsVerticalScrollIndicator = NO;
scrollV.delegate = self;
scrollV.pagingEnabled = YES;
self.scrollView = scrollV;
[self addSubview:scrollV];
//添加三张imageView
for (NSUInteger i = 0; i < imageViewCount; i++) {
UIImageView *imageV = [[UIImageView alloc]init];
[self.scrollView addSubview:imageV];
}
//pagecontrol
UIPageControl *pageC = [[UIPageControl alloc]init];
self.pageControl = pageC;
[self addSubview:pageC];
}
return self;
}
-(void)layoutSubviews
{
[super layoutSubviews];
CGFloat w = self.frame.size.width;
CGFloat h = self.frame.size.height;
self.scrollView.frame = CGRectMake(0, 0, w, h);
self.pageControl.frame = CGRectMake(0, h - 37, w, 37);
if (_isPortrait) {
[self.scrollView.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
UIImageView *img = (UIImageView *)obj;
img.frame = CGRectMake(0, idx * h, w, h);
}];
self.scrollView.contentSize = CGSizeMake(0, imageViewCount * self.scrollView.frame.size.height);
}else
{
[self.scrollView.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
UIImageView *img = (UIImageView *)obj;
img.frame = CGRectMake(idx * w, 0, w, h);
}];
self.scrollView.contentSize = CGSizeMake(imageViewCount * self.frame.size.width, 0);
}
self.pageControl.numberOfPages = self.imageNames.count;
self.pageControl.currentPage = 0;
[self updateImage];
// [self performSelectorInBackground:@selector(startTimer) withObject:nil];
}
//在layoutSubviews中做操作时,要小心,它会走至少两次,之前无形中走了两次startTimer方法,然后创建了两个timer,虽然后面我打self.timer是null,但是还有一个timer,虽然表面上没有强制指针引用它,实际上它在创建出来的时候就有强指针引用它了,然后我没法获取另一个timer并且invalidate掉,尼玛的内存泄露啊,而且他奶奶的还一直操控我的程序,好坑爹!!!
#pragma mark -
#pragma mark <UIScrollViewDelegate>
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
[self stopTimer];
}
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
[self startTimer];
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self updateImage];
}
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
[self updateImage];
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSInteger page = 0;
CGFloat minDistance = MAXFLOAT;
for (int i = 0; i<self.scrollView.subviews.count; i++) {
UIImageView *imageView = self.scrollView.subviews[i];
CGFloat distance = 0;
if (_isPortrait) {
distance = ABS(imageView.frame.origin.y - scrollView.contentOffset.y);
}else
{
distance = ABS(imageView.frame.origin.x - scrollView.contentOffset.x);
}
if (distance < minDistance) {
minDistance = distance;
page = imageView.tag;
}
}
self.pageControl.currentPage = page;
}
#pragma mark -
#pragma mark private methods
- (void)updateImage
{
for (NSUInteger i = 0; i < self.scrollView.subviews.count; i++) {
UIImageView *imagV = self.scrollView.subviews[i];
NSInteger index = self.pageControl.currentPage;
if (i == 0) {
index--;
}else if(i == 2)
{
index++;
}
if (index < 0) {
index = self.pageControl.numberOfPages - 1;
}else if(index >= self.pageControl.numberOfPages )
{
index = 0;
}
imagV.tag = index;
imagV.image = [UIImage imageNamed:self.imageNames[index]];
}
if (_isPortrait) {
self.scrollView.contentOffset = CGPointMake(0, self.scrollView.frame.size.height);
}else
{
self.scrollView.contentOffset = CGPointMake(self.scrollView.frame.size.width, 0);
}
}
- (void)next
{
if (_isPortrait) {
[self.scrollView setContentOffset:CGPointMake(0, 2 * self.scrollView.frame.size.height) animated:YES];
}else
{
[self.scrollView setContentOffset:CGPointMake(2 * self.scrollView.frame.size.width, 0) animated:YES];
}
}
- (void)startTimer
{
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0f target:self selector:@selector(next) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop]addTimer:self.timer forMode:NSRunLoopCommonModes];
}
-(void)stopTimer
{
[self.timer invalidate];
self.timer = nil;
}
#pragma mark -
#pragma mark setter and getter
-(void)setImageNames:(NSArray *)imageNames
{
_imageNames = imageNames;
[self startTimer];
}
@end