步骤思路
1.写出基本的colletionView
,让他进行水平滑动,一个高度只能显示一个cell
2.了解基本的UICollectionViewLayoutAttributes
属性,让他可以改变一些cell的属性,例如aphelia,和缩小和放大
3.了解一个方法,当我们rect
改变的时候,会判断是否刷新cell的布局
4.判断哪个cell离colleview的中点最近,了解一个target函数
4.1使用刚才rect方法,调用super方法,获取的是计算好饿cell的中心点,所以知道了他的中心点,如果使用的是self,那么会在调用一次,比较麻烦
4.2 计算collviontView
的中心点值(注意,不能像过去的那个计算 过去:偏移量(手松开的哪一个可)+宽度的一般),此时拿到的偏移量事不准的,因为我们刺客还有速度,应该拿到最后的偏移量,也就是propertyTargetPoint,将要去哪里!!!
4.3,要知道手松开的那一刻,的offSet是不准的,应该获取最终的,将要去的位置,所以传递rect是不能下传递,而是将来结束的时候,rect,用x最难判断,就是propertyPointOffset.x
4.4 遍历布局属性,获取那个距离最短,所有的都偏移这段多
4.5 明确,其实最后的所有的偏移量,= 最小的间距 + 目标偏移量!但是“最小的间距”可能是正负数!!
5.给cell的初始化和结束设置一个sectionInset
6.了解prepare
函数的使用
1.写出基本的colletionView,让他进行水平滑动,一个高度只能显示一个cell
直接去写一个
colletionView
放在vc上就好,保证colletionView上只有一行,设置数据源和代理方法,布局对象直接使用系统的流水布局
2.了解基本的UICollectionViewLayoutAttributes
属性
每个cell都有尺寸,位置和aplha值等等,其实每个cell都有一个
UICollectionViewLayoutAttributes
属性,这里有cell所有的信息,包括刚刚说的三个,还有很多~~
/**
解释类:UICollectionViewLayoutAttributes
* 每一个cell的尺寸,位置等各种属性都对应这个一个UICollectionViewLayoutAttributes,这个类中含有很多自己的属性,改变这个属性,那么cell的大小或者位置也会发生变化.还有`transform `可以更改他的形变等~
@property (nonatomic) CGRect frame;
@property (nonatomic) CGPoint center;
@property (nonatomic) CGSize size;
@property (nonatomic) CATransform3D transform3D;
@property (nonatomic) CGRect bounds NS_AVAILABLE_IOS(7_0);
@property (nonatomic) CGAffineTransform transform NS_AVAILABLE_IOS(7_0);
@property (nonatomic) CGFloat alpha;
@property (nonatomic) NSInteger zIndex; // default is 0
@property (nonatomic, getter=isHidden) BOOL hidden; // As an optimization, UICollectionView might not create a view for items whose hidden attribute is YES
@property (nonatomic, strong) NSIndexPath *indexPath;
在这里还能拿到collectionView这个控件
*/
想写出本文的目标效果,我们必须要重写布局,自定义一个布局,但是继承那个比较好,有两个选择,
- 一个是抽象类
UICollectionViewLayout
,继承这个,那么我们滑动都不行,要重写的东西特别多,特别费事,不建议 - 还有就是
UICollectionViewFlowLayout
这个苹果已经给我计算好了很多的东西,可以拿来就用,改改基本的某个属性就行
2.1 自定义一个layout布局SFLayout
直接在项目中将系统的替换,效果应该是一样的~
2.2 func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]?
这个函数是干啥的?
刚才都说了,每个cell都有一个
UICollectionViewLayoutAttributes
属性,这个函数的意思是,在rect
范围之内的cell的属性放到一个数组中,传递出来,一定要调用super,然后用一个数组承接,为什么用super?因为super是流水布局,返回来的数组是计算好的,可以微调使用
/**
打印出当前rect之内的所有cell的“布局属性”- UICollectionViewLayoutAttributes(返回的是一个数组)
*/
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
//获得super已经计算好的尺寸
let array = super.layoutAttributesForElementsInRect(rect)
//对于每个属性进行尺寸,位置的微调
for index in 0 ..< array!.count
{
let abc:UICollectionViewLayoutAttributes = array![index]
// 1.改变allah值
// abc.alpha = 0.2
//2.改变了大小
// let scale:CGFloat = CGFloat(arc4random_uniform(345))/345.0
// abc.transform = CGAffineTransformMakeScale(scale, scale)
}
return array
3.了解一个方法,当我们rect
改变的时候,会判断是否刷新cell的布局
3.1 我们看到的这些item都没有变化~
我们滑动的时候,item一直没有发生变化,但是我想调用
layoutAttributesForElementsInRect(默认之调用一次)
刷新一下内部,如何处理?
重写一个方法,只有滑动colletionView,rect发生了变化,就会调用布局属性方法
/**
当collectionView显示的rect发生了额变化,询问一下,是否要去刷新所在cell的layoutAttribute,返回true,调用layoutAttributesForElementsInRect刷新
*/
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
return true
}
3.2 如何让放大的倍数会根据距离来变化?
这个相对来说有点不好理解,不过多看两遍就好了~
1.要注意蓝色的是frame,也就是rect
2.绿色的是contentSize我们现在的size肯定是大于frame的
3.黑色的永远在frame的中点,挺好了,是frame的中点,随着content offset的改变(绿色的view一直前进或者后退),黑线还在frame 的中点,但是他在绿色的view的x值一直发生变化
4.红色的是cell
5.黄色的是cell的中心
思路解析
1.黑色线的位置如何计算?
黑线.x = contentOffset.x + frame.size.width*0.5
2.黄线如何计算?
attir.center.x
3.如何就算黄线和黑线的间距?
用间距绝对值就行 abs(黑线.x - atria.center.x)
4.如何根据绝对值改变cell的大小?
比例 = 间距绝对值/frame.width ,除以宽度,是随便取得数据,可以是任意的,但是一定要大于间距的绝对值,我们要比例一定是(0,1)之间的,但是随着间距的变大,那么比例越来越大啊,咋整,只要用真实比例 =(1-比例)
就行,也可以用1.2- 比例
这个随意,看具体的效果就好了,根据你们的要求,看看到底调到几
/**
打印出当前rect之内的所有cell的“布局属性”- UICollectionViewLayoutAttributes(返回的是一个数组)
*/
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
//获得super已经计算好的尺寸
let array = super.layoutAttributesForElementsInRect(rect)
//对于每个属性进行尺寸,位置的微调
for index in 0 ..< array!.count
{
let abc:UICollectionViewLayoutAttributes = array![index]
//1.获取collctionView最中间的线的X值
let collectionViewX:CGFloat = (collectionView?.contentOffset.x)! + CGFloat((collectionView?.frame.width)!)*0.5
//2.获取cell的中心点位置
let cellX = abc.center.x
let scale = abs(cellX - collectionViewX)/(collectionView?.frame.width)!
let trueScale = 1-scale
abc.transform = CGAffineTransformMakeScale(trueScale, trueScale)
}
return array
}
4.判断哪个cell离colleview的中点最近
刚才写完了如何将cell按照比例放大和缩小,还有就是如何放置到中间
思路
1.重写过去cell停止应该在什么位置的函数
2.找出rect中那个一个cell里黑线最近
3.获取那个cell里黑线最近的距离,可能是正数也可能是负数
4.让所有的cell都微调 合适的间距,最终让那个特定的cell在屏幕中间
/**
当结束后,cell应该停止的位置
:param: proposedContentOffset 打算停止的位置(通过速度计算出来的)
:param: velocity 速度
:returns: 最后停止的位置,你可能重新给数据了,就按照你给的位置停止,如果没有重写这个方法,那么就会返回proposedContentOffset这个位置(相对于当前的位置)
*/
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
return CGPointZero
}
4.1.当结束后,cell应该停止的位置,正常cell停止的位置是
proposedContentOffset
,如果你重写了,那么cell停止后的位置就是你返回的位置
4.2 找出rect中那个一个cell里黑线最近
求间距,还用刚才的那个公式用间距绝对值就行 abs(黑线.x - attri.center.x)
行不行,为什么?
答案:不行,公式中获取的cell的中心点是可控的,我们知道,那一时刻,必须的,但是为什么在本函数中,我们认为他不行,不是准确的值?,因为我们快速滑动,然后松手,他还有速度,根据惯性,他还要滑动一会,所以不能取此时此刻的黄线(cell的中点),所以我们的公式是本函数返回值 = proposedContentOffset .x + 黄线和黑线最近的间距(可能正也可能负)
首先在本方法中,应该获取到rect中的cell的那些属性(
layoutAttributesForElementsInRect
),如何获取那个数组?
调用super.layoutAttributesForElementsInRect
方法,可以轻松获取的是计算好的cell的中心点。
如果使用的是self.layoutAttributesForElementsInRect
,获取的数据是经过我们计算的,我们此时要拿到未经过计算的attir数组
//获取将要显示rect里面的cell属性
let attrs = super.layoutAttributesForElementsInRect(rect)
获取完了数组,我们拿数组中的对象和中线比较一下,看看那个是最近的,做一个记录,然后让所有的返回都是
本函数返回值 = proposedContentOffset .x + 黄线和黑线最近的间距(可能正也可能负)
/**
当结束后,cell应该停止的位置
:param: proposedContentOffset 打算停止的位置(通过速度计算出来的)
:param: velocity 速度
:returns: 最后停止的位置,你可能重新给数据了,就按照你给的位置停止,如果没有重写这个方法,那么就会返回proposedContentOffset这个位置(相对于当前的位置)
*/
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
// print(proposedContentOffset)
//1.获取所有的布局属性,调用super的方法获取
//获取将要显示的rect值
let rX:CGFloat = proposedContentOffset.x
let rect = CGRectMake(rX, 0, (collectionView?.frame.width)!, (collectionView?.frame.height)!)
//获取将要显示rect里面的cell属性
let attrs = super.layoutAttributesForElementsInRect(rect)
//计算colletionView中间哪个线的x
let centX:CGFloat = proposedContentOffset.x + (collectionView?.frame.width)!*0.5
//保存最小的间距
var margin = MAXFLOAT
//2.遍历属性,获取最小的间距
for index in 0 ..< (attrs?.count)! {
let att = attrs![index]
let op = Float(att.center.x - centX)
if abs(margin) > abs(op)
{
margin = Float(att.center.x - centX)
}
}
//所有的cell都要偏移量 = proposedContentOffset.x + marign(margin可能是正负)
var currentOffset = proposedContentOffset
currentOffset.x += CGFloat(margin)
return currentOffset
}
4.3 计算
collviontView
的中心点值(注意,不能像过去的那个计算 过去:偏移量(手松开的哪一个可)+宽度的一般),此时拿到的偏移量事不准的,因为我们刺客还有速度,应该拿到最后的偏移量,也就是propertyTargetPoint,将要去哪里!!!
4.3,要知道手松开的那一刻,的offSet是不准的,应该获取最终的,将要去的位置,所以传递rect是不能下传递,而是将来结束的时候,rect,用x最难判断,就是propertyPointOffset.x
4.4 遍历布局属性,获取那个距离最短,所有的都偏移这段多
4.5 明确,其实最后的所有的偏移量,= 最小的间距 + 目标偏移量!但是“最小的间距”可能是正负数!!
5.给cell的初始化和结束设置一个sectionInset
,了解prepare
函数的使用
基本都讲完了,但是刚刚启动程序的时候,0号cell里左边太近了,不好看,我们想第一个cell和最后一个cell都在屏幕中间,怎么办?
给他sectionInset设置数据就好了,组间距
/**
用来做布局的初始化,不建议在init中调用,因为那时候的colletionView = nil
*/
override func prepareLayout() {
super.prepareLayout()
//设置sectionInset
let magin:CGFloat = ((collectionView?.frame.width)! - itemSize.width) * 0.5
sectionInset = UIEdgeInsetsMake(0, magin, 0, magin)
}
6.自顶一个有imageView的cell就好了~
同xib加载的,所以注册的时候有点不同,是这样的
//注册cell
collectionView.registerNib(UINib.init(nibName: "SFImageCell", bundle: nil),
forCellWithReuseIdentifier: sfCellIdent)