通过看网上的轮子,结果发现很多都不能满足我们的需求,于是我决定自己写一个满足该项目的轮播图。
基本原理是用scrollView上面放置5个button,只展示下标为1,2,3的,左边隐藏下标为0的,右边隐藏下标为4的,用于在滑动的时候展示准备好的内容。当滑动一个button的宽度时,将scrollView复位到刚开始的状态,并且将button重新赋值。其中下标为2的button大小始终为选中时的大小。如图所示
首先上.h文件的代码
#import <UIKit/UIKit.h>
typedef void(^SYPCarouselViewBlock)(NSInteger selectedIndex);
@interface SYPCarouselView : UIView
@property (copy, nonatomic) SYPCarouselViewBlock block;
/**
*
* 初始化控件
*
* @param frame frame
*
* @param array 图片链接数组
*
* @param selectedIndex 选中button下标
*
*/
- (instancetype)initWithFrame:(CGRect)frame withArray:(NSArray *)array withSelectedIndex:(NSInteger)selectedIndex;
@end
block用来传回选中下标
相对应的.m文件
@interface SYPCarouselView () <UIScrollViewDelegate>
/** 数组 */
@property (strong, nonatomic) NSArray *array;
/** 滑动视图 */
@property (strong, nonatomic) UIScrollView *scrollView;
/** 当前选中下标 */
@property (assign, nonatomic) NSInteger selectedIndex;
@end
@implementation SYPCarouselView
- (instancetype)initWithFrame:(CGRect)frame withArray:(NSArray *)array withSelectedIndex:(NSInteger)selectedIndex
{
self = [super initWithFrame:frame];
if (self) {
_array = array;
_selectedIndex = selectedIndex;
[self createView];
}
return self;
}
比较简单,对初始化的参数赋值给属性。接下来是创建滑动视图
- (void)createView {
self.backgroundColor = [UIColor whiteColor];
CGFloat width = self.bounds.size.width;
CGFloat height = self.bounds.size.height;
//滑动视图的宽与中间放大按钮的宽一致,为控件宽的三分之一,高度是控件的高度
_scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(width / 3, 0, width / 3, height)];
_scrollView.contentSize = CGSizeMake(width / 3 * 5, height);
//隐藏滚动条
_scrollView.showsVerticalScrollIndicator = NO;
_scrollView.showsHorizontalScrollIndicator = NO;
//禁止弹出和分页
_scrollView.bounces = NO;
_scrollView.pagingEnabled = YES;
//保证在scrollView以外还能展示
_scrollView.clipsToBounds = NO;
_scrollView.delegate = self;
[self addSubview:_scrollView];
//创建按钮组
for (NSInteger i = 0; i < 5; i++) {
UIButton *button = [[UIButton alloc] init];
//当下标为2时,按钮变大
if (i == 2) {
button.frame = CGRectMake(width / 3 * 2, 0, width / 3, height);
} else {
button.frame = CGRectMake(i * width / 3 + 10, height / width * 30, width / 3 - 20, height - height / width * 60);
}
[_scrollView addSubview:button];
button.tag = i;
[button addTarget:self action:@selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside];
}
[self setImage];
//滑动视图默认将index为2的按钮显示在屏幕中间
[_scrollView setContentOffset:CGPointMake(width / 3 * 2, 0) animated:NO];
}
其中_scrollView.clipsToBounds = YES;是scrollView外展示button所必须的
#pragma mark - 在scrollView外接收滑动事件
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *view = [super hitTest:point withEvent:event];
if ([view isEqual:self]) {
for (UIView *subview in _scrollView.subviews) {
CGPoint offset = CGPointMake(point.x - _scrollView.frame.origin.x + _scrollView.contentOffset.x - subview.frame.origin.x,
point.y - _scrollView.frame.origin.y + _scrollView.contentOffset.y - subview.frame.origin.y);
if ((view = [subview hitTest:offset withEvent:event])) {
return view;
}
}
return _scrollView;
}
return view;
}
重写hitTest:withEvent:方法,从而使scrollView之外可以接收到滑动事件。接下来是滑动事件
#pragma mark - UIScrollViewDelegate滑动时调用
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat width = self.bounds.size.width;
//往正方向滑动
if (scrollView.contentOffset.x >= width) {
_selectedIndex++;
//当index越界,超出范围,根据index的值来判断循环起始index
while (_selectedIndex > _array.count - 1) {
//正方向,顺序循环
if (_selectedIndex > _array.count - 1) {
_selectedIndex = _selectedIndex - _array.count;
}
}
//图片赋值
[self setImage];
//重置scrollView位置
[scrollView setContentOffset:CGPointMake(width / 3 * 2, 0) animated:NO];
//回调当前下标
_block(_selectedIndex);
//往负方向滑动
} else if (scrollView.contentOffset.x <= width / 3) {
_selectedIndex--;
//当index越界,超出范围,根据index的值来判断循环起始index
while (_selectedIndex < 0) {
//负方向,倒序循环
if (_selectedIndex < 0) {
_selectedIndex = _array.count + _selectedIndex;
}
}
//图片赋值
[self setImage];
//重置scrollView位置
[scrollView setContentOffset:CGPointMake(width / 3 * 2, 0) animated:NO];
//回调当前下标
_block(_selectedIndex);
}
}
轮播图重要的地方就是这里,因为scrollView不能循环,当滑到头的时候,要根据滑动方向来对index进行处理,做出循环效果
#pragma mark - 按钮点击事件
- (void)buttonAction:(UIButton *)button {
CGFloat width = self.bounds.size.width;
switch (button.tag) {
case 1: {
[_scrollView setContentOffset:CGPointMake(width / 3, 0) animated:YES];
} break;
case 2: {
} break;
case 3: {
[_scrollView setContentOffset:CGPointMake(width, 0) animated:YES];
} break;
default:
break;
}
}
按钮点击事件,不多解释
#pragma mark - 赋值
- (void)setImage {
for (NSInteger i = 0; i < _scrollView.subviews.count; i++) {
UIButton *button = _scrollView.subviews[i];
//由于中间按钮index为2,根据选中的图片index确定其他位置按钮图片的index
NSInteger index = _selectedIndex + i - 2;
//如果index越界,对index进行处理,做出循环效果
while (index < 0 || index > _array.count - 1) {
if (index < 0) {
index = _array.count + index;
} else if (index > _array.count - 1) {
index = index - _array.count;
}
}
//图片下载
UIImageView *imageManager = [[UIImageView alloc] init];
[self addSubview:imageManager];
[imageManager sd_setImageWithURL:_array[index] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (image) {
[button setBackgroundImage:image forState:UIControlStateNormal];
[imageManager removeFromSuperview];
} else {
[button setBackgroundImage:KNEW_HOLDER_IMAGE forState:UIControlStateNormal];
[imageManager removeFromSuperview];
}
}];
}
}
利用前面选中的下标,来确定其他button图片的index,因为有5个button,当图片少于5张或者index越界的时候,要对index进行处理,做出循环效果。图片下载用的第三方框架SDWebImage