实现UIScrollView的无限轮播,市面上很少见的UISc

作者:新闻中心

同学们在写需求的时候肯定会时常用到UIScrollView。而说到UIScrollView,大家最先想到的肯定就是它上面的无限轮播功能。苹果在UIScrollView上并没有提供相应的方法让大家实现轮播,所以就需要通过代码进行处理来实现。先上图

轮播图是App中常用的展示界面,主要用到的控件有UIScrollView和UIPageControl。难点有两处,一个是定时器自动轮播,一个是手动操作后不影响定器自动轮播

当初就像在网上找用scrollView写的轮播图可是 到处都是collectionView的 没有scrollView的 今天就写了个scrollView的手动轮播图和大家分享

本文是投稿文章,作者:codingZero

图片 1无限轮播效果图.gif

一、要达到的效果

图片 2

效果动图.gif

要写这样一个轮播图
首先要有scrollView(创建scrollView)然后为了 达到更好的效果 还需要对scrollView做一系列的属性设置
然后还要有一堆图片(用一个数组来存放这些图片)
最后再将图片放到scrollView上 实现轮播(其实看似简单 但是其中有一些小细节 需要特别的注意 假设有5张图片 当一次按顺序滑到第5张时 再滑动就应该出现第1张 如果只是单纯地改变scrollView的偏移量 这样滑到第1张得效果很不好 很突兀 不是理想效果 我们就要想办法解决这个问题)
我设计的这款轮播图 还带有图片缩放功能
下面我们通过代码来实现我们想要的效果

导语

我先给大家讲讲其实现的原理:我们假设用几张图片实现轮播效果。首先,我们需要打开UIScrollView的分页滑动

二、逻辑分析

首先,轮播图需要轮播几张图片,就要往scrollView上放几张对应的图片,这里定为3张。图片大小宽度和ScrollView的frame一样,便于翻页。

然后,当图片从头轮播时,到第三张,此时图片应该跳到第一张才能实现循环,但是要保证流畅的话,就不能直接设置scrollView的contentOffset为(0,0)。解决办法是,在scrollView的最后再加一张ImageView,上面放第一张图片,这样的话当轮播到第四张ImageView(视觉效果是第一张图片)时,再设置scrollView的contentOffset为(0,0),这样视觉效果就流畅了

第三,因为需要往两个方向轮播,所以也要在scrollView的开头加一个ImageView,上面放第三张图片,并设置当轮播到这里时设置scrollView的contentOffset为倒数第二张imageView的位置。

// ViewController.m

#import "ViewController.h"
#define WIDTH self.view.frame.size.width
#define HEIGHT self.view.frame.size.height

@interface ViewController () <UIScrollViewDelegate>
@property (nonatomic, strong) UIScrollView *photoScrollView;
@property (nonatomic, strong) UIScrollView *smallScrollView;

@property (nonatomic, strong) UIPageControl *page;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.photoScrollView = [[UIScrollView alloc] initWithFrame:self.view.frame];
self.photoScrollView.contentSize = CGSizeMake(WIDTH * 9, 0);
self.photoScrollView.delegate = self;
self.photoScrollView.pagingEnabled = YES;
self.photoScrollView.bounces = NO;
[self.view addSubview:self.photoScrollView];

NSArray *photoArr = @[@"u=3808451166,1723806820&fm=23&gp=0", @"u=1019182931,1402276621&fm=23&gp=0",@"u=1712427375,1304261470&fm=23&gp=0", @"u=2019074505,523552158&fm=23&gp=0", @"u=2216889308,3751754546&fm=23&gp=0", @"u=2301975825,1085041863&fm=23&gp=0", @"u=2427941702,1013456056&fm=23&gp=0", @"u=3808451166,1723806820&fm=23&gp=0", @"u=1019182931,1402276621&fm=23&gp=0"];
for (NSInteger i = 0; i < photoArr.count; i  ) {
    self.smallScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(i * WIDTH, 0, WIDTH, HEIGHT)];
    self.smallScrollView.delegate = self;
    self.smallScrollView.minimumZoomScale = 0.5;
    self.smallScrollView.maximumZoomScale = 2;
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, WIDTH, HEIGHT)];
    UIImage *image = [UIImage imageNamed:photoArr[i]];
    [imageView setImage:image];
    [self.smallScrollView addSubview:imageView];
    [self.photoScrollView addSubview:self.smallScrollView];
       }
self.photoScrollView.contentOffset = CGPointMake(WIDTH, 0);

 self.page = [[UIPageControl alloc] initWithFrame:CGRectMake(50, HEIGHT - 20, 100, 20)];
self.page.numberOfPages = 7;
self.page.currentPage = 0;
[self.page addTarget:self action:@selector(pageChange:) forControlEvents:UIControlEventValueChanged];
[self.view addSubview:self.page];

}
// 点击小点 图片跟着滑动
- (void)pageChange:(UIPageControl *)page {
[self.photoScrollView     setContentOffset:CGPointMake((page.currentPage   1) * WIDTH, 0) animated:YES];
}


- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return [scrollView.subviews firstObject];
}

- (void)scrollViewDidEndDecelerating:(UIScrollView   *)scrollView {
 for (UIScrollView *scroll in scrollView.subviews) {
    if ([scroll isKindOfClass:[UIScrollView class]]) {
        scroll.zoomScale = 1;
    }
}
self.page.currentPage = self.photoScrollView.contentOffset.x / WIDTH - 1;

if (self.photoScrollView.contentOffset.x == 8 * WIDTH) {
    self.photoScrollView.contentOffset = CGPointMake(WIDTH, 0);
    self.page.currentPage = 0;
}
if (self.photoScrollView.contentOffset.x == 0) {
    self.photoScrollView.contentOffset =CGPointMake(7 * WIDTH, 0);
    self.page.currentPage = 6;
}
NSLog(@"%f", self.photoScrollView.contentOffset.x);
}

在不少项目中,都会有图片轮播这个功能,现在网上关于图片轮播的框架层出不穷,千奇百怪,笔者根据自己的思路,用两个imageView也实现了图片轮播,这里说说笔者的主要思路以及大概步骤,具体代码请看这里,如果觉得好用,请献上你的star。

/// 分页滑动_scrollView.scrollEnabled = YES;
综上:要实现三张图片的轮播,ScrollView的contentSize的宽度为5张图片宽度,第一张ImageView放第三张图片,最后一张ImageView放第一张图片,其他的按顺序放图片。以上能实现的是手指滑动的轮播,还有自动轮播和手指滑动后自动轮播的稍复杂功能,但是核心原理还是上面的原理。直接上代码,更合味

让我们来分析一下 我们变成的整体过程:

该轮播框架的优势:

它方便的帮助我们实现了轮播的效果,然后就需要我们来实现“无限的”轮播。接下来,我们就需要摆放图片了,在摆放图片时需要注意,我们需要在第一张图片的位置摆放最后一张图片(可能有点懵哈,不过不要着急慢慢往下看),然后我们依次摆放图片(从第一张到最后一张),最后在所有图片的尾部我们再放上第一张图片。这样我们就多放了两张图片(分别在首尾多放了一张图)。我把对应的方法写一下:

三、代码分析

添加UIScrollView、NStimer和UIPageControl控件

@property (nonatomic)UIScrollView *scrollView;
@property(nonatomic,strong)UIPageControl * pageController;//页面控制器
@property(nonatomic,strong)NSTimer * timer;//计时器

定义几个宏

#define imageCount 3//图片的张数
//当前设备的屏幕宽度
#define kScreenWidth [UIScreen mainScreen].bounds.size.width
//当前设备的屏幕高度
#define kScreenHeight [UIScreen mainScreen].bounds.size.height

初始化控件,并将图片添加上去

//初始化定时器
self.timer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
//添加scrollView
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, 250)];
self.scrollView.contentSize = CGSizeMake((imageCount   2)*kScreenWidth, 0);
self.scrollView.pagingEnabled = YES;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.bounces = NO;
self.scrollView.scrollEnabled = YES;
self.scrollView.contentOffset = CGPointMake(1*kScreenWidth, 0);
self.scrollView.delegate = self;
[self.view addSubview:self.scrollView];
//添加图片
for (int i = 0; i < imageCount 2; i  ) {
    if (i == 0) {
        UIImageView * imageV = [[UIImageView alloc] initWithFrame:CGRectMake(i*kScreenWidth, 0, kScreenWidth, 250)];
        [self.scrollView addSubview:imageV];
        imageV.image = [UIImage imageNamed:@"13.jpg"];
    } else if(i == imageCount   1){
        UIImageView * imageV = [[UIImageView alloc] initWithFrame:CGRectMake(i*kScreenWidth, 0, kScreenWidth, 250)];
        [self.scrollView addSubview:imageV];
        imageV.image = [UIImage imageNamed:@"11.jpg"];
    }else{
        UIImageView * imageV = [[UIImageView alloc] initWithFrame:CGRectMake(i*kScreenWidth, 0, kScreenWidth, 250)];
        [self.scrollView addSubview:imageV];
        imageV.image = [UIImage imageNamed:[NSString stringWithFormat:@"1%d.jpg",i]];
    }
}
//在scrollView上添加page
self.pageController = [[UIPageControl alloc] init];
[self.view addSubview:self.pageController];
self.pageController.frame = CGRectMake(kScreenWidth/2 - 50, 250 - 20, 100, 25);
self.pageController.numberOfPages = imageCount;
self.pageController.pageIndicatorTintColor = [UIColor whiteColor];
self.pageController.currentPageIndicatorTintColor = [UIColor cyanColor];
self.pageController.currentPage = 0;
  1. 签订UIScrollView协议 设置三个属性 一个大的scrollView 一个小的scrollView 和一个pageControl

  2. 在viewDidLoad中书写代码 创建一个大的scrollView(photoScrollView) 并对他进行一些列的设置 将它放在self.view上

  3. 创建数组 数组中存放图片名 (其中要注意 这里一共是要轮播7张图片 但是我们为了实现的效果 在数组中方9张图片 顺序是: 7 1 2 3 4 5 6 7 1 同时photoScrollView的滑动范围 设置为CGSizeMake(WIDTH * 9, 0))

  4. for 循环 一共是9张图片 要在循环里 创建UIImageView 然后创建smallScrollView 按照上面的顺序将将创建好 UIImageView 放在smallScrollView 上(这个scrollView是负责图片缩放的 在这里要设置缩放比例) 最后再将smallScrollView放在photoScrollView上

  5. 这一步很重要 通过以上的一系列操作 我们已经将图片都按照顺序排在photoScrollView上了 接下来 我们要设置这个photoScrollView的偏移量 要他显示的第一张图片是我们的第一张图片(self.photoScrollView.contentOffset = CGPointMake(WIDTH, 0);)

  6. 创建pageControl 这里我们轮播的图片是7张 所以self.page.numberOfPages = 7; 而且要给pageControl绑定事件

  7. 实现图片的放大功能

    • (UIView *)viewForZoomingInScrollView: (UIScrollView *)scrollView {
      return [scrollView.subviews firstObject];
      }
  8. 实现图片的轮播 (在scrollView停止减速的这个方法里来实现)

    • (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
      // 对一个图片实现缩放后 滑到另一张图片 然后再划回来 使图片变为原来大小
      for (UIScrollView *scroll in scrollView.subviews) {
      if ([scroll isKindOfClass:[UIScrollView class]]) {
      scroll.zoomScale = 1;
      }
      }
      // 设置当滑动图片时小点跟着移动
      self.page.currentPage = self.photoScrollView.contentOffset.x / WIDTH - 1;
      // 当scrollView滑到最后一张时 改变scrollView的偏移量 让scrollView显示第一张图片 这时同时设置小点的位置也是0 才能做到滑动图片小点的移动是同步的
      if (self.photoScrollView.contentOffset.x == 8 * WIDTH) {
      self.photoScrollView.contentOffset = CGPointMake(WIDTH, 0);
      self.page.currentPage = 0;
      }
      if (self.photoScrollView.contentOffset.x == 0) {
      self.photoScrollView.contentOffset =CGPointMake(7 * WIDTH, 0);
      self.page.currentPage = 6;
      }
      NSLog(@"%f", self.photoScrollView.contentOffset.x);
      }
  9. 设置点击小点图片也会跟着移动

    • (void)pageChange:(UIPageControl *)page {
      [self.photoScrollView setContentOffset:CGPointMake((page.currentPage 1) * WIDTH, 0) animated:YES];
      }
  • 文件少,代码简洁

  • 不依赖任何其他第三方库,耦合度低

  • 同时支持本地图片及网络图片

  • 可修改分页控件位置,显示或隐藏

  • 自定义分页控件的图片,就是这么个性

  • 自带图片缓存,一次加载,永久使用

  • 性能好,占用内存少,轮播流畅

/// 将图片放置在UIScrollView上-setupImage { /// 在UIScrollView的最前面添加一张图片 UIImageView *firstImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, self.scrollView.frame.size.height)]; /// 图片名是最后一张图片 firstImageView.image = [UIImage imageNamed:self.imageNameList.lastObject]; [self.scrollView addSubview:firstImageView]; /// 添加图片 for (NSInteger index = 0; index < self.imageNameList.count; index   ) { /// UIScrollView上的每一张图片 UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake((index   1) * kScreenWidth, 0, kScreenWidth, self.scrollView.frame.size.height)]; imageView.image = [UIImage imageNamed:self.imageNameList[index]]; [self.scrollView addSubview:imageView]; self.scrollView.contentSize = CGSizeMake((index   2) * self.scrollView.bounds.size.width, 0); } /// 在UIScrollView的最后面添加一张图片 UIImageView *lastImageView = [[UIImageView alloc] initWithFrame:CGRectMake((self.imageNameList.count   1) * kScreenWidth, 0, kScreenWidth, self.scrollView.frame.size.height)]; /// 图片名是第一张图片 lastImageView.image = [UIImage imageNamed:self.imageNameList.firstObject]; [self.scrollView addSubview:lastImageView]; /// 设置UIScrollView的偏移量 self.scrollView.contentSize = CGSizeMake((self.imageNameList.count   2) * self.scrollView.bounds.size.width, 0); /// 设置UIScrollView的起始偏移距离 self.scrollView.contentOffset = CGPointMake(kScreenWidth, 0); /// 图片总数 self.pageControl.numberOfPages = self.imageNameList.count; self.pageControl.currentPage = 0;}
滑动ScrollView时,要设置页码控件PageControl的改变,还要设置在最后一张和第一张ImageView时要做跳转,这些都可以在代理方法中去实现。遵循代理UIScrollViewDelegate

在只要ScrollView滑动时就会走的代理方法中设置PageControl的改变

-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
    //当scrollView滑动时,设置page
    CGFloat scroll = scrollView.contentOffset.x/kScreenWidth;
    NSInteger number = (NSInteger)scroll;//偏移了几个屏幕宽的距离
    if (number == imageCount 1||(number == imageCount&&scroll - number > kScreenWidth/2)) {//如果偏移为最大值或将要到最大值
        self.pageController.currentPage = 0;
    }else if (number == 0||(number == 0&&scroll-number < kScreenWidth/2)){//如果偏移为最小值或者将要到最小值
        self.pageController.currentPage = imageCount- 1;
    }else{
        if (scroll - number>kScreenWidth/2) {
            self.pageController.currentPage = (number-1) 1;
        }
        if (scroll - number<=kScreenWidth/2) {
            self.pageController.currentPage = (number-1);
        }
    }
}

在滑动结束减速时的代理方法中设置偏移

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
    if (self.scrollView.contentOffset.x == (imageCount   1)*kScreenWidth) {//手动滑到最后一张ImageView
        self.scrollView.contentOffset = CGPointMake(kScreenWidth, 0);
    }else if (self.scrollView.contentOffset.x == 0*kScreenWidth) {//手动滑到第一张ImageView
        self.scrollView.contentOffset = CGPointMake(imageCount*kScreenWidth, 0);
    }
}

实际使用

其实,如果大家看到这里,应该就会大致明白无线轮播的实现原理了。接下来就是最后一步,在UIScrollView的代理方法里面写逻辑:判断UIScrollView的偏移量,当其滑动到首位时(显示的是最后一张图片),滑动停止,就把偏移量修改最后面图片的位置上。同理,当UIScrollView滑动到最后时(显示的是第一张图片),滑动停止,就把偏移量修改到第一张图片的位置上。

以上实现的是可手动轮播,下面是自动轮播的实现

自动轮播的实现主要是在定时器的定时事件中实现功能。先创建一个全局变量,控制显示的图片角标,并循环使用

static NSInteger pageNumber = 0;//用于记录计时器计时循环

然后实现定时器的定时事件方法

-(void)timerAction{
    if(pageNumber == imageCount){//手动滑滑到最大偏移,即显示的是视觉上第一张图
        pageNumber = 0;
        //跳到第一张图
        self.scrollView.contentOffset = CGPointMake(kScreenWidth,0);
        //然后滑到视觉上第二张图片
        [UIView animateWithDuration:0.5 animations:^{
            self.scrollView.contentOffset = CGPointMake((pageNumber 2)*kScreenWidth,0);
        }];
    }else if(pageNumber == 0){
        //滑到视觉上第二张图
        [UIView animateWithDuration:0.5 animations:^{
            self.scrollView.contentOffset = CGPointMake((pageNumber 2)*kScreenWidth,0);
        }];
    }else {
        [UIView animateWithDuration:0.5 animations:^{
            self.scrollView.contentOffset = CGPointMake((pageNumber 2)*kScreenWidth,0);
        }];
    }
    pageNumber  ;
}

对于上面的方法中pageNumber的理解:pageNumber=0时,当前显示第一张图,但是将要显示第二张图;等于1时当前显示第二张图,将要显示第三张图,以此类推。当pageNumber=3,这时显示的是最后一张ImageView,也就是第一张图,所以将pageNumber设置为0,因为是要循环显示,所以将Scrollview的偏移设置为(kScreenWidth,0),即第一张图,此时将要显示的是第二张图,如此便完成了循环自动轮播

我们先看demo,代码如下

#pragma mark - UIScrollViewDelegate-scrollViewDidEndDecelerating:(UIScrollView *)scrollView { /// 当UIScrollView滑动到第一位停止时,将UIScrollView的偏移位置改变 if (scrollView.contentOffset.x == 0) { scrollView.contentOffset = CGPointMake(self.imageNameList.count * kScreenWidth, 0); self.pageControl.currentPage = self.imageNameList.count; /// 当UIScrollView滑动到最后一位停止时,将UIScrollView的偏移位置改变 } else if (scrollView.contentOffset.x == (self.imageNameList.count   1)* kScreenWidth) { scrollView.contentOffset = CGPointMake(kScreenWidth, 0); self.pageControl.currentPage = 0; } else { self.pageControl.currentPage = scrollView.contentOffset.x / kScreenWidth - 1; }}
手动轮播和自动轮播都已实现,现在要实现的是自动 手动轮播。

图片 3

ok,原理其实就是这样。在首尾多加两张图片当做占位符,然后当UIScrollView滑动到占位符的位置时,改变UIScrollView的偏移量,简单且方便。下面就是全部代码:

思路:当ScrollView正要自动轮播到下一张图时,我刚好进行了手动操作,这样的话ScrollView到底是按自动播放到下一张还是按手动滑到下一张?感觉有冲突。所以这里的处理方式是,只要进行了手动操作,就将自动播放停止,手动操作完毕,在开启自动播放,这样就不冲突了

先写两个方法,开启定时器的方法和结束定时器的方法

-(void)beginAction{//开启定时器
    //如果计时器已开启  先停止
    if (self.timer) [self stopAction];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
}
-(void)stopAction{//结束定时器
    [self.timer invalidate];
    self.timer = nil;
}

首先,在ScrollView开始被拖拽时停止定时器,实现ScrollView开始拖拽的代理方法

-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
    //结束计时
    [self stopAction];
}

在拖拽完毕的时候开启定时器,可以在ScrollView的减速结束的代理方法添加开启定时器的代码(这个代理方法上面写过了,直接添加一行代码即可)。

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
    if (self.scrollView.contentOffset.x == (imageCount   1)*kScreenWidth) {//手动滑到最后一张ImageView
        self.scrollView.contentOffset = CGPointMake(kScreenWidth, 0);
    }else if (self.scrollView.contentOffset.x == 0*kScreenWidth) {//手动滑到第一张ImageView
        self.scrollView.contentOffset = CGPointMake(imageCount*kScreenWidth, 0);
    }
    //启动定时器
    [self beginAction];
}

手指滑动结束后,开始自动播放,此时是定时器的定时事件方法(timerAction)起作用,该播放哪一张图由pageNumber等于几决定,由于定时器停止时pageNumber的值,无法正确指示显示哪一张图了,所以再手动滑动时,应该同步改变pageNumber的值。在ScrollView的减速结束代理方法中加入代码

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
    if (self.scrollView.contentOffset.x == (imageCount   1)*kScreenWidth) {//手动滑到最后一张ImageView
        self.scrollView.contentOffset = CGPointMake(kScreenWidth, 0);
    }else if (self.scrollView.contentOffset.x == 0*kScreenWidth) {//手动滑到第一张ImageView
        self.scrollView.contentOffset = CGPointMake(imageCount*kScreenWidth, 0);
    }
    //启动定时器
    [self beginAction];
    //拖拽结束后给记录轮播到第几张的变量赋值
    NSInteger number = (NSInteger)self.scrollView.contentOffset.x/kScreenWidth;
    if (number == imageCount 1){//在最后一张(向右才会到这里)
        pageNumber = 0;
    }else if(number == 0){//在scrollView的第一张(向左才会到这里)
        pageNumber = imageCount -1;
    }else{
        pageNumber = number - 1;
    }
}

至此,大功告成

运行效果

#import "ViewController.h"#define kScreenWidth [UIScreen mainScreen].bounds.size.width@interface ViewController () <UIScrollViewDelegate>/// 滑动控制器@property (nonatomic, strong) UIScrollView *scrollView;/// 图片数组@property (nonatomic, strong) NSArray<NSString *> *imageNameList;/// 页码控制器@property (nonatomic, strong) UIPageControl *pageControl;@end@implementation ViewController- viewDidLoad { [super viewDidLoad]; // 设置图片名的数组 self.imageNameList = @[@"image0", @"image1", @"image2", @"image3"]; // 添加图片 [self setupImage];}/// 将图片放置在UIScrollView上-setupImage { /// 在UIScrollView的最前面添加一张图片 UIImageView *firstImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, self.scrollView.frame.size.height)]; /// 图片名是最后一张图片 firstImageView.image = [UIImage imageNamed:self.imageNameList.lastObject]; [self.scrollView addSubview:firstImageView]; /// 添加图片 for (NSInteger index = 0; index < self.imageNameList.count; index   ) { /// UIScrollView上的每一张图片 UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake((index   1) * kScreenWidth, 0, kScreenWidth, self.scrollView.frame.size.height)]; imageView.image = [UIImage imageNamed:self.imageNameList[index]]; [self.scrollView addSubview:imageView]; self.scrollView.contentSize = CGSizeMake((index   2) * self.scrollView.bounds.size.width, 0); } /// 在UIScrollView的最后面添加一张图片 UIImageView *lastImageView = [[UIImageView alloc] initWithFrame:CGRectMake((self.imageNameList.count   1) * kScreenWidth, 0, kScreenWidth, self.scrollView.frame.size.height)]; /// 图片名是第一张图片 lastImageView.image = [UIImage imageNamed:self.imageNameList.firstObject]; [self.scrollView addSubview:lastImageView]; /// 设置UIScrollView的偏移量 self.scrollView.contentSize = CGSizeMake((self.imageNameList.count   2) * self.scrollView.bounds.size.width, 0); /// 设置UIScrollView的起始偏移距离 self.scrollView.contentOffset = CGPointMake(kScreenWidth, 0); /// 图片总数 self.pageControl.numberOfPages = self.imageNameList.count; self.pageControl.currentPage = 0;}#pragma mark - UIScrollViewDelegate-scrollViewDidEndDecelerating:(UIScrollView *)scrollView { /// 当UIScrollView滑动到第一位停止时,将UIScrollView的偏移位置改变 if (scrollView.contentOffset.x == 0) { scrollView.contentOffset = CGPointMake(self.imageNameList.count * kScreenWidth, 0); self.pageControl.currentPage = self.imageNameList.count; /// 当UIScrollView滑动到最后一位停止时,将UIScrollView的偏移位置改变 } else if (scrollView.contentOffset.x == (self.imageNameList.count   1)* kScreenWidth) { scrollView.contentOffset = CGPointMake(kScreenWidth, 0); self.pageControl.currentPage = 0; } else { self.pageControl.currentPage = scrollView.contentOffset.x / kScreenWidth - 1; }}#pragma mark - Get方法-(UIScrollView *)scrollView { if (!_scrollView) { _scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, 200)]; _scrollView.pagingEnabled = YES; _scrollView.clipsToBounds = NO; _scrollView.scrollEnabled = YES; _scrollView.delegate = self; _scrollView.bounces = NO; _scrollView.showsHorizontalScrollIndicator = NO; _scrollView.showsVerticalScrollIndicator = NO; [self.view addSubview:_scrollView]; } return _scrollView;}-(UIPageControl *)pageControl { if (!_pageControl) { _pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(0, 150, kScreenWidth, 50)]; _pageControl.pageIndicatorTintColor = [UIColor blackColor]; _pageControl.currentPageIndicatorTintColor = [UIColor grayColor]; [self.view addSubview:_pageControl]; } return _pageControl;}@end

四、demo下载

gitHub下载地址

图片 4

好了,如果大家使用的是swift语言,还可以参考这篇文章:

五、补充

今天去面试,面试官说,在没有失焦的情况下,一直滑动,滑动到头,就会滑不动了。这种情况怎么处理?当时没有回答上来,也没考虑过,回家玩了玩以前的DEMO,确实没有考虑这种情况。但是经过反复验证,找到了一种解决办法。也许不是最简单的。先呈上,请品味。

轮播实现步骤

思路:只要scrollView在动,就会走scrollViewDidScroll代理方法,那只要捕捉到偏移量最大和最小的瞬间,并在此瞬间将scrollView的偏移量设置到对应的位置就可以了。我发现一直拖拽的状态是走了scrollViewWillBeginDragging代理方法,但没有走scrollViewDidEndDragging代理方法,一旦走了scrollViewDidEndDragging代理方法,拖拽就结束。所以,我可以设置一个全局变量,记录拖拽的状态。有了拖拽的状态,我就可以在scrollViewDidScroll的方法中捕捉偏移量最大值和最小值。具体看下面实现。

接下来,笔者将从各方面逐一分析。

首先添加一个属性,布尔值类型,用来记录是否在拖拽状态
@property(nonatomic,assign)BOOL isDraging;//是否拖拽

当然,应该给一个初始值,这是我的习惯

    self.isDraging = NO;

层级结构

然后给这个属性进行赋值

在scrollView的开始拖拽的代理方法中,修改isDraging属性的值为YES,表示已经进入拖拽状态

-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
    //结束计时
    [self stopAction];
    self.isDraging = YES;
}

在scrollView结束拖拽的代理方法中,修改isDraging属性的值为NO,表示已经结束拖拽状态

-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
    self.isDraging = NO;
}

然后在代理方法:scrollViewDidScroll中加入两个判断,即在拖拽状态下,滑动到最大值和滑动到最小值时,跳转到第一张图片位置(此位置偏移量为一张图片的宽度)和最后一张图片位置(此位置偏移量为3张图片宽度偏移量位置)

-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
    if (self.isDraging&&self.scrollView.contentOffset.x == 0) {
        self.scrollView.contentOffset = CGPointMake(imageCount*kScreenWidth, 0);//跳到了倒数第二张ImageView(即最后一张图)
    }
    if (self.isDraging&&self.scrollView.contentOffset.x == (imageCount   1)*kScreenWidth) {
        self.scrollView.contentOffset = CGPointMake(kScreenWidth,0);//偏移量为第一张图
    }
    ...
}

这样问题就解决了。欢迎指点!

最底层是一个UIView,上面有一个UIScrollView以及UIPageControl,scrollView上有两个UIImageView,imageView宽高 = scrollview宽高 = view宽高

图片 5

轮播原理

假设轮播控件的宽度为x高度为y,我们设置scrollview的contentSize.width为3x,并让scrollview的水平偏移量为x,既显示最中间内容

1 scrollView.contentSize = CGSizeMake(3x, y);
2 scrollView.contentOffset = CGPointMake(x, 0);

图片 6

将imageView添加到scrollview内容视图的中间位置

图片 7

接下来使用代理方法scrollViewDidScroll来监听scrollview的滚动,定义一个枚举变量来记录滚动的方向

1 typedef enum{
2   DirecNone,
3   DirecLeft,
4   DirecRight
5 } Direction;@property (nonatomic, assign) Direction direction;
6  
7 - (void)scrollViewDidScroll:(UIScrollView *)scrollView {  self.direction = scrollView.contentOffset.x >x? DirecLeft : DirecRight;
8 }

使用KVO来监听direction属性值的改变

[self addObserver:self forKeyPath:@"direction" options:NSKeyValueObservingOptionNew context:nil];

判断滚动的方向,当偏移量大于x,表示左移,则将otherImageView加在右边,偏移量小于x,表示右移,则将otherImageView加在左边

图片 8

1 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {   //self.currIndex表示当前显示图片的索引,self.nextIndex表示将要显示图片的索引
2   //_images为图片数组
3   if(change[NSKeyValueChangeNewKey] == change[NSKeyValueChangeOldKey]) return;  if ([change[NSKeyValueChangeNewKey] intValue] == DirecRight) {    self.otherImageView.frame = CGRectMake(0, 0, self.width, self.height);    self.nextIndex = self.currIndex - 1;    if (self.nextIndex < 0) self.nextIndex = _images.count – 1;
4   } else if ([change[NSKeyValueChangeNewKey] intValue] == DirecLeft){    self.otherImageView.frame = CGRectMake(CGRectGetMaxX(_currImageView.frame), 0, self.width, self.height);    self.nextIndex = (self.currIndex   1) % _images.count;
5   }  self.otherImageView.image = self.images[self.nextIndex];
6 }

通过代理方法scrollViewDidEndDecelerating来监听滚动结束,结束后,会变成以下两种情况:

图片 9

此时,scrollview的偏移量为0或者2x,我们通过代码再次将scrollview的偏移量设置为x,并将currImageView的图片修改为otherImageView的图片

 1 - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
 2   [self pauseScroll];
 3 }
 4  
 5 - (void)pauseScroll {  self.direction = DirecNone;//清空滚动方向
 6     //判断最终是滚到了右边还是左边
 7   int index = self.scrollView.contentOffset.x / x;  if (index == 1) return; //等于1表示最后没有滚动,返回不做任何操作
 8   self.currIndex = self.nextIndex;//当前图片索引改变
 9   self.pageControl.currentPage = self.currIndex;  self.currImageView.frame = CGRectMake(x, 0, x, y);  self.currImageView.image = self.otherImageView.image;  self.scrollView.contentOffset = CGPointMake(x, 0);
10 }

那么我们看到的还是currImageView,只不过展示的是下一张图片,如图,又变成了最初的效果

图片 10

自动滚动

轮播的功能实现了,接下来添加定时器让它自动滚动,相当简单

1 - (void)startTimer {   //如果只有一张图片,则直接返回,不开启定时器
2    if (_images.count <= 1) return;   //如果定时器已开启,先停止再重新开启
3    if (self.timer) [self stopTimer];   self.timer = [NSTimer timerWithTimeInterval:self.time target:self selector:@selector(nextPage) userInfo:nil repeats:YES];
4    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
5 }
6  
7 - (void)nextPage {    //动画改变scrollview的偏移量就可以实现自动滚动
8   [self.scrollView setContentOffset:CGPointMake(self.width * 2, 0) animated:YES];
9 }

注意:setContentOffset:animated:方法执行完毕后不会调用scrollview的scrollViewDidEndDecelerating方法,但是会调用scrollViewDidEndScrollingAnimation方法,因此我们要在该方法中调用pauseScroll

1 - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
2   [self pauseScroll];
3 }

拖拽时停止自动滚动

当我们手动拖拽图片时,需要停止自动滚动,此时我们只需要让定时器失效就行了,当停止拖拽时,重新启动定时器

1 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
2   [self.timer invalidate];
3 }
4  
5 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
6   [self startTimer];
7 }

加载图片

实际开发中,我们很少会轮播本地图片,大部分都是服务器获取的,也有可能既有本地图片,也有网络图片,那要如何来加载呢?

定义4个属性

  • NSArray imageArray:暴露在.h文件中,外界将要加载的图片或路径数组赋值给该属性

  • NSMutableArray images:用来存放图片的数组

  • NSMutableDictionary imageDic:用来缓存图片的字典,key为URL

  • NSMutableDictionary operationDic:用来保存下载操作的字典,key为URL

判断外界传入的是图片还是路径,如果是图片,直接加入图片数组中,如果是路径,先添加一个占位图片,然后根据路径去下载图片

1 _images = [NSMutableArray array];for (int i = 0; i < imageArray.count; i  ) {    if ([imageArray[i] isKindOfClass:[UIImage class]]) {
2       [_images addObject:imageArray[i]];//如果是图片,直接添加到images中
3     } else if ([imageArray[i] isKindOfClass:[NSString class]]){
4       [_images addObject:[UIImage imageNamed:@"placeholder"]];//如果是路径,添加一个占位图片到images中
5       [self downloadImages:i];  //下载网络图片
6     }
7   }

下载图片,先从缓存中取,如果有,则替换之前的占位图片,如果没有,去沙盒中取,如果有,替换占位图片,并添加到缓存中,如果没有,开启异步线程下载

 1 - (void)downloadImages:(int)index {  NSString *key = _imageArray[index];  //从字典缓存中取图片
 2   UIImage *image = [self.imageDic objectForKey:key];  if (image) {
 3     _images[index] = image;//如果图片存在,则直接替换之前的占位图片
 4   }else{    //字典中没有从沙盒中取图片
 5     NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];    NSString *path = [cache stringByAppendingPathComponent:[key lastPathComponent]];    NSData *data = [NSData dataWithContentsOfFile:path];    if (data) {             //沙盒中有,替换占位图片,并加入字典缓存中
 6       image = [UIImage imageWithData:data];
 7       _images[index] = image;
 8       [self.imageDic setObject:image forKey:key];
 9     }else{       //字典沙盒都没有,下载图片
10       NSBlockOperation *download = [self.operationDic objectForKey:key];//查看下载操作是否存在
11       if (!download) {//不存在
12         //创建一个队列,默认为并发队列
13         NSOperationQueue *queue = [[NSOperationQueue alloc] init];        //创建一个下载操作
14         download = [NSBlockOperation blockOperationWithBlock:^{          NSURL *url = [NSURL URLWithString:key];          NSData *data = [NSData dataWithContentsOfURL:url];           if (data) {                        //下载完成后,替换占位图片,存入字典并写入沙盒,将下载操作从字典中移除掉
15             UIImage *image = [UIImage imageWithData:data];
16             [self.imageDic setObject:image forKey:key];            self.images[index] = image;                        //如果只有一张图片,需要在主线程主动去修改currImageView的值
17             if (_images.count == 1) [_currImageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
18             [data writeToFile:path atomically:YES];
19             [self.operationDic removeObjectForKey:key]; 
20             }
21         }];
22         [queue addOperation:download];
23         [self.operationDic setObject:download forKey:key];//将下载操作加入字典
24       }
25     }
26   }
27 }

监听图片点击

当图片被点击的时候,我们往往需要执行某些操作,因此需要监听图片的点击,思路如下

1.定义一个block属性暴露给外界void(^imageClickBlock)(NSInteger index)

(不会block的可以用代理,或者看这里)

2.设置currImageView的userInteractionEnabled为YES

3.给currImageView添加一个点击的手势

4.在手势方法里调用block,并传入图片索引

结束语

上面是笔者的主要思路以及部分代码,需要源码的请前往笔者的github下载:,记得献上你的星星哦

本文由新葡京8455发布,转载请注明来源

关键词: