iOS 利用UICollectionView做一個無限循環廣告欄
一、效果圖
左右絲滑滑動,並且有縮放動畫。
二、分析和思路
1. 為什麽選擇用UICollectionView去做上面的效果?
首先無限效果永遠是表現出來的,而不是程序裏面創建了無數個view,如何做到無限效果的視覺差這本身就是一個技術活。
以我的知識水平,可以做無限效果的有三種方式:
1). 三個view + 滑動手勢。原理圖如下:
mid下面的承載view為工作區,負責添加滑動手勢和根據手勢滑動距離去修改left,mid,right三個view的位置和狀態。當手勢滑動結束的時候,需要在關閉隱式動畫的基礎上修改三個頁面顯示的內容並且重置三個view的位置和狀態。
優點:邏輯簡單,三個view的切換也不費事,比較的節省資源,因為是手勢控制的,所以落點位置都比較好定位;
缺點:只支持全景展示,不能做到一個顯示框中顯示三個item(否則就要用超過3個view的了),另外如果要實現手勢的快滑和慢滑則又是另外一番努力了。
2). UIScrollView:
UIScrollView 就是在 1) 的基礎上去掉自己添加的手勢,用UIScrollView作為控制載體。而且UIScrollView提供了良好的滑動代理,有大量的api可以使用,只要用心是可以做出很多酷炫的效果。
當然了,問題也很明顯,代碼控制復雜;做無限滾動的時候,到底創建多少個item比較合適?
3). UICollectionView:
用UICollectionView最大原因就是cell的重用規則,可以讓你不去關心到底創建多少個item。而且UICollectionView是可以左右滑的。原理大約如下:
滑動表現出來的無限只是因為在滑動停止後,重置了section。比如你想要sectionCount個section,section裏面的rows就是廣告的個數,初始狀態下,一定是讓屏幕中央顯示的是indexPath為(sectionCount/2 ,0)的item,這樣當向前滑動和向後滑動的時候就不會出問題,為了在快滑過程中更加絲滑柔順,盡量的讓sectionCount大點,,因為cell的重用機制讓你可以不考慮這兒的內存消耗問題,我這兒給的是100。
在滑動動畫結束之後,需要關閉隱式動畫條件下重置當前的滑動位置為indexPath=(sectionCount/2,currentRow),時機我選擇在NSTimer的處理函數裏面。
到這兒基本上把無限循環的邏輯講完了,再說一下item的縮放效果實現。
從最開始給的效果圖可以很容易得到一個結論:距離中心點位置越大,縮放比越大,屏幕中心點的縮放比最小為0,所以我們需要獲取當前點到屏幕中心的distance;
這個也是比較簡單的,在 UICollectionViewFlowLayout 回調函數
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
中可以輕松拿到,代碼如下:
CGRect frame = CGRectZero; frame.origin = self.collectionView.contentOffset; frame.size = self.collectionView.size; for (UICollectionViewLayoutAttributes *attribute in array) { //確立cell相對於屏幕中央的距離 CGFloat distance = CGRectGetMidX(frame) - attribute.center.x; //公式一 }
為了計算出來下一步的縮放比,我們這兒需要約定一個參考寬度activeDistance,即,當前distance等於activeDistance時,縮放比最小到0,(離得越近,顯示的越小,很符合人眼的觀察習慣),為了更好的理解參考寬度,我們可以這樣約定,距離中心距離為activeDistance,縮放比為scaleFactor,這樣縮放比的計算公式為:
CGFloat scale = 1 - (distance/activeDistance)*scaleFactor; 公式二
activeDistance和scaleFactor的值需要自己多次嘗試,修改,我這兒給個建議值:
activeDistance = 300.0;
scaleFactor = 0.05;
最後把求出來的scale賦值給cell的attribute,完整代碼如:
1 - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect { 2 3 NSArray *array = [super layoutAttributesForElementsInRect:rect]; 4 5 CGRect frame = CGRectZero; 6 frame.origin = self.collectionView.contentOffset; 7 frame.size = self.collectionView.size; 8 9 for (UICollectionViewLayoutAttributes *attribute in array) { 10 //確立cell相對於屏幕中央的距離 11 CGFloat distance = CGRectGetMidX(frame) - attribute.center.x; 12 13 //到中心位置的相對於x的比例,原則就是越近的越大,越遠的越小。 14 CGFloat normalDistance = fabs(distance / self.activeDistance); 15 16 CGFloat scale = 1 - self.scaleFactor * normalDistance; 17 //屬性賦值 18 attribute.transform3D = CATransform3DMakeScale(scale,scale, 1); 19 } 20 return array; 21 }
到這兒基本上就完了,除了最後一步,因為你把上述代碼運行起來的時候就會發現,可以縮放,可以無限滾動,但是每次滾動之後item都不會自己跑到屏幕中央,這就需要修改回調
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
這的邏輯很簡單,在滑動結束後,屏幕中可能存在N個cell,從這N個cell中選擇出離屏幕中央最近的cell,把選擇出來的這個cell居中顯示,就可以了。具體代碼如下:
1 // 確定最終滾到的位置 2 - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity { 3 4 CGRect targetRect = CGRectMake(proposedContentOffset.x, proposedContentOffset.y, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height); 5 6 NSArray *array = [super layoutAttributesForElementsInRect:targetRect]; 7 8 CGFloat horizontalCenterX = proposedContentOffset.x + self.collectionView.bounds.size.width / 2.; 9 CGFloat offsetAdjustment = CGFLOAT_MAX; 10 for (UICollectionViewLayoutAttributes *attribute in array) { 11 // 12 CGFloat tempCenterX = attribute.center.x; 13 if (fabs(horizontalCenterX - tempCenterX) < fabs(offsetAdjustment)) { 14 offsetAdjustment = tempCenterX - horizontalCenterX; 15 } 16 } 17 18 CGPoint resultPoint = CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y); 19 20 return resultPoint; 21 }
三、結束
現在是真的完了,實現的思路和關鍵代碼都列出來了,這兒再附一個demo地址:
https://github.com/goldBreak/Demos
iOS 利用UICollectionView做一個無限循環廣告欄