图片无限循环解读
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给重置了,你觉得我们能够看到刚替换的图片么?
- 所以说,无限循环就是一个赤果果的障眼法!
代码解释
我先分段代码解释,再在最后上完整的代码
整个无限循环功能其实重点只在接下来的三个方法里
//这四个方法是灵魂 //重置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]; }
我已经将无限循环的灵魂思想与代码结合在一起,在上面的代码中详细叙述了,其他的就不过多解释了,解释多了反而会造成反效果,接下来是这个无限循环的整体代码,欢迎拿来使用
完整代码
使用
//控制器中的使用代码 /* 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 //loopPageView.h #import <UIKit/UIKit.h> @interface loopPageView : UIView @property (nonatomic ,strong)NSArray *imageNames; /** * 是否垂直显示 */ @property (nonatomic,assign) BOOL isPortrait; @end loopPageView.m //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