一步一步拆解一个简单的iOS轮播图(三图)

时间:2023-03-08 19:45:55

导言(可以不看):

不吹不黑,也许是东半球最简单的iOS轮播图拆分注释(讲解不敢当)了(tree new bee)。(一句话包含两个人,你能猜到有谁吗?提示:一个在卖手机,一个最近在卖书)哈哈。。。

我第一次项目中需要使用轮播图的时候我是用的别人写好的一个*,那个轮播封装很多东西,包括比如可以设置pageControl的位置,可以传图片url或本地图片,缓存网络图片等等。但是我觉得没必要搞那么复杂,我喜欢简单并足够做事的东西。现在有时间便想自己把它拆解一下。看了一些简书上一些作者写的关于轮播图的讲解,我发现好多人写的其实是有问题的,虽然不易发现,但是你仔细测一下他的demo,很多都有问题。

我的这个轮播控件基本能满足现有市场上的所有app的轮播,反正我没见过把轮播搞得更花哨的,没太大意义。我自己把它的实现分为三块:1、添加基本控件,控制滚动(也就是控制scrollView,实现代理方法);2、自动滚动,timer;3、处理点击事件(代理)。代码注释很详细了,还看不懂的可以给我留言。

自定义一个View继承自UIView,这个类就是封装的轮播图类

先看看我们需要在初始化用语句中给这个自定义View添加哪些控件:scrollView、pageControl、三个按钮图(有self的应该知道是定义在在类拓展中的属性吧)

 - (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
//定义一个scrollView,最主要的轮播控件
UIScrollView *scrollView = [[UIScrollView alloc] init];
scrollView.delegate = self;
//横竖两种滚轮都不显示
scrollView.showsVerticalScrollIndicator = NO;
scrollView.showsHorizontalScrollIndicator = NO;
//需要分页
scrollView.pagingEnabled = YES;
//不需要回弹(试了一下加不加应该都没什么影响)
scrollView.bounces = NO;
[self addSubview:scrollView];
self.scrollView = scrollView; //在scrollView中添加三个图片按钮,因为后面需要响应点击事件,所以我直接用按钮不用imageView了,感觉更方便一些
for (int i = ;i < imageBtnCount; i++) {
UIButton *imageBtn = [[UIButton alloc] init];
[scrollView addSubview:imageBtn];
}
//添加pageControl
UIPageControl *pageControl = [[UIPageControl alloc] init];
[self addSubview:pageControl];
self.pageControl = pageControl;
}
return self;
}

类拓展中的属性:(timer后面会需要,定时自动轮播)

 @property (nonatomic, weak) UIScrollView*scrollView;
@property (nonatomic, weak) UIPageControl *pageControl;
@property (nonatomic, weak) NSTimer *timer;

接下来布局子控件:

布局子控件之前要先说一个东西:

 static const int imageBtnCount = ;

这个count我们很多地方都会用到,因为这个轮播图的原理就是用三张图来实现无限循环轮播的假象。(#define能少用就少用吧啊)

 //布局子控件
- (void)layoutSubviews {
[super layoutSubviews];
//设置scrollView的frame
self.scrollView.frame = self.bounds; CGFloat width = self.bounds.size.width;
CGFloat height = self.bounds.size.height;
//设置contentSize,不同轮播方向的时候contentSize是不一样的
if (self.isScrollDorectionPortrait) { //竖向
//contentSize要放三张图片
self.scrollView.contentSize = CGSizeMake(width, height * imageBtnCount);
} else { //横向
self.scrollView.contentSize = CGSizeMake(width * imageBtnCount, height);
}
//设置三张图片的位置,并为三个按钮添加点击事件
for (int i = ; i < imageBtnCount; i++) {
UIButton *imageBtn = self.scrollView.subviews[i];
[imageBtn addTarget:self action:@selector(imageBtnClick:) forControlEvents:UIControlEventTouchUpInside];
if (self.isScrollDorectionPortrait) { //竖向
imageBtn.frame = CGRectMake(, i * height, width, height);
} else { //横向
imageBtn.frame = CGRectMake(i * width, , width, height);
}
}
//设置contentOffset,显示最中间的图片
if (self.isScrollDorectionPortrait) { //竖向
self.scrollView.contentOffset = CGPointMake(, height);
} else { //横向
self.scrollView.contentOffset = CGPointMake(width, );
} //设置pageControl的位置
CGFloat pageW = ;
CGFloat pageH = ;
CGFloat pageX = width - pageW;
CGFloat pageY = height - pageH;
self.pageControl.frame = CGRectMake(pageX, pageY, pageW, pageH); }

接下来看一下对外接口:(代理暂时不用看,那是后面处理点击的事了)

 #import <UIKit/UIKit.h>

 @class ATCarouselView;
@protocol ATCarouselViewDelegate <NSObject>
@optional
/**
* 点击图片的回调事件
*/
- (void)carouselView:(ATCarouselView *)carouselView indexOfClickedImageBtn:(NSUInteger)index;
@end @interface ATCarouselView : UIView
//传入图片数组
@property (nonatomic, copy) NSArray *images;
//pageControl颜色设置
@property (nonatomic, strong) UIColor *currentPageColor;
@property (nonatomic, strong) UIColor *pageColor;
//是否竖向滚动
@property (nonatomic, assign, getter=isScrollDorectionPortrait) BOOL scrollDorectionPortrait; @property (weak, nonatomic) id<ATCarouselViewDelegate> delegate;
@end

使用者需要设置的东西都在这里了:接下来看set方法:(pageControl的太简单就不占篇幅了)

 //根据传入的图片数组设置图片
- (void)setImages:(NSArray *)images {
_images = images;
//pageControl的页数就是图片的个数
self.pageControl.numberOfPages = images.count;
//默认一开始显示的是第0页
self.pageControl.currentPage = ;
//设置图片显示内容
[self setContent];
//开启定时器
[self startTimer]; }

下面看setContent方法,设置显示内容,定时器在后面说:

 //设置显示内容
- (void)setContent {
//设置三个imageBtn的显示图片
for (int i = ; i < self.scrollView.subviews.count; i++) {
//取出三个imageBtn
UIButton *imageBtn = self.scrollView.subviews[i];
//这个是为了给图片做索引用的
NSInteger index = self.pageControl.currentPage; if (i == ) { //第一个imageBtn,隐藏在当前显示的imageBtn的左侧
index--; //当前页索引减1就是第一个imageBtn的图片索引
} else if (i == ) { //第三个imageBtn,隐藏在当前显示的imageBtn的右侧
index++; //当前页索引加1就是第三个imageBtn的图片索引
}
//无限循环效果的处理就在这里
if (index < ) { //当上面index为0的时候,再向右拖动,左侧图片显示,这时候我们让他显示最后一张图片
index = self.pageControl.numberOfPages - ;
} else if (index == self.pageControl.numberOfPages) { //当上面的index超过最大page索引的时候,也就是滑到最右再继续滑的时候,让他显示第一张图片
index = ;
}
imageBtn.tag = index;
//用上面处理好的索引给imageBtn设置图片
[imageBtn setBackgroundImage:self.images[index] forState:UIControlStateNormal];
[imageBtn setBackgroundImage:self.images[index] forState:UIControlStateHighlighted]; }
}

先把原理图粘在这吧,对照着代码看可能更容易一点:

一步一步拆解一个简单的iOS轮播图(三图)

最后这个是最核心的步骤:

一步一步拆解一个简单的iOS轮播图(三图)

好了,接着看updateContent:

 //状态改变之后更新显示内容
- (void)updateContent {
CGFloat width = self.bounds.size.width;
CGFloat height = self.bounds.size.height;
[self setContent];
//唯一跟设置显示内容不同的就是重新设置偏移量,让它永远用中间的按钮显示图片,滑动之后就偷偷的把偏移位置设置回去,这样就实现了永远用中间的按钮显示图片
//设置偏移量在中间
if (self.isScrollDorectionPortrait) {
self.scrollView.contentOffset = CGPointMake(, height);
} else {
self.scrollView.contentOffset = CGPointMake(width, );
}
}

后面就简单了,滚动的时候的一些操作:

 //拖拽的时候执行哪些操作
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
//拖动的时候,哪张图片最靠中间,也就是偏移量最小,就滑到哪页
//用来设置当前页
NSInteger page = ;
//用来拿最小偏移量
CGFloat minDistance = MAXFLOAT;
//遍历三个imageView,看那个图片偏移最小,也就是最靠中间
for (int i = ; i < self.scrollView.subviews.count; i++) {
UIButton *imageBtn = self.scrollView.subviews[i];
CGFloat distance = ;
if (self.isScrollDorectionPortrait) {
distance = ABS(imageBtn.frame.origin.y - scrollView.contentOffset.y);
} else {
distance = ABS(imageBtn.frame.origin.x - scrollView.contentOffset.x);
}
if (distance < minDistance) {
minDistance = distance;
page = imageBtn.tag;
}
}
self.pageControl.currentPage = page;
} //结束拖拽的时候更新image内容
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self updateContent];
}

接下来就是定时器和代理设置点击事件了,这种比较简单的我就不多说了,我理解最难的地方都在上面的图里说明白了:

先说最简单的点击事件:.h文件

 @class ATCarouselView;
@protocol ATCarouselViewDelegate <NSObject>
@optional
/**
* 点击图片的回调事件
*/
- (void)carouselView:(ATCarouselView *)carouselView indexOfClickedImageBtn:(NSUInteger)index;
@end

.m文件

 - (void)imageBtnClick:(UIButton *)btn {
// NSLog(@"%ld",btn.tag);
if ([self.delegate respondsToSelector:@selector(carouselView:indexOfClickedImageBtn:)])
{
[self.delegate carouselView:self indexOfClickedImageBtn:btn.tag];
} }

最后是定时器自动轮播的处理:

 //开始计时器
- (void)startTimer {
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(nextImage) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
self.timer = timer;
}
//停止计时器
- (void)stopTimer {
//结束计时
[self.timer invalidate];
//计时器被系统强引用,必须手动释放
self.timer = nil;
}
//通过改变contentOffset * 2换到下一张图片
- (void)nextImage {
CGFloat height = self.bounds.size.height;
CGFloat width = self.bounds.size.width;
if (self.isScrollDorectionPortrait) {
[self.scrollView setContentOffset:CGPointMake(, * height) animated:YES];
} else {
[self.scrollView setContentOffset:CGPointMake( * width, ) animated:YES];
}
}

最后是使用这个轮播图:

 - (void)viewDidLoad {
[super viewDidLoad];
ATCarouselView *carousel = [[ATCarouselView alloc] initWithFrame:CGRectMake(, , [UIScreen mainScreen].bounds.size.width, )];
carousel.delegate = self;
// carousel.scrollDorectionPortrait = YES;
carousel.images = @[
[UIImage imageNamed:@""],
[UIImage imageNamed:@""],
[UIImage imageNamed:@""],
[UIImage imageNamed:@""],
[UIImage imageNamed:@""]
];
carousel.currentPageColor = [UIColor orangeColor];
carousel.pageColor = [UIColor grayColor];
[self.view addSubview:carousel]; }
- (void)carouselView:(ATCarouselView *)carouselView indexOfClickedImageBtn:(NSUInteger )index {
NSLog(@"点击了第%ld张图片",index);
}

博客里把所有代码都贴上就太浪费空间了,基本上所有比较重要的都在上面了,如果还有不懂的可以看一下demo跑一下,有问题欢迎留言: my github:https://github.com/alan12138/carousel