一步一步拆解一个简单的iOS轮播图(三图)
导言(可以不看):
不吹不黑,也许是东半球最简单的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]; }
}
先把原理图粘在这吧,对照着代码看可能更容易一点:

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

好了,接着看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
一步一步拆解一个简单的iOS轮播图(三图)的更多相关文章
- 用Vue实现一个简单的图片轮播
本文已收录至https://github.com/likekk/studyBlog欢迎大家star,共同学习,共同进步.如果文章有错误的地方,欢迎大家指出.后期将在将GitHub上规划前端学习的路线和 ...
- viewPager+Handler+Timer简单实现广告轮播效果
基本思想是在Avtivity中放一个ViewPager,然后通过监听去实现联动效果,代码理由详细的解释,我就不说了. MainActivity.java package com.example.adm ...
- 前端(十七)—— jQuery基础:jQuery的基本使用、JQ功能概括、JS对象与JQ对象转换、Ajax简单应用、轮播图
jQuery的基本使用.JQ功能概括.JS对象与JQ对象转换.Ajax简单应用.轮播图 一.认识jQuery 1.什么是jQuery jQuery是对原生JavaScript二次封装的工具函数集合 j ...
- 【如何快速的开发一个简单的iOS直播app】(代码篇)
开篇([如何快速的开发一个完整的iOS直播app](原理篇)) 好久没写简书,因为好奇的我跑去学习直播了,今天就分享一下我的感慨. 目前为止直播还是比较热点的技术的,简书,git上有几篇阅读量和含金量 ...
- 用原生的javascript 实现一个无限滚动的轮播图
说一下思路:和我上一篇博客中用JQ去写的轮播图有相同点和不同点 相同点: 首先页面布局是一样的 同样是改变.inner盒子的位置去显示不同的图片 不同点: 为了实现无限滚动需要多添加两张重复的图片 左 ...
- 最简单的html轮播图制作适合新手
html代码 --------------------------------------------------------------------------------------------- ...
- 关于最近在做的一个js全屏轮播插件
最近去面试了,对方要求我在一个星期内用原生的js代码写一个全屏轮播的插件,第一想法就是跟照片轮播很相似,只是照片轮播是有定义一个宽高度大小已经确定了的容器用来存储所有照片,然后将照片全部左浮动,利用m ...
- 使用Handler和Timer+Timertask实现简单的图片轮播
布局文件就只放了一个简单的ImageView,就不展示了. 下面是Activity package com.example.administrator.handlerthreadmessagedemo ...
- 纯JS写最简单的图片轮播
非常简单的一个大图轮播,通过将控制显示位置来进行轮播效果,写来给正在学习的新手朋友们参考交流. 先看效果:(实际效果没有这么快) 先看布局: <div id="display" ...
随机推荐
- PYTHON之全局变量
应该尽量避免使用全局变量.不同的模块都可以自由的访问全局变量,可能会导致全局变量的不可预知性.对全局变量,如果程序员甲修改了_a的值,程序员乙同时也要使用_a,这时可能导致程序中的错误.这种错误是很难 ...
- Independent Components Analysis:独立成分分析
一.引言 ICA主要用于解决盲源分离问题.需要假设源信号之间是统计独立的.而在实际问题中,独立性假设基本是合理的. 二.随机变量独立性的概念 对于任意两个随机变量X和Y,如果从Y中得不到任何关于X的信 ...
- [ASP.NET MVC 小牛之路]02 - C#知识点提要
本人博客已转移至:http://www.exblr.com/liam 本篇博文主要对asp.net mvc开发需要撑握的C#语言知识点进行简单回顾,尤其是C# 3.0才有的一些C#语言特性.对于正在 ...
- Worktile 技术架构概要
其实早就该写这篇博客了,一直说忙于工作没有时间,其实时间挤挤总会有的,可能就是因为懒吧!从2013年11月一直拖到现在,今天就简单谈谈 Worktile 的技术架构吧 . Worktile 自上线到现 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (18) -----第三章 查询之结果集扁平化和多属性分组
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 3-14 结果集扁平化 问题 你有一对多关联的两个实体,你想通过一个查询,获取关联 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (24) ------ 第五章 加载实体和导航属性之查询内存对象
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-4 查询内存对象 问题 你想使用模型中的实体对象,如果他们已经加载到上下文中, ...
- Everything search syntax
Operators: space AND | OR ! NOT < > Grouping " " Search for an exact phrase. Wildcar ...
- ASP.NET MVC Routing学习笔记(一)
Routing在ASP.NET MVC中是非常核心的技术,属于ASP.NET MVC几大核心技术之一,在使用Routing之前,得先引入System.Web.Routing,但其实不用这么麻烦,因为在 ...
- css双飞翼布局
双飞翼布局是一种比较灵活的布局,始于淘宝UED,玉伯提出的,他着重介绍的是双飞翼栅格布局. 三列布局为"双飞燕"布局,可以把三栏比作一只鸟,main部分相当是于鸟的身体,而lef ...
- jQuery第二篇 (帅哥)
1.1 jQuery操作DOM jQuery课程的目标:学会使用jQuery设计常见效果 选择器 基本选择器:#id ..class .element.* . 层级选择器: 空格.>.+.~ 基 ...