UICollectionView之滚动图片缩放

开始前的准备
  • 先看下效果,这个效果是使用UICollectionView实现的,通过自定义继承自系统的流水布局
kobe.gif
  • 如果你对上面效果感兴趣,那非常欢迎你继续往下看,首先我需要先说明几个关于自定义继承自系统的流水布局的几个关键方法,实现这个效果的基础就是先清楚这几个方法哦
    实现这个效果,就默认大家已经掌握了collectionView的最基本使用了哦,所以这里就不在详细说明collectionView的一些基本属性和方法啦
基本结构

实现该效果其实也不是很复杂的哦

  • 创建一个UICollectionView,尺寸和屏幕一样大
  • 自定义Cell继承自UICollectionViewCell --- LBPhotoCell
  • 自定义布局,继承自系统的UICollectionViewFlowLayout --- LBVerLayout
关键方法说明

可能会枯燥,但是这个是理解这个效果的前提哦,我也会非常用心的讲解

1)重写   - (void)prepareLayout

 - 该方法是准备布局,会在cell显示之前调用,可以在该方法中设置布局的一些属性,比如滚动方向,cell之间的水平间距,以及行间距等
 - 也建议在这个方法中做布局的初始化操作,不建议在init方法中初始化,这个时候可能CollectionView还没有创建,官方文档也有明确说明哦
 - 如果重写了该方法,一定要调用父类的prepareLayout
2) 重写   - (NSArray *)layoutAttributesForElementsInRect:(CGRect):rect
 
 - 该方法的返回值是一个存放着rect范围内所有元素的布局属性的数组
 - 数组里面的对象决定了rect范围内所有元素的排布(frame)
 - 里面存放的都是UICollectionViewLayoutAttributes对象,该对象决定了cell的排布样式
 - 一个cell就对应一个UICollectionViewLayoutAttributes对象
 - UICollectionViewLayoutAttributes对象决定了cell的frame
3) 重写   - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
  - 是否允许在里面cell位置改变的时候重新布局
  - 默认是NO,返回YES的话,该方法内部重新会按顺序调用以下2个方法
      **- (void)prepareLayout
      **- (NSArray *)layoutAttributesForElementsInRect:(CGRect):rect

4)重写   - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
 - proposedContentOffset:原本情况下,collectionview停止滚动时最终的偏移量
    **滑动的时候手松开因为惯性并不会立即停止,会再滚动一会才会真正停止,这个属性就是记录这个真正停止这一刻的偏移量
    **我们这个效果是手指松开,完全停止滚动的时候,离屏幕中间y值最近的cell自动滚动到屏幕的中间
    **所以我们需要利用该方法的返回值,这个返回值就是需要我们给一个偏移量,这个collectionview在它由于惯性滚动结束后,再去多滚动我们给的这一部分偏移量
 - velocity:滚动速率,可以根据velocity的x或y判断它是向上/向下/向右/向左滑动
    **这个参数在这里没有什么用,但是这个参数本身还是非常有用的,我之前使用过它来判断当前tabbleview是向上滑还是向下滑,这个时候可以通过这个判断很简单的就控制是隐藏tabBar或者显示tabBar,或者是隐藏显示导航条,使用很爽
具体实现

复杂的方法说明后,终于迎来了更为枯燥的撸码时刻~~~
1)在ViewController.m文件中

static NSString * const LBPhoto = @"kobe";

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 创建布局
    LBVerLayout *layout = [[LBVerLayout alloc] init];
    layout.itemSize = CGSizeMake(150, 150);
    
    // 创建collectionView
    CGFloat collectionW = self.view.frame.size.width;
    CGFloat collectionH = self.view.frame.size.height;
    CGRect frame = CGRectMake(0, 0, collectionW , collectionH);
    UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:layout];
    collectionView.dataSource = self;
    collectionView.delegate = self;
    collectionView.backgroundColor = [UIColor clearColor];
    [self.view addSubview:collectionView];
    
    // 注册cell,我这里是使用的xib
    [collectionView registerNib:[UINib nibWithNibName:NSStringFromClass([LBPhotoCell class]) bundle:nil] forCellWithReuseIdentifier:LBPhoto];    

}

#pragma mark - <UICollectionViewDataSource>
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return 20;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{ 
    //创建cell
    LBPhotoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:LBPhoto forIndexPath:indexPath];
    //重写imageName的set方法,内部其实就是给cell的imageView赋值(imageView是我们自己给cell添加的子控件)
    cell.imageName = [NSString stringWithFormat:@"kobe_%zd", indexPath.item ];
}

2)在继承自UICollectionViewCell的自定义cell的.m文件中

@interface LBPhotoCell()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation LBPhotoCell

- (void)awakeFromNib {
    //给imageView的图层设置边框宽度以及边框颜色
    self.imageView.layer.borderWidth = 10;
    self.imageView.layer.borderColor = [UIColor blackColor].CGColor;
}

//重写imageName的set方法,外界传一个图片名给我们,我们在cell内部给cell的子控件赋值
- (void)setImageName:(NSString *)imageName
{
    _imageName = [imageName copy];
    self.imageView.image = [UIImage imageNamed:imageName];
}
@end

3)最后一步,最为关键的一步,赋值的计算都是在这个类里面实现的
在继承自系统的UICollectionViewFlowLayout的类的.m文件中

- (void)prepareLayout
{
    [super prepareLayout];
    // 垂直滚动
    self.scrollDirection = UICollectionViewScrollDirectionVertical;
    self.minimumInteritemSpacing = 20;

    // 设置collectionView里面内容的内边距(上、左、下、右)
    CGFloat inset = (self.collectionView.frame.size.width - 2*self.itemSize.width) /3;
    self.sectionInset = UIEdgeInsetsMake(inset, inset, inset, inset);
}

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
    return YES;
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
     // 拿到系统已经帮我们计算好的布局属性数组,然后对其进行拷贝一份,后续用这个新拷贝的数组去操作
    NSArray * originalArray   = [super layoutAttributesForElementsInRect:rect];
    NSArray * curArray = [[NSArray alloc] initWithArray:originalArray copyItems:YES];
    
    // 计算collectionView中心点的y值(这个中心点可不是屏幕的中线点哦,是整个collectionView的,所以是包含在屏幕之外的偏移量的哦)
    CGFloat centerY = self.collectionView.contentOffset.y + self.collectionView.frame.size.height * 0.5;
    
    // 拿到每一个cell的布局属性,在原有布局属性的基础上,进行调整
    for (UICollectionViewLayoutAttributes *attrs in curArray) {
        // cell的中心点y 和 collectionView最中心点的y值 的间距的绝对值
        CGFloat space = ABS(attrs.center.y - centerY);
        
        // 根据间距值 计算 cell的缩放比例
        // 间距越大,cell离屏幕中心点越远,那么缩放的scale值就小
        CGFloat scale = 1 - space / self.collectionView.frame.size.height;
        
        // 设置缩放比例
        attrs.transform = CGAffineTransformMakeScale(scale, scale);
    }
    
    return curArray;
}

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
    // 计算出停止滚动时(不是松手时)最终显示的矩形框
    CGRect rect;
    rect.origin.y = proposedContentOffset.y;
    rect.origin.x = 0;
    rect.size = self.collectionView.frame.size;
    
    // 获得系统已经帮我们计算好的布局属性数组
    NSArray *array = [super layoutAttributesForElementsInRect:rect];
    
    // 计算collectionView最中心点的y值
    // 再啰嗦一下,这个proposedContentOffset是系统帮我们已经计算好的,当我们松手后它惯性完全停止后的偏移量
    CGFloat centerY = proposedContentOffset.y + self.collectionView.frame.size.height * 0.5;
    
    // 当完全停止滚动后,离中点Y值最近的那个cell会通过我们多给出的偏移量回到屏幕最中间
    // 存放最小的间距值
    // 先将间距赋值为最大值,这样可以保证第一次一定可以进入这个if条件,这样可以保证一定能闹到最小间距
    CGFloat minSpace = MAXFLOAT;
    for (UICollectionViewLayoutAttributes *attrs in array) {
        if (ABS(minSpace) > ABS(attrs.center.y - centerY)) {
            minSpace = attrs.center.y - centerY;
        }
    }
    // 修改原有的偏移量
    proposedContentOffset.y += minSpace;
    return proposedContentOffset;
}

点击这里下载代码

(ps:感谢@予人与人,亲自敲了代码发现了我漏掉的一个问题,非常感谢)

结束语
  • OK,这个效果也基本实现了,忙碌的周末也快结束了,明天公司会有新人过来面试,一些不错的面试题有机会的话会继续跟大家分享的
  • 慢慢发现把自己稍微了解的东西分享出去这样非常有助于自己的提升,感觉自己会不是牛逼的,最牛逼的是把自己会的能分享给别人,过程中会发现很多细节
  • 今后会经常性的和大家分享实用有趣的东西,我们共同提升
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容