步驟思路
1.寫出基本的colletionView
垄开,讓他進行水平滑動恰响,一個高度只能顯示一個cell
2.了解基本的UICollectionViewLayoutAttributes
屬性,讓他可以改變一些cell的屬性芙扎,例如aphelia星岗,和縮小和放大
3.了解一個方法,當(dāng)我們rect
改變的時候戒洼,會判斷是否刷新cell的布局
4.判斷哪個cell離colleview的中點最近俏橘,了解一個target函數(shù)
4.1使用剛才rect方法,調(diào)用super方法圈浇,獲取的是計算好餓cell的中心點敷矫,所以知道了他的中心點,如果使用的是self汉额,那么會在調(diào)用一次曹仗,比較麻煩
4.2 計算collviontView
的中心點值(注意,不能像過去的那個計算 過去:偏移量(手松開的哪一個可)+寬度的一般),此時拿到的偏移量事不準(zhǔn)的蠕搜,因為我們刺客還有速度怎茫,應(yīng)該拿到最后的偏移量,也就是propertyTargetPoint,將要去哪里9旄颉C巯堋!
4.3祥山,要知道手松開的那一刻圃验,的offSet是不準(zhǔn)的,應(yīng)該獲取最終的缝呕,將要去的位置澳窑,所以傳遞rect是不能下傳遞,而是將來結(jié)束的時候供常,rect摊聋,用x最難判斷,就是propertyPointOffset.x
4.4 遍歷布局屬性栈暇,獲取那個距離最短麻裁,所有的都偏移這段多
4.5 明確,其實最后的所有的偏移量源祈,= 最小的間距 + 目標(biāo)偏移量煎源!但是“最小的間距”可能是正負(fù)數(shù)!香缺!
5.給cell的初始化和結(jié)束設(shè)置一個sectionInset
6.了解prepare
函數(shù)的使用
1.寫出基本的colletionView手销,讓他進行水平滑動,一個高度只能顯示一個cell
直接去寫一個
colletionView
放在vc上就好赫悄,保證colletionView上只有一行原献,設(shè)置數(shù)據(jù)源和代理方法,布局對象直接使用系統(tǒng)的流水布局
2.了解基本的UICollectionViewLayoutAttributes
屬性
每個cell都有尺寸鄙陡,位置和aplha值等等,其實每個cell都有一個
UICollectionViewLayoutAttributes
屬性躏啰,這里有cell所有的信息趁矾,包括剛剛說的三個,還有很多~~
/**
解釋類:UICollectionViewLayoutAttributes
* 每一個cell的尺寸给僵,位置等各種屬性都對應(yīng)這個一個UICollectionViewLayoutAttributes毫捣,這個類中含有很多自己的屬性详拙,改變這個屬性,那么cell的大小或者位置也會發(fā)生變化.還有`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這個控件
*/
想寫出本文的目標(biāo)效果蔓同,我們必須要重寫布局饶辙,自定義一個布局,但是繼承那個比較好斑粱,有兩個選擇弃揽,
- 一個是抽象類
UICollectionViewLayout
,繼承這個则北,那么我們滑動都不行矿微,要重寫的東西特別多,特別費事咒锻,不建議 - 還有就是
UICollectionViewFlowLayout
這個蘋果已經(jīng)給我計算好了很多的東西冷冗,可以拿來就用守屉,改改基本的某個屬性就行
2.1 自定義一個layout布局SFLayout
直接在項目中將系統(tǒng)的替換惑艇,效果應(yīng)該是一樣的~
2.2 func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]?
這個函數(shù)是干啥的?
剛才都說了拇泛,每個cell都有一個
UICollectionViewLayoutAttributes
屬性滨巴,這個函數(shù)的意思是,在rect
范圍之內(nèi)的cell的屬性放到一個數(shù)組中俺叭,傳遞出來恭取,一定要調(diào)用super,然后用一個數(shù)組承接熄守,為什么用super蜈垮?因為super是流水布局,返回來的數(shù)組是計算好的,可以微調(diào)使用
/**
打印出當(dāng)前rect之內(nèi)的所有cell的“布局屬性”- UICollectionViewLayoutAttributes(返回的是一個數(shù)組)
*/
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
//獲得super已經(jīng)計算好的尺寸
let array = super.layoutAttributesForElementsInRect(rect)
//對于每個屬性進行尺寸裕照,位置的微調(diào)
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.了解一個方法攒发,當(dāng)我們rect
改變的時候,會判斷是否刷新cell的布局
3.1 我們看到的這些item都沒有變化~
我們滑動的時候晋南,item一直沒有發(fā)生變化惠猿,但是我想調(diào)用
layoutAttributesForElementsInRect(默認(rèn)之調(diào)用一次)
刷新一下內(nèi)部,如何處理负间?
重寫一個方法,只有滑動colletionView偶妖,rect發(fā)生了變化,就會調(diào)用布局屬性方法
/**
當(dāng)collectionView顯示的rect發(fā)生了額變化政溃,詢問一下趾访,是否要去刷新所在cell的layoutAttribute,返回true董虱,調(diào)用layoutAttributesForElementsInRect刷新
*/
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
return true
}
3.2 如何讓放大的倍數(shù)會根據(jù)距離來變化?
這個相對來說有點不好理解扼鞋,不過多看兩遍就好了~
1.要注意藍色的是frame,也就是rect
2.綠色的是contentSize我們現(xiàn)在的size肯定是大于frame的
3.黑色的永遠在frame的中點,挺好了,是frame的中點藏鹊,隨著content offset的改變(綠色的view一直前進或者后退)润讥,黑線還在frame 的中點,但是他在綠色的view的x值一直發(fā)生變化
4.紅色的是cell
5.黃色的是cell的中心
思路解析
1.黑色線的位置如何計算盘寡?
黑線.x = contentOffset.x + frame.size.width*0.5
2.黃線如何計算楚殿?
attir.center.x
3.如何就算黃線和黑線的間距?
用間距絕對值就行 abs(黑線.x - atria.center.x)
4.如何根據(jù)絕對值改變cell的大小竿痰?
比例 = 間距絕對值/frame.width 脆粥,除以寬度,是隨便取得數(shù)據(jù)影涉,可以是任意的变隔,但是一定要大于間距的絕對值,我們要比例一定是(0,1)之間的蟹倾,但是隨著間距的變大匣缘,那么比例越來越大啊,咋整鲜棠,只要用真實比例 =(1-比例)
就行肌厨,也可以用1.2- 比例
這個隨意,看具體的效果就好了豁陆,根據(jù)你們的要求柑爸,看看到底調(diào)到幾
/**
打印出當(dāng)前rect之內(nèi)的所有cell的“布局屬性”- UICollectionViewLayoutAttributes(返回的是一個數(shù)組)
*/
override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
//獲得super已經(jīng)計算好的尺寸
let array = super.layoutAttributesForElementsInRect(rect)
//對于每個屬性進行尺寸,位置的微調(diào)
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停止應(yīng)該在什么位置的函數(shù)
2.找出rect中那個一個cell里黑線最近
3.獲取那個cell里黑線最近的距離表鳍,可能是正數(shù)也可能是負(fù)數(shù)
4.讓所有的cell都微調(diào) 合適的間距,最終讓那個特定的cell在屏幕中間
/**
當(dāng)結(jié)束后祥诽,cell應(yīng)該停止的位置
:param: proposedContentOffset 打算停止的位置(通過速度計算出來的)
:param: velocity 速度
:returns: 最后停止的位置譬圣,你可能重新給數(shù)據(jù)了,就按照你給的位置停止原押,如果沒有重寫這個方法胁镐,那么就會返回proposedContentOffset這個位置(相對于當(dāng)前的位置)
*/
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
return CGPointZero
}
4.1.當(dāng)結(jié)束后,cell應(yīng)該停止的位置,正常cell停止的位置是
proposedContentOffset
,如果你重寫了诸衔,那么cell停止后的位置就是你返回的位置
4.2 找出rect中那個一個cell里黑線最近
求間距盯漂,還用剛才的那個公式用間距絕對值就行 abs(黑線.x - attri.center.x)
行不行,為什么笨农?
答案:不行就缆,公式中獲取的cell的中心點是可控的,我們知道谒亦,那一時刻竭宰,必須的空郊,但是為什么在本函數(shù)中,我們認(rèn)為他不行切揭,不是準(zhǔn)確的值狞甚?,因為我們快速滑動廓旬,然后松手哼审,他還有速度,根據(jù)慣性孕豹,他還要滑動一會涩盾,所以不能取此時此刻的黃線(cell的中點),所以我們的公式是本函數(shù)返回值 = proposedContentOffset .x + 黃線和黑線最近的間距(可能正也可能負(fù))
首先在本方法中励背,應(yīng)該獲取到rect中的cell的那些屬性(
layoutAttributesForElementsInRect
)春霍,如何獲取那個數(shù)組?
調(diào)用super.layoutAttributesForElementsInRect
方法叶眉,可以輕松獲取的是計算好的cell的中心點址儒。
如果使用的是self.layoutAttributesForElementsInRect
,獲取的數(shù)據(jù)是經(jīng)過我們計算的竟闪,我們此時要拿到未經(jīng)過計算的attir數(shù)組
//獲取將要顯示rect里面的cell屬性
let attrs = super.layoutAttributesForElementsInRect(rect)
獲取完了數(shù)組离福,我們拿數(shù)組中的對象和中線比較一下杖狼,看看那個是最近的炼蛤,做一個記錄,然后讓所有的返回都是
本函數(shù)返回值 = proposedContentOffset .x + 黃線和黑線最近的間距(可能正也可能負(fù))
/**
當(dāng)結(jié)束后蝶涩,cell應(yīng)該停止的位置
:param: proposedContentOffset 打算停止的位置(通過速度計算出來的)
:param: velocity 速度
:returns: 最后停止的位置理朋,你可能重新給數(shù)據(jù)了,就按照你給的位置停止绿聘,如果沒有重寫這個方法嗽上,那么就會返回proposedContentOffset這個位置(相對于當(dāng)前的位置)
*/
override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
// print(proposedContentOffset)
//1.獲取所有的布局屬性,調(diào)用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可能是正負(fù))
var currentOffset = proposedContentOffset
currentOffset.x += CGFloat(margin)
return currentOffset
}
4.3 計算
collviontView
的中心點值(注意熄攘,不能像過去的那個計算 過去:偏移量(手松開的哪一個可)+寬度的一般),此時拿到的偏移量事不準(zhǔn)的兽愤,因為我們刺客還有速度,應(yīng)該拿到最后的偏移量挪圾,也就是propertyTargetPoint浅萧,將要去哪里!U芩肌洼畅!
4.3,要知道手松開的那一刻棚赔,的offSet是不準(zhǔn)的帝簇,應(yīng)該獲取最終的徘郭,將要去的位置,所以傳遞rect是不能下傳遞丧肴,而是將來結(jié)束的時候残揉,rect,用x最難判斷芋浮,就是propertyPointOffset.x
4.4 遍歷布局屬性冲甘,獲取那個距離最短,所有的都偏移這段多
4.5 明確途样,其實最后的所有的偏移量江醇,= 最小的間距 + 目標(biāo)偏移量!但是“最小的間距”可能是正負(fù)數(shù):蜗尽陶夜!
5.給cell的初始化和結(jié)束設(shè)置一個sectionInset
,了解prepare
函數(shù)的使用
基本都講完了,但是剛剛啟動程序的時候裆站,0號cell里左邊太近了条辟,不好看,我們想第一個cell和最后一個cell都在屏幕中間宏胯,怎么辦羽嫡?
給他sectionInset設(shè)置數(shù)據(jù)就好了,組間距
/**
用來做布局的初始化肩袍,不建議在init中調(diào)用杭棵,因為那時候的colletionView = nil
*/
override func prepareLayout() {
super.prepareLayout()
//設(shè)置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)