在網(wǎng)上看到別人用collectionView寫的圖片瀏覽器,可以縮放和定位功能,因此深入學習了一下。
collectionView的系統(tǒng)的流水布局已經(jīng)可以滿足日常需要,但是如果你需要給滾動的時候加上動畫,還是需要自定義流水布局。
如何自定義流水布局
1.需要重寫的方法
自定義流水布局只需要新建一個類,繼承自UICollectionViewFlowLayout,然后重寫它的系統(tǒng)方法。
和定制圖片瀏覽器效果有關的5個系統(tǒng)方法:
- (void)prepareLayout;
這個方法是在collectionView第一次顯示或者刷新的時候會調(diào)用,默認這個方法是不做任何事情的,子類可以重寫它來進行數(shù)據(jù)結構的設置或者計算任何需要執(zhí)行的布局后的初始計算。- (CGSize)collectionViewContentSize;
這個方法是用來計算collectionView的滾動范圍,可以重寫,也可以不重寫。- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;
這個方法的意思是你提供一個區(qū)域,然后返回這個區(qū)域包含的cell的屬性。- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;
這個方法意思是在滾動時候是否可以重新布局,默認是NO,如果想要滾動時候產(chǎn)生縮放效果則必須設置為yes,這里invalidate意思是刷新。- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity;
這個方法是獲得最終滾動的位置,因為在滾動時候有一個速率,蘋果內(nèi)部根據(jù)速率來計算緩沖,因此你快速滑動,會向后緩沖一段距離。
2.開始實現(xiàn)滾動時候縮放效果
2.1 實現(xiàn)滾動時候可以設置布局
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
2.2 在滾動的過程中對圖片進行縮放
離屏幕中心點越近的圖片越大,離中心點越遠的圖片越小,這邊只需要拿到具體的cell改變他的transform
屬性就可以。具體如何實現(xiàn)呢,需要以下的思路:
- 先確定一個
rect
來獲得這個rect的cell,rect的寬度和高度不用說肯定是collectionView的寬度和高度,但是坐標點是在變換的,因為collectionView會滾動,但是通過深入學習bounds
我們可以知道滾動底層實現(xiàn)就是改變bounds,因此滾動時候設置的frame直接取collectionView的bounds就可以了。 - 拿到給定的范圍的cell,這時候我們需要計算cell的縮放的大小,越靠近屏幕中間的則越大,離的越遠則越小,為了設置動態(tài)縮放,我們需要計算cell中心點離屏幕中心點的位置,然后計算比例,在中心點就是正常尺寸,有偏移的就縮小一定程度。
通過上面這個圖我們可以直觀的理解,屏幕顯示的范圍在不停的變化,但是每次變化基本上都會包含2個cell,通過計算cell離中心點的距離,具體的計算就是cell的中心點橫坐標減去collectionView的偏移的x,減去collectionView一半寬度就能夠得到偏移量(要取絕對值,因為可能是負的)。
- 然后計算百分比,這里得到的偏移量是cell的中心點離collectionView中心點距離,因此與collectionView的寬度一半進行相除,如果是0,則意味著2個中心點重合,這時候的縮放比例應該是最大的,如果為1則是最小,是負相關,因此用1來減去得到正相關,這樣當在中心點位置就是1,距離最遠就是0.但我們也不希望離中心點位置遠的縮小為0,比中心點的小一部分尺寸就可以了,所以
cellOffset / (self.collectionView.frame.size.width * 0.5) * 0.3
這樣離中心點距離為半個collectionView寬度的cell尺寸縮小0.7
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
// ----1.先獲取當前collectionView所在區(qū)域的cell
NSArray *attrs = [super layoutAttributesForElementsInRect:self.collectionView.bounds];
for (UICollectionViewLayoutAttributes *cellAttr in attrs) {
// ----獲取偏移量(取絕對值)
CGFloat cellOffset = fabs(cellAttr.center.x - self.collectionView.contentOffset.x - self.collectionView.frame.size.width * 0.5);
// ----計算百分比
CGFloat perCent = 1 - cellOffset / (self.collectionView.frame.size.width * 0.5) * 0.3;
cellAttr.transform = CGAffineTransformMakeScale(perCent, perCent);
}
2.3 設置滾動結束的停留位置
在滾動之后,最好的用戶體驗是自動停留在距離中心點最近的圖片顯示在中間,這樣就需要用到了- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
這個方法,我們來分析下如何處理:
- 因為我們知道有個緩沖,所以手指滑動時候,collectionView的偏移量和最終停留位置的坐標點不一致,這個方法返回的就是最終停留的坐標點,因此可以先調(diào)用super的這個方法,獲得到最終停留點。
- 根據(jù)最終的滾動停止的坐標,就可以得出最后滾動停止之后的區(qū)域,然后拿到該區(qū)域的cell這里要調(diào)用super的方法,因為我們已經(jīng)重寫了當前類的這個方法;
- 計算cell誰離中心點最近,然后把該cell設置為屏幕中心,因此要取出最小的的距離點。這里比較值的時候可能會有負的,因此要去絕對值
- 然后用最終的偏移量加上這個距離中心點的距離,這樣可以設置距離最近的圖片顯示在中信。通過打印發(fā)現(xiàn)可能會出現(xiàn)-0這種情況,導致卡頓,需要做個判斷
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{
CGPoint targetP = [super targetContentOffsetForProposedContentOffset:proposedContentOffset withScrollingVelocity:velocity];
CGFloat collectionW = self.collectionView.frame.size.width;
CGFloat collectionH = self.collectionView.frame.size.height;
CGRect frame = CGRectMake(targetP.x, 0, collectionW, collectionH);
NSArray *attrs = [super layoutAttributesForElementsInRect:frame];
CGFloat minOffSetX = MAXFLOAT;
for (UICollectionViewLayoutAttributes *attr in attrs) {
// ----這里要減去最終偏移量的x!!!
CGFloat cellOffset = attr.center.x - targetP.x - collectionW * 0.5;
if (fabs(cellOffset) < fabs(minOffSetX)) {
minOffSetX = cellOffset;
}
}
targetP.x += minOffSetX;
// ----拖動時候可能會卡頓,通過打印發(fā)現(xiàn)有負0,這種情況,因此做個判斷
if (targetP.x < 0) {
targetP.x = 0;
}
return targetP;
}