iOS图片无限循环解读

版权所有,禁止匿名转载;禁止商业使用。

图片无限循环解读


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


图片无限循环的文章网上已经有一大堆,但是我还是希望自己能写一个非常详细的解读文章出来

我会先一步一步文字加图片描述实现的原理,最后再上代码

在无限循环中我只使用3张ImageView,在不断的滑动过程中,我不会去移动ImageView的位置,我要做的只有两件事:


每次滑动后,重新计算好imageView新的image,然后设置给imageView

紧接着重新设置scrollView的contentoffsize

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


先假设我有5张广告图片,由于宽度问题,我只显示三张,图片中的数字代表第几张图片,看清楚,是 图片 不是imageView!

iOS开发,无限循环

初始图片显示状态


- 一开始,把contentoffset设置到中间的那张imageView上,也就是显示中间的imageView,并且以后每次滚动完都会进行这样的设置,这里先记住

- 图中显示的是第0张图片,所以向左滑动显示的肯定是最后一张图片,也就是第4张,同理,右滑是第1张

- 所以,三张imageView分别放置第4,第0,第1张图片,是非常正常的

- 接下来,我们向右滑动:

iOS开发,无限循环

滑动后应该显示的状态


- 大家注意看了,因为我们只设置了三张图片,那么经过我们向右滑动,屏幕应该显示的应该是第1张,而左边的imageView放置的应该是第0张图片,右边的imageView放置的则是第2张图片,这是理所当然的

- 这时,大家肯定会有疑问,我们刚刚不是右滑了么,屏幕应该停留在第三张imageView上,并且右边已经没有imageView了,scrollView已经滑动到头了,而左边两张imageView依然放置的是第4张和第0张图片啊,你现在是神马情况?

- 大家的疑问是非常正确的,那么我想请大家想一下,如果我在向右滑动这个用户操作结束后,我马上替换这三张imageView的image,让三张imageView分别显示第0,第1,第2张图片,并且,马上设置scrollView的contentoffset于中间的那张imageView上,看清楚哈,是马上!

- 经过上面那一小段的操作,现在我们的scrollView不就是这样一个状态了么:

iOS开发,无限循环

右滑一次之后的样子


- 这时候,有个关键点我必须详细说明一下,在上述步骤操作中,我的确是先把每个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


0 0