这是一个使用3个 imageView 完成的广告展示页面
代码如下:无线循环的广告分页
实现思路如下:
当用户向左滑动屏幕时,此时 imageView3 展示到了 scrollView 上,imageView1 完全看不见,可以将 imageView1 重用到 imageView3 后面展示下一张图片。
当用户向右滑动屏幕时,此时 imageView1 展示到了 scrollView 上, imageView3 完全看不见,可以将 imageView3 重用到 imageView1 前面展示上一张图片。
具体代码和讲解:
第一步需要搞清楚项目需求
创建项目,这里以我的 AMInfiniteScrollPage 为例:
拿到一个自定义控件,我们要先写其接口,就是先思考外界如何使用你的控件。
1 2 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。
1 2 3 4 5 6 7 8 9
| @interface AMInfiniteScrollPage ()
@property (weak, nonatomic) UIScrollView *scrollView;
@property (weak, nonatomic) UIPageControl *pageController;
@end
|
初始化方法需要写在下面的方法中:
这里建议将代码模块化,一个方法做一件事情,将每个功能都封装起来。
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
| - (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 是因为将来用户可能点击图片进入广告界面,因此需要监听用户的点击,按钮可以很轻松的监听到用户点击
建议把子控件写成懒加载的形式,这样在设置子控件的时候不用在意顺序,肯定是不为空的。下面为懒加载代码:
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
|
- (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
的尺寸就准确了。因此在这个方法中布局子控件是最合适的
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
| - (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 上去的最佳时机。
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
| - (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 赋值。
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
| @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 的变化。具体代码如下
1 2 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 自动滚动。
1 2 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
方法。定时器需要在用户拖拽时销毁,而在用户停止拖拽时启动。
1 2 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 的点击事件,需要设置代理。
当用户点击了哪张图片,就调用代理方法通知外界,并将图片的索引传给外界。
第一步:声明代理和代理方法
1 2 3 4 5 6 7 8
| @protocol AMInfiniteScrollPageDelegate <NSObject>
@optional
- (void)infiniteScrollPage:(AMInfiniteScrollPage *)infiniteScrollPage didSelectedImageAtIndex:(NSInteger)index;
@end
|
第二步:在监听点击的方法中调用代理的代理方法即可
1 2 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]; } }
|