iOS实战-自定义的横向滚动控件CustomScrollView

来源:互联网 发布:浙江大学网络运行系统 编辑:程序博客网 时间:2024/05/09 05:50

CustomScrollView

  使用官方UIScrollView组件定制的一个横向滚动的视图。由于能力有限,暂没有抽象成一个UI组件,如果有大神能进行抽象封装,非常欢迎,大家多多交流!

1.1 说明

  CustomScrollView包括诺干个子视图,可以横向滚动,滚动过程中会根据子视图所在位置进行大小缩放。即最中间的视图最大,两边呈对称状态逐渐减小。且可以通过点击按钮进行滚动,选定某个子视图居中。还可以动态进行新增和删除子视图的操作,其中删除操作为在子视图上进行上滑手势操作。

Github 项目传送门——CustomScrollView

1.2 截图

二、具体实现

接下来我们来看看是怎么一步一步实现这种效果的。

2.1 模型

这里的模型只是我们简单定义的一个数据模型,模型包含了一个名称和对应的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

2.2 界面实现和控件绑定

  界面直接在xib文件里实现。只需要一个UIScrollView和UILabel就可以了,UILabel是为了当UIScrollView中的子视图滚动式,也会跟着切换。效果如图:

2.3 ViewController的实现

首先我们需要一些宏定义的常量:

//默认scrollView显示的模型数目#define MODEL_NUMBER 5//屏幕宽度#define UISCREEN_WIDTH [[UIScreen mainScreen] bounds].size.width//默认图标缩放比率#define SCALE_RATE 0.6

其次我们需要一些变量,比如Outlet变量,数据模型数组等变量,详见demon里的代码。接下来我们主要详细介绍几个重点方法。

2.3.1 计算每个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;}

此时我们应该会得到这样一个界面了。

2.3.2 实现UIScrollViewDelegate

  我们的很多滚动动画效果都是基于UIScrollViewDelegate中的回调方法的。接下来我们就看看如何实现这些效果。

首先我们看看官方API中UIScrollView有哪些协议方法。

我们这里用的主要是这几个方法。

  1. (void)scrollViewDidScroll:(UIScrollView *)scrollView
  2. (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
  3. (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
  4. (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

1 0