这是一个使用3个 imageView 完成的广告展示页面
代码如下:无线循环的广告分页
实现思路如下:
当用户向左滑动屏幕时,此时 imageView3 展示到了 scrollView 上,imageView1 完全看不见,可以将 imageView1 重用到 imageView3 后面展示下一张图片。
当用户向右滑动屏幕时,此时 imageView1 展示到了 scrollView 上, imageView3 完全看不见,可以将 imageView3 重用到 imageView1 前面展示上一张图片。
具体代码和讲解:
第一步需要搞清楚项目需求
创建项目,这里以我的 AMInfiniteScrollPage 为例:
拿到一个自定义控件,我们要先写其接口,就是先思考外界如何使用你的控件。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | @interface AMInfiniteScrollPage : UIView
 
 
 @property (strong, nonatomic) NSArray *imageNames;
 
 
 @property (assign, nonatomic) NSTimeInterval timeInterval;
 
 
 @property (strong, nonatomic) UIColor *currentPageIndicatorTintColor;
 
 
 @property (strong, nonatomic) UIColor *pageIndicatorTintColor;
 
 
 @property (assign, nonatomic) CGPoint pageControllerCenter;
 
 | 
需要展示广告图片,因此需要接受一个图片名数组:imageNames。
既然是广告,需要自动滚动,因此要设置滚动时间间隔:timeInterval
右下角的点点的颜色和位置也不应该写死,要有外界传入,因此需要参数:currentPageIndicatorTintColor,pageIndicatorTintColor,pageControllerCenter。
既然是广告肯定可以点,因此我们还需要监听用户对图片的点击,需要设置代理。
接下来就要开始初始化控件了
接下来就要初始化控件了。我们需要搞清楚都有哪些控件,第一:滚动说明肯定有 scrollView,scrollView 里需要展示图片,所以 scrollView 里包含 button 按钮。第二:需要标注页码,所以需要 pageController。
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | @interface AMInfiniteScrollPage () 
 
 @property (weak, nonatomic) UIScrollView *scrollView;
 
 
 @property (weak, nonatomic) UIPageControl *pageController;
 
 @end
 
 | 
初始化方法需要写在下面的方法中:
这里建议将代码模块化,一个方法做一件事情,将每个功能都封装起来。
| 12
 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
 
 | - (instancetype)initWithFrame:(CGRect)frame{
 if (self = [super initWithFrame:frame]) {
 
 
 [self setupScrollView];
 }
 return self;
 }
 
 - (void)setupScrollView
 {
 
 self.scrollView.showsHorizontalScrollIndicator = NO;
 self.scrollView.showsVerticalScrollIndicator = NO;
 
 
 self.scrollView.bounces = NO;
 
 
 self.scrollView.pagingEnabled = YES;
 
 
 self.scrollView.userInteractionEnabled = YES;
 
 
 self.scrollView.delegate = self;
 
 
 for (NSInteger i = 0; i < imageViewCount; i++) {
 
 UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
 
 
 button.adjustsImageWhenHighlighted = NO;
 
 
 
 if (i == 1) {
 [button addTarget:self action:@selector(didSelectedImage) forControlEvents:UIControlEventTouchUpInside];
 }
 
 [self.scrollView addSubview:button];
 }
 }
 
 | 
这里用按钮替代了 imageView 是因为将来用户可能点击图片进入广告界面,因此需要监听用户的点击,按钮可以很轻松的监听到用户点击
建议把子控件写成懒加载的形式,这样在设置子控件的时候不用在意顺序,肯定是不为空的。下面为懒加载代码:
| 12
 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
 
 | 
 
 - (UIScrollView *)scrollView
 {
 if (_scrollView == nil) {
 
 UIScrollView *scrollView = [[UIScrollView alloc] init];
 
 _scrollView = scrollView;
 
 [self addSubview:scrollView];
 }
 return _scrollView;
 }
 
 
 
 
 - (UIPageControl *)pageController
 {
 if (_pageController == nil) {
 
 UIPageControl *pageController = [[UIPageControl alloc] init];
 
 _pageController = pageController;
 
 
 pageController.hidesForSinglePage = YES;
 
 [self addSubview:pageController];
 }
 return _pageController;
 }
 
 | 
到这里我们已经初始化完成了 scrollView 和 pageController
第三步要控制子控件的尺寸
接下来我们需要设置子控件的尺寸了,下面这点很关键:
由于 AMInfiniteScrollPage:UIView 是父控件,而它的尺寸是由外界决定的,而设置子控件(scrollView 和 pageController)的尺寸时必须基于父控件,这时我们需要用到 -layoutSubviews方法,这个方法会在 AMInfiniteScrollPage:UIView 的尺寸发生变化之后的一个恰当的时刻调用,而进入这个方法后,AMInfiniteScrollPage:UIView 的尺寸就准确了。因此在这个方法中布局子控件是最合适的
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 
 | - (void)layoutSubviews{
 [super layoutSubviews];
 
 
 self.scrollView.frame = self.bounds;
 self.scrollView.contentSize = CGSizeMake(self.bounds.size.width * imageViewCount, 0);
 
 
 for (NSInteger i = 0; i < imageViewCount; i++) {
 
 UIButton *button = self.scrollView.subviews[i];
 
 button.frame = CGRectMake(i * self.scrollView.frame.size.width, 0, self.scrollView.frame.size.width, self.scrollView.frame.size.height);
 }
 
 
 if (!CGPointEqualToPoint(self.pageController.center, CGPointZero)) {
 self.pageController.center = self.pageControllerCenter;
 } else {
 self.pageController.center = CGPointMake(0.83 * self.bounds.size.width, 0.9 * self.bounds.size.height);
 }
 
 
 [self addImageToButton];
 }
 
 | 
在布局完子控件的尺寸后,就要给子控件设置内容了。前面说过,单独的功能封装成单独的方法。
给子控件设置显示内容
重写 imageNames 的 setter 方法,这里可以拿到外界传入的 imageNames,我们就可以知道有多少页了,同时还知道具体的图片是什么了。这时设置 pageController 和 添加图片到 imageView 上去的最佳时机。
| 12
 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
 
 | - (void)setImageNames:(NSArray *)imageNames{
 _imageNames = imageNames;
 
 
 self.pageController.numberOfPages = imageNames.count;
 self.pageController.currentPage = 0;
 
 
 [self addImageToButton];
 
 
 
 
 - (void)addImageToButton
 {
 
 [self.theCurrentPages removeAllObjects];
 
 for (NSInteger i = 0 ; i < imageViewCount; i++) {
 
 
 UIButton *button = self.scrollView.subviews[i];
 
 
 NSInteger index = self.pageController.currentPage;
 
 if (i == 0) {
 
 index--;
 } else if (i == 2) {
 
 index++;
 }
 
 
 if (index < 0) {
 
 index = self.pageController.numberOfPages - 1;
 } else if (index == self.pageController.numberOfPages) {
 
 index = 0;
 }
 
 [button setBackgroundImage:[UIImage imageNamed:self.imageNames[index]] forState:UIControlStateNormal];
 [self.theCurrentPages addObject:@(index)];
 }
 
 
 self.scrollView.contentOffset = CGPointMake(self.scrollView.frame.size.width, 0);
 }
 
 | 
思路:以当前展示图片的中间的 button 为基准,假设图片名在 imageNames 中的索引为 index,则前一个索引就是 index–(由前一个 imageView 负责展示),后一个索引就是 index++(由后一个 button 负责展示),如果当前索引为0,则说明展示的是第一张图片,这前一个 button 就展示 imageNames 的最后一张图片。
   因为一会滚动时需要拿到当前3个 button  所展示的3张图片进行操作,所以用一个数组保存当前的3个 button 展示的图片的索引。然后根据索引对 button 中的 image 赋值。
| 12
 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
 
 | @property (strong, nonatomic) NSMutableArray *theCurrentPages;
 
 ```
 最后不要忘记让 scrollView 偏移到中间的 button(显示中间的 button 的背景图片)。
 
 
 
 但由于此时可能 `AMInfiniteScrollPage:UIView` 调用 `- (void)setImageNames:(NSArray *)imageNames` 方法时并没有尺寸。因此为了保险起见,应当在 `- (void)layoutSubviews` 方法中设置完尺寸后再调用一次 `- (void)addImageToButton` 方法显示图片。
 
 ***
 
 到这一步,当你导入头文件,设置属性后,运行程序已经可以看见图片显示了。
 
 ### 实现业务逻辑
 
 接下来需要实现的是,当用户拖拽 scrollView 时,3个 imageView 不断显示新的图片即可。
 代码如下:
 
 ```objc
 
 
 
 - (void)scrollViewDidScroll:(UIScrollView *)scrollView
 {
 
 NSInteger cunrrentPage = 0;
 
 if (self.scrollView.contentOffset.x >= 0.5 * self.scrollView.contentSize.width) {
 
 
 cunrrentPage = [self.theCurrentPages[imageViewCount - 1] integerValue];
 } else if (self.scrollView.contentOffset.x >= 0.5 * self.scrollView.frame.size.width) {
 
 
 cunrrentPage = [self.theCurrentPages[imageViewCount - 2] integerValue];
 } else {
 
 cunrrentPage = [self.theCurrentPages[imageViewCount - 3] integerValue];
 }
 
 self.pageController.currentPage = cunrrentPage;
 }
 
 | 
我们必须实现 scrollView 的代理方法,关于 scrollView 的用法可参见 UIScrollView
具体看注释。通过 scrollView 的 contentOffset.x 的值来判定当前展示的是哪个 button。
及时调整 currentPage 的值。接下来,当用户停止拖拽时,只需要根据 currentPage 来重新给 button 赋上新的 image 即可。这里需要考虑不是用户拖拽,而是别的情况导致 scrollView 的 contentOffset 属性的变化引起的 currentPage 的变化。具体代码如下
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | 
 
 - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
 {
 
 [self addImageToButton];
 }
 
 
 
 
 - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
 {
 [self addImageToButton];
 }
 
 | 
当这些代码完成后,你已经可以拖拽广告页面了,基本实现了3个控件轮播展示广告分页,接下来需要做的就是加一个定时器让 scrollView 自动滚动。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | 
 
 - (void)setTimeInterval:(NSTimeInterval)timeInterval
 {
 _timeInterval = timeInterval;
 
 
 NSTimer *timer = [NSTimer timerWithTimeInterval:timeInterval target:self selector:@selector(nextPage) userInfo:nil repeats:YES];
 self.timer = timer;
 
 [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
 }
 
 
 
 
 - (void)nextPage
 {
 [self.scrollView setContentOffset:CGPointMake(2 * self.scrollView.frame.size.width, 0) animated:YES];
 }
 
 | 
我们拿到外界的间隔时间,创建定时器。每隔 timeInterval 秒执行 nextPage 方法。定时器需要在用户拖拽时销毁,而在用户停止拖拽时启动。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 
 | 
 
 - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
 {
 [self stopTimer];
 }
 
 
 
 
 - (void)stopTimer
 {
 [self.timer invalidate];
 self.timer = nil;
 }
 
 
 
 
 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
 {
 [self setTimeInterval:self.timeInterval];
 }
 
 | 
至此除了监听按钮点击功能,其他功能已经写完了。
使用代理让外界可以监听到按钮的点击
要想监听按钮的点击功能,由于外界拿不到 AMInfiniteScrollPage 中的 button,因此外界无法直接监听 AMInfiniteScrollPage 的点击事件,需要设置代理。
当用户点击了哪张图片,就调用代理方法通知外界,并将图片的索引传给外界。
第一步:声明代理和代理方法
| 12
 3
 4
 5
 6
 7
 8
 
 | @protocol AMInfiniteScrollPageDelegate <NSObject>
 @optional
 
 - (void)infiniteScrollPage:(AMInfiniteScrollPage *)infiniteScrollPage didSelectedImageAtIndex:(NSInteger)index;
 
 
 @end
 
 | 
第二步:在监听点击的方法中调用代理的代理方法即可
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | 
 
 - (void)didSelectedImage
 {
 
 if ([self.delegate respondsToSelector:@selector(infiniteScrollPage:didSelectedImageAtIndex:)]) {
 
 [self.delegate infiniteScrollPage:self didSelectedImageAtIndex:self.pageController.currentPage];
 }
 }
 
 |