CustomScrollView
使用官方UIScrollView组件定制的一个横向滚动的视图。由于能力有限,暂没有抽象成一个UI组件,如果有大神能进行抽象封装,非常欢迎,大家多多交流!
说明
CustomScrollView包括诺干个子视图,可以横向滚动,滚动过程中会根据子视图所在位置进行大小缩放。即最中间的视图最大,两边呈对称状态逐渐减小。且可以通过点击按钮进行滚动,选定某个子视图居中。还可以动态进行新增和删除子视图的操作,其中删除操作为在子视图上进行上滑手势操作。
截图
具体实现
接下来我们来看看是怎么一步一步实现这种效果的。
模型
这里的模型只是我们简单定义的一个数据模型,模型包含了一个名称和对应的logo图标的名字。
//YSModel.h
#import <Foundation/Foundation.h>
@interface YSModel : NSObject
@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *logoName;
- (instancetype)initWithName:(NSString *)name logoName:(NSString *)logoName;
@end
//YSModel.m
#import "YSModel.h"
@implementation YSModel
- (instancetype)init
{
return [self initWithName:@"自定义" logoName:@"custom"];
}
- (instancetype)initWithName:(NSString *)name logoName:(NSString *)logoName
{
self = [super init];
if (self)
{
_name = name;
_logoName = logoName;
}
return self;
}
@end
界面实现和控件绑定
界面直接在xib文件里实现。只需要一个UIScrollView和UILabel就可以了,UILabel是为了当UIScrollView中的子视图滚动式,也会跟着切换。效果如图:
ViewController的实现
首先我们需要一些宏定义的常量:
//默认scrollView显示的模型数目
#define MODEL_NUMBER 5
//屏幕宽度
#define UISCREEN_WIDTH [[UIScreen mainScreen] bounds].size.width
//默认图标缩放比率
#define SCALE_RATE 0.6
其次我们需要一些变量,比如Outlet变量,数据模型数组等变量,详见demon里的代码。接下来我们主要详细介绍几个重点方法。
计算每个cell的宽高,以及初始化数据和UI视图
- (void)viewDidLoad
{
[super viewDidLoad];
//计算ScrollView中每个cell的宽高
self.cellWidth = self.scrollView.frame.size.width / MODEL_NUMBER;
self.cellHeight = self.scrollView.frame.size.height;
[self initModels];
[self initScrollView];
}
1.[self initModels] 方法主要是初始化模型数据,比较简单。
2.其主要的ScrollView初始化实现为方法[self initScrollView]。
该方法中,主要设置了ScrollView的初始化属性,例如禁用水平,垂直方向的滚动条,设定ScrollView初始位置等。
- (void)initScrollView
{
//设定scrollView的contentSize,即scrollView中包含的cell个数计算出来的内容大小
//+4是因为前后分别有两个空白的cell视图
self.scrollView.contentSize = CGSizeMake(self.cellWidth * (self.models.count + 4), self.cellHeight);
//清除scrollView的子视图
//[self.scrollView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
//设置scrollView的委托对象
self.scrollView.delegate = self;
//隐藏水平和竖直方向的滚动条
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.showsVerticalScrollIndicator = YES;
//设置scrollView滚动的减速速率
self.scrollView.decelerationRate = 0.95f;
if (!self.cellView)
{
self.cellView = [NSMutableArray array];
}
else
{
[self.cellView removeAllObjects];
}
//添加两个空白的cell块
for (int i = 0; i < 2; i++)
{
UIView *view = [self createEmptyCell:CGRectMake(self.cellWidth * i, 0, self.cellWidth, self.cellHeight)];
[self.scrollView addSubview:view];
}
//默认的六个块
for (int i = 2; i < self.models.count + 2; i++)
{
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(self.cellWidth * i, 0, self.cellWidth, self.cellHeight)];
//创建一个ImageView用于显示图标logo
UIImageView *image = [[UIImageView alloc] initWithFrame:CGRectMake(5, 5, self.cellWidth - 10, self.cellWidth - 10)];
//设置图片为logo图片
image.image = [UIImage imageNamed:[self.models[i - 2] logoName]];
//开启可交互模式
[image setUserInteractionEnabled:YES];
image.tag = i - 2;
view.tag = i -2;
//最后一个"自定义"按钮添加特定触摸手势
if (i == self.models.count + 1)
{
UITapGestureRecognizer *tapAddModel = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(tapToAddModel:)];
[image addGestureRecognizer:tapAddModel];
}
//别的模型添加点击手势和向上滑动删除手势
else
{
//添加点击手势
UITapGestureRecognizer *tapEditModel = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(tapToEditModel:)];
[image addGestureRecognizer:tapEditModel];
//添加滑动手势
UISwipeGestureRecognizer *swipeGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self
action:@selector(swipeToDeleteModel:)];
//设置滑动方向为向上
[swipeGesture setDirection:UISwipeGestureRecognizerDirectionUp];
[image addGestureRecognizer:swipeGesture];
}
[view addSubview:image];
//记录下对应的cell视图
[self.cellView addObject:view];
[self.scrollView addSubview:view];
}
//添加两个空白的块
for (long i = self.models.count + 2; i < self.models.count + 4; i++)
{
UIView *view = [self createEmptyCell:CGRectMake(self.cellWidth * i, 0, self.cellWidth, self.cellHeight)];
[self.scrollView addSubview:view];
}
//设置默认居中为第三个模型
[self.scrollView setContentOffset:CGPointMake(self.cellWidth * 2, 0) animated:YES];
self.cellIndex = 2;
//设置背景颜色和文字
[self updateCellBackground:(int)self.cellIndex];
}
//创建空白cell视图
- (UIView *)createEmptyCell:(CGRect)frame
{
UIView *view = [[UIView alloc] initWithFrame:frame];
//设置背景透明
view.backgroundColor = [UIColor clearColor];
return view;
}
此时我们应该会得到这样一个界面了。
实现UIScrollViewDelegate
我们的很多滚动动画效果都是基于UIScrollViewDelegate中的回调方法的。接下来我们就看看如何实现这些效果。
首先我们看看官方API中UIScrollView有哪些协议方法。
我们这里用的主要是这几个方法。
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
接下来我们详细看一下每个方法的实现。
(void)scrollViewDidScroll:(UIScrollView *)scrollView
//滑动过程中回调的函数,无论是手动滑动的,还是代码动画滑动都会回调该方法
//在这里计算那个cell是可见的,然后计算缩放比例,进行动画缩放
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//处理每一个cell,计算它的缩放比例
for (int i = 0; i < self.models.count; i++)
{
//cell左侧x位置
float lead = self.cellWidth * (i + 2);
//cell右侧x位置
float tail = self.cellWidth * (i + 3);
float rate = SCALE_RATE;
//cell在屏幕左,右侧,不可见,设置为默认缩放比例0.6
if (self.scrollView.contentOffset.x >= tail || (self.scrollView.contentOffset.x + UISCREEN_WIDTH) <= lead)
{
//暂时啥都不干
}
//cell在屏幕上
else
{
float sub = lead - self.scrollView.contentOffset.x;
//前半部分
if (sub <= 2 * self.cellWidth)
{
rate = sub / (2 * self.cellWidth) * SCALE_RATE + SCALE_RATE;
}
else
{
rate = (UISCREEN_WIDTH - sub - self.cellWidth) / (2 * self.cellWidth) * SCALE_RATE + SCALE_RATE;
}
}
//缩放该cell的视图
[self viewToScale:rate target:self.cellView[i]];
}
}
(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
//scrollView 拖拽操作结束
//判断接下来是否会进行减速操作,如果不需要减速则在这里进行计算,得出当前那个cell最靠近中间位置,并把该cell滑动到居中的位置
//否则,不做任何处理。其实则就是要进行减速,减速完毕会回调scrollViewDidEndDecelerating。
//综上,都会计算需要居中哪个cell
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (!decelerate)
{
[self cellJumpToIndex:scrollView];
}
}
(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
//scrollView 滑动过程减速完毕后回调的方法
//在这里进行计算,得出当前那个cell最靠近中间位置,并把该cell滑动到居中的位置
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self cellJumpToIndex:scrollView];
}
(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
//滑动动画结束时调用的函数
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
//根据居中的选项更新背景和文字
[self updateCellBackground:(int)self.cellIndex];
[self.scrollView setUserInteractionEnabled:YES];
}
我们要实现自动滑动居中的效果,这里有一个很关键的方法。该方法用于计算当前最靠近居中位置的是哪一个cell子视图。我们首先计算当前ScrollView的contentOffset.x的位置,从而得知当前显示的cell有哪些,然后计算出处于中间位置的cell的索引下标,再计算出该cell居中时的contentOffset.x位置,再进行动画移动到该位置即可。
- (void)cellJumpToIndex:(UIScrollView *)scrollView
{
if (self.scrollView.contentOffset.x < self.cellWidth * 0.5)
{
[self.scrollView setContentOffset:CGPointMake(0, 0) animated:YES];
}
else if (self.scrollView.contentOffset.x > self.cellWidth * (self.models.count + 1.5))
{
[self.scrollView setContentOffset:CGPointMake(self.cellWidth * (self.models.count + 1), 0) animated:YES];
}
int index = (int)(self.scrollView.contentOffset.x / self.cellWidth + 0.5);
[self.scrollView setContentOffset:CGPointMake(self.cellWidth * index, 0) animated:YES];
//选定某个模式,进行模式更新等操作
self.cellIndex = index;
}
最后就是一个是进行缩放的方法,还有一个更新cell对应的视图的方法。
//按比例缩放视图
- (void)viewToScale:(float)scale target:(UIView *)view
{
UIImageView *image = [[view subviews] lastObject];
[UIView beginAnimations:@"scale" context:nil];
image.transform = CGAffineTransformMakeScale(scale, scale);
[UIView commitAnimations];
}
//滑动到某个cell时更新视图的方法
- (void)updateCellBackground:(int)index
{
self.name.text = [self.models[index] name];
}
此时我们基本可以实现一开始希望得到的滚动缩放效果了。接下来我们的任务就是实现动态的cell增加和删除等功能。
期待ing…
个人博客
林友松。一个逗比的开发者。
Email:lysongzi.hnu@gmail.com
博客地址:lysongzi.com