iOS开发之窥探UICollectionViewController(五) --一款炫酷的图片浏览组件

时间:2021-06-27 08:53:41

本篇博客应该算的上CollectionView的高级应用了,从iOS开发之窥探UICollectionViewController(一)到今天的(五),可谓是由浅入深的窥探了一下UICollectionView的用法,这些用法不仅包括SDK中自带的流式布局(UICollectionViewDelegateFlowLayout)而且介绍了如何根据你的需求去自定义属于你自己的CollectionView。自定义的CollectionView可谓是非常灵活,其灵活性也决定了其功能的强大。CollectionView的自定义就是其Cell高度可定制的属性,通过对Cell赋值不同的属性来达到自定义的目的。

在上篇博客《iOS开发之窥探UICollectionViewController(四) --一款功能强大的自定义瀑布流》中,通过自定义的CollectionView创建了一个可定制的自定义瀑布流,效果还是蛮ok的。本篇博客是使用自定义CollectionView的另一个实例,自定义CollectionView的方式和上一篇是一致的,都是重写UICollectionViewLayout相应的方法,然后再通过委托回调来设置布局的参数。自定义CollectionView的思路是一样的,只是具体的实现方式不同。学习么,要学会举一反三,希望大家能通过这两篇自定义CollectionView的博客来写出属于你自己的自定义效果。

  

一.效果展示

废话少说,进入今天博客的主题,下方就是今天博客中Demo的运行效果。虽然运行效果做成gif丢帧了,看起来有些卡,不过跑起来还是比较流畅的。切换图片时进行一个360度的旋转,并且修改Cell的层级,当前显示的图片层级最高。并且移动时,如果要显示的图片不在屏幕*就做一个位置矫正。点击图片时,使用仿射变换使其放大,再点击使其缩小。接下来将会详细的介绍其实现方案。

iOS开发之窥探UICollectionViewController(五) --一款炫酷的图片浏览组件

  

二.该自定义布局的使用方式

我们先看一下该自定义布局是如何使用的,然后再通过使用方式来逐步介绍它是如何实现的。这也是一个由浅入深的过程,因为用起来要比做起了更容易。比如开汽车容易,造汽车可就麻烦多了。所以在本篇博客的第二部分,将要介绍如何去使用该自定义组件。

其实所有CollectionView的自定义布局的使用方式都是一样的,分为以下几步:

    

1.为我们的CollectionView指定该布局,本篇博客的CollectionView是通过Storyboard来实现的,所以我们可以通过Storyboard来指定自定义的布局文件,如果你是使用纯代码方式,可以在CollectionView实例化时来指定所需的布局。下方是使用Storyboard来指定的布局文件,需要把Layout选项调到Custom下,然后下方的Class选项就是你要关联的自定义布局文件,具体如下所示。代码的就在此不做赘述了,网上一抓一大把。

iOS开发之窥探UICollectionViewController(五) --一款炫酷的图片浏览组件

2.给Storyboard上的CollectionViewController关联一个类,然后我们就可以使用自定义的布局了。获取指定的自定义布局对象,然后指定委托代理对象,如下所示:

 - (void)viewDidLoad {
[super viewDidLoad]; _customeLayout = (CustomTransformCollecionLayout *) self.collectionViewLayout;
_customeLayout.layoutDelegate = self;
}

3.除了实现CollectionView的DataSource和Delegate, 我们还需实现布局的代理方法,该自定义布局要实现的代理方法如下。第一个是设置Cell的大小,也就是宽高。第二个是设置Cell间的边距。

 #pragma mark <CustomTransformCollecionLayoutDelegate>

 - (CGSize)itemSizeWithCollectionView:(UICollectionView *)collectionView
collectionViewLayout:(CustomTransformCollecionLayout *)collectionViewLayout {
return CGSizeMake(, );
} - (CGFloat)marginSizeWithCollectionView:(UICollectionView *)collectionView
collectionViewLayout:(CustomTransformCollecionLayout *)collectionViewLayout {
return 10.0f;
}

4.点击Cell放大和缩小是在UICollectionViewDataSource中点击Cell的代理方法中做的,在此就不做赘述了,详见GitHub上分享的链接。

三. 如何实现

上面介绍了如何去使用该自定义组件,接下来就是“造车”的过程了。本篇博客的第三部分介绍如何去实现这个自定义布局。

1. CustomTransformCollecionLayout头文件中的代码如下所示,该文件中定义了一个协议,协议中的方法就是在CollectionView中要实现的那两个代理方法。这些代理方法提供了Cell的大小和边距。该文件的接口中定义了一个代理对象,当然为了强引用循环,该代理对象是weak类型的。

 //
// CustomTransformCollecionLayout.h
// CustomTransformCollecionLayout
//
// Created by Mr.LuDashi on 15/9/24.
// Copyright (c) 2015年 ZeluLi. All rights reserved.
// #import <UIKit/UIKit.h> #define SCREEN_WIDTH [[UIScreen mainScreen] bounds].size.width
#define SCREEN_HEIGHT [[UIScreen mainScreen] bounds].size.height @class CustomTransformCollecionLayout; @protocol CustomTransformCollecionLayoutDelegate <NSObject>
/**
* 确定cell的大小
*/
- (CGSize) itemSizeWithCollectionView:(UICollectionView *)collectionView
collectionViewLayout:(CustomTransformCollecionLayout *)collectionViewLayout; /**
* 确定cell的大小
*/
- (CGFloat) marginSizeWithCollectionView:(UICollectionView *)collectionView
collectionViewLayout:(CustomTransformCollecionLayout *)collectionViewLayout; @end @interface CustomTransformCollecionLayout : UICollectionViewLayout @property (nonatomic, weak) id<CustomTransformCollecionLayoutDelegate> layoutDelegate; @end

2.接下来介绍一下CustomTransformCollecionLayout实现文件也就是.m中的代码,其中的延展中的属性如下所示。numberOfSections:该参数代表着CollectionView的Section的个数。numberOfCellsInSection:代表着每个Section中Cell的个数。itemSize则是Cell的尺寸(宽高),该属性的值是由布局代理方法提供。itemMargin: 该属性是Cell的边距,它也是通过布局的代理方法提供。itemsX: 用来存储计算的每个Cell的X坐标。

 //
// CustomTransformCollecionLayout.m
// CustomTransformCollecionLayout
//
// Created by Mr.LuDashi on 15/9/24.
// Copyright (c) 2015年 ZeluLi. All rights reserved.
// #import "CustomTransformCollecionLayout.h" @interface CustomTransformCollecionLayout() @property (nonatomic) NSInteger numberOfSections;
@property (nonatomic) NSInteger numberOfCellsInSection;
@property (nonatomic) CGSize itemSize;
@property (nonatomic) CGFloat itemMargin; @property (nonatomic, strong) NSMutableArray *itemsX; @end

3. 在实现中我们需要重写UICollectionViewLayout中相关的方法,需要重写的方法如下:

(1). 预加载布局方法, 该方法会在UICollectionView加载数据时执行一次,在该方法中负责调用一些初始化函数。具体如下所示。

 #pragma mark -- UICollectionViewLayout 重写的方法
- (void)prepareLayout {
[super prepareLayout]; [self initData]; [self initItemsX];
}

(2).下面的方法会返回ContentSize, 说白一些,就是CollectionView滚动区域的大小。

 /**
* 该方法返回CollectionView的ContentSize的大小
*/
- (CGSize)collectionViewContentSize {
CGFloat width = _numberOfCellsInSection * (_itemSize.width + _itemMargin);
return CGSizeMake(width, SCREEN_HEIGHT);
}

(3).下方的方法是为每个Cell绑定一个UICollectionViewLayoutAttributes对象,用来设置每个Cell的属性。

 /**
* 该方法为每个Cell绑定一个Layout属性~
*/
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSMutableArray *array = [NSMutableArray array]; //add cells
for (int i = ; i < _numberOfCellsInSection; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:]; UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath]; [array addObject:attributes];
}
return array;
}

  

(4).下方这个方法是比较重要的,重写这个方法是为了为每个Cell设定不同的属性值。其中transform的值是根据CollectionView的滚动偏移量来计算的,所以在滚动CollectionView时,Cell也会跟着旋转。具体的实现方案在代码中添加了注释,如下所示:

 /**
* 为每个Cell设置attribute
*/
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{ //获取当前Cell的attributes
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; //获取滑动的位移
CGFloat contentOffsetX = self.collectionView.contentOffset.x;
//根据滑动的位移计算当前显示的时第几个Cell
NSInteger currentIndex = [self countIndexWithOffsetX: contentOffsetX];
//获取Cell的X坐标
CGFloat centerX = [_itemsX[indexPath.row] floatValue];
//计算Cell的Y坐标
CGFloat centerY = SCREEN_HEIGHT/; //设置Cell的center和size属性
attributes.center = CGPointMake(centerX, centerY);
attributes.size = CGSizeMake(_itemSize.width, _itemSize.height); //计算当前偏移量(滑动后的位置 - 滑动前的位置)
CGFloat animationDistance = _itemSize.width + _itemMargin;
CGFloat change = contentOffsetX - currentIndex * animationDistance + SCREEN_WIDTH / - _itemSize.width / ; //做一个位置修正,因为当滑动过半时,currentIndex就会加一,就不是上次显示的Cell的索引,所以要减去一做个修正
if (change < ) {
change = contentOffsetX - (currentIndex - ) * animationDistance + SCREEN_WIDTH/ - _itemSize.width/;
} if (currentIndex == && contentOffsetX <= ) {
change = ;
} //旋转量
CGFloat temp = M_PI * * (change / (_itemSize.width + _itemMargin)); //仿射变换 赋值
attributes.transform = CGAffineTransformMakeRotation(temp); //把当前显示的Cell的zIndex设置成较大的值
if (currentIndex == indexPath.row) {
attributes.zIndex = ;
} else {
attributes.zIndex = currentIndex;
} return attributes;
}

(5).要让Cell随着滚动旋转起来,你需要重写下面这个方法,并且返回YES。该方法返回YES意味着当滚动时,会再次执行上面(4)的方法,重新为每个Cell的属性赋值。所以重写下面的方法,并返回YES(下面的表达式也是一样的)才可以运动起来呢。

 //当边界发生改变时,是否应该刷新布局。如果YES则在边界变化(一般是scroll到其他地方)时,将重新计算需要的布局信息。
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return !CGRectEqualToRect(newBounds, self.collectionView.bounds);
}

(6).重写下面的方法是为了修正CollectionView滚动的偏移量,使当前显示的Cell出现在屏幕的中心的位置,方法如下:

 //修正Cell的位置,使当前Cell显示在屏幕的中心
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity{ //计算显示的是第几个Cell
NSInteger index = [self countIndexWithOffsetX:proposedContentOffset.x]; CGFloat centerX = index * (_itemSize.width + _itemMargin) + (_itemSize.width/); proposedContentOffset.x = centerX - SCREEN_WIDTH/; return proposedContentOffset;
}

4.下方就是我自己实现的方法了,也就在重写的方法中调用的函数,具体如下。

 #pragma mark -- 自定义的方法
/**
* 根据滚动便宜量来计算当前显示的时第几个Cell
*/
- (NSInteger) countIndexWithOffsetX: (CGFloat) offsetX{
return (offsetX + (SCREEN_WIDTH / )) / (_itemSize.width + _itemMargin);
} /**
* 初始化私有属性,通过代理获取配置参数
*/
- (void) initData{
_numberOfSections = self.collectionView.numberOfSections; _numberOfCellsInSection = [self.collectionView numberOfItemsInSection:]; _itemSize = [_layoutDelegate itemSizeWithCollectionView:self.collectionView collectionViewLayout:self]; _itemMargin = [_layoutDelegate marginSizeWithCollectionView:self.collectionView collectionViewLayout:self]; } /**
* 计算每个Cell的X坐标
*/
- (void) initItemsX{
_itemsX = [[NSMutableArray alloc] initWithCapacity:_numberOfCellsInSection]; for (int i = ; i < _numberOfCellsInSection; i ++) {
CGFloat tempX = i * (_itemSize.width + _itemMargin) + _itemSize.width/;
[_itemsX addObject:@(tempX)];
} }

至此,Demo的代码讲解完毕,经过上述步骤,你就可以写出上面动画中的自定义效果了,具体代码会在github中进行分享。分享链接如下:

github上Demo的链接地址:https://github.com/lizelu/CustomTransformCollecionLayout

iOS开发之窥探UICollectionViewController(五) --一款炫酷的图片浏览组件的更多相关文章

  1. iOS开发之窥探UICollectionViewController&lpar;四&rpar; --一款功能强大的自定义瀑布流

    在上一篇博客中<iOS开发之窥探UICollectionViewController(三) --使用UICollectionView自定义瀑布流>,自定义瀑布流的列数,Cell的外边距,C ...

  2. iOS开发之窥探UICollectionViewController&lpar;三&rpar; --使用UICollectionView自定义瀑布流

    上篇博客的实例是自带的UICollectionViewDelegateFlowLayout布局基础上来做的Demo, 详情请看<iOS开发之窥探UICollectionViewControlle ...

  3. iOS开发之窥探UICollectionViewController&lpar;一&rpar; -- Ready Your CollectionViewController

    之前用CollectionViewController只是皮毛,一些iOS从入门到精通的书上也是泛泛而谈.这几天好好的搞了搞苹果的开发文档上CollectionViewController的内容,亲身 ...

  4. iOS开发之窥探UICollectionViewController&lpar;二&rpar; --详解CollectionView各种回调

    UICollectionView的布局是可以自己定义的,在这篇博客中先在上篇博客的基础上进行扩充,我们先使用UICollectionViewFlowLayout,然后好好的介绍一下UICollecti ...

  5. iOS开发Swift篇—(五)元组类型

    iOS开发Swift篇—(五)元组类型 一.元组类型介绍 1.什么是元组类型 元组类型由 N个 任意类型的数据组成(N >= 0),组成元组类型的数据可以称为“元素” 示例: let posit ...

  6. 一款炫酷Loading动画--载入成功

    简单介绍 昨天在简书上看到一篇文章.介绍了一个载入动画的实现过程 一款Loading动画的实现思路(一) 仅仅可惜原动画是IOS上制作的.而看了一下.作者的实现思路比較复杂,于是趁着空暇写了一个And ...

  7. 一款炫酷Loading动画--载入失败

    简单介绍 上一篇文章一款炫酷Loading动画–载入成功.给大家介绍了成功动画的绘制过程,这篇文章将接着介绍载入失败特效的制作. 相比成功动画,有了前面的经验,失败动画的过程就显得比較简单了. 动画结 ...

  8. iOS 开发之模糊效果的五种实现

    前言 在iOS开发中我们经常会用到模糊效果使我们的界面更加美观,而iOS本身也提供了几种达到模糊效果的API,如:Core Image,使用Accelerate.Framework中的vImage A ...

  9. 一款炫酷的幻灯片播放框架介绍(附demo及使用方法)

    废话不多说,先上demo(建议在chrome下打开 F键全屏 esc退出全屏): 我的demo-博客园简介 官网demo 更多demo 今天为大家介绍一款基于css3和JavaScript的幻灯片播放 ...

随机推荐

  1. css的小问题总结

    1.居中问题 比如让宽度为60%的<div class="box">居中,可以再.box里面设置margin:auto 2.高度固定的div里面有两个子类div且高度和 ...

  2. CSRF攻击原理以及防御

    一.CSRF是什么? CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSR ...

  3. Tips about Object-oriented programming

    1, Return subinterface For example, we have a parent interface: public interface A<T extends A&lt ...

  4. 2&period;2&lowbar;线性表的顺序存储结构&lowbar;参考集合ArrayList

    [线性表的顺序存储从结构] 指的是用一段连续的存储单元一次储存线性表的数据元素. [线性表的顺序存储的结构代码 C语言版] #define MAXSIZE 20 /*存储空间初始分配量*/ typed ...

  5. WinForm 文件操作

    文件及文件夹操作 C/S:WinForm可以操作客户端文件 Client ServerB/S:浏览器服务 Brower Server 命名空间:using system .IO; 1. File类:文 ...

  6. js常用正则表达式表单验证代码

    方法一:  var re=/正则表达式/;  re.test($("txtid").val())  方法二:  $("txtid").val.match(/正则 ...

  7. android模拟器访问PC本地接口

    一般来讲PC本地接口是localhost:8080 而在安卓模拟器上用的话,他会映射模拟器本身的,也就是说,可以把模拟器也当成一个PC端来看待,这样会好理解点吧 而在模拟器上想要访问PC本地的loca ...

  8. PAT L2-020 功夫传人

    https://pintia.cn/problem-sets/994805046380707840/problems/994805059118809088 一门武功能否传承久远并被发扬光大,是要看缘分 ...

  9. 解决因为本地代码和远程代码冲突,导致git pull无法拉取远程代码的问题

    一.问题 当本地代码和远程代码有冲突的时候,执行git pull操作的时候,会提示有冲突,然后直接终止本次pull,查了些资料没有找到强制pull的方式,但是可以使用如下方式解决. 二.解决思路 可以 ...

  10. c&num; 键值对的方式post提交

    DataContractJsonSerializer jsQcData = new DataContractJsonSerializer(typeof(DATA<data>));//DAT ...