无限循环的广告分页实现

这是一个使用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

右下角的点点的颜色位置也不应该写死,要有外界传入,因此需要参数:currentPageIndicatorTintColorpageIndicatorTintColorpageControllerCenter

既然是广告肯定可以点,因此我们还需要监听用户对图片的点击,需要设置代理

接下来就要开始初始化控件了

接下来就要初始化控件了。我们需要搞清楚都有哪些控件,第一:滚动说明肯定有 scrollView,scrollView 里需要展示图片,所以 scrollView 里包含 button 按钮。第二:需要标注页码,所以需要 pageController。

1
2
3
4
5
6
7
8
9
@interface AMInfiniteScrollPage () 

/** scrollView */
@property (weak, nonatomic) UIScrollView *scrollView;

/** pageController */
@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]) {

// 创建并设置 scrollView
[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];

// 默认是 YES,如果是 YES 按钮会在点击的时候变暗
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
/**
* 创建 scrollView
*/
- (UIScrollView *)scrollView
{
if (_scrollView == nil) {

UIScrollView *scrollView = [[UIScrollView alloc] init];

_scrollView = scrollView;

[self addSubview:scrollView];
}
return _scrollView;
}

/**
* 创建 pageController
*/
- (UIPageControl *)pageController
{
if (_pageController == nil) {

UIPageControl *pageController = [[UIPageControl alloc] init];

_pageController = pageController;

// 单页时隐藏
pageController.hidesForSinglePage = YES;

[self addSubview:pageController];
}
return _pageController;
}

到这里我们已经初始化完成了 scrollViewpageController

第三步要控制子控件的尺寸

接下来我们需要设置子控件的尺寸了,下面这点很关键:

由于 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];

// 设置 scrollView 的尺寸
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);
}

// 设置 pageController
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;

// 设置 pageController
self.pageController.numberOfPages = imageNames.count;
self.pageController.currentPage = 0;

// 更新 imageView 图片
[self addImageToButton];

/**
* 添加图片到 button 的背景图片中去
*/
- (void)addImageToButton
{
// 清空当前 currentPages 数组
[self.theCurrentPages removeAllObjects];

for (NSInteger i = 0 ; i < imageViewCount; i++) {

// 取出 button
UIButton *button = self.scrollView.subviews[i];

// 取出当前页码
NSInteger index = self.pageController.currentPage;

if (i == 0) {
// 如果是第一个 imageView,所对应的图片名在图片数组中的位置应该是: index - 1
index--;
} else if (i == 2) {
// 如果是第三个 imageView,所对应的图片名在图片数组中的位置应该是: index + 1
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
/** 用来记录当前3个 imageView 显示的页码 */
@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
{
// 找出中间的 button 的页码
NSInteger cunrrentPage = 0;

if (self.scrollView.contentOffset.x >= 0.5 * self.scrollView.contentSize.width) {

// 当向左划超过 button 一半的尺寸时
cunrrentPage = [self.theCurrentPages[imageViewCount - 1] integerValue];
} else if (self.scrollView.contentOffset.x >= 0.5 * self.scrollView.frame.size.width) {

// 当向左或者向右滑都未超过一半 button 的尺寸时
cunrrentPage = [self.theCurrentPages[imageViewCount - 2] integerValue];
} else {
// 向右滑超过 button 一半的尺寸时
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];
}

/**
* contentOffset 属性改变时
*/
- (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
/**
* scrollView 用户拖拽之前会调用
*/
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
[self stopTimer];
}

/**
* 停止定时器
*/
- (void)stopTimer
{
[self.timer invalidate];
self.timer = nil;
}

/**
* scrollView 用户停止拖拽时
*/
- (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
/**
* 点击了 scrollView中的按钮
*/
- (void)didSelectedImage
{
// 监听到了按钮点击 调用代理方法通知外界
if ([self.delegate respondsToSelector:@selector(infiniteScrollPage:didSelectedImageAtIndex:)]) {

[self.delegate infiniteScrollPage:self didSelectedImageAtIndex:self.pageController.currentPage];
}
}
文章作者: Ammar
文章链接: http://lizhaoloveit.cn/2014/05/07/%E6%97%A0%E9%99%90%E5%BE%AA%E7%8E%AF%E7%9A%84%E5%B9%BF%E5%91%8A%E5%88%86%E9%A1%B5%E5%AE%9E%E7%8E%B0/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ammar's Blog
打赏
  • 微信
  • 支付宝

评论