前幾天在gitHub看到個不錯的效果,就是
DaiExpandCollectionView,效果如圖:

所以趕緊下下來源碼看看他怎么實現的,打開源碼看了半天,發現他沒寫什么關於動畫的代碼啊。。。
經高人指點,才明白原來他是利用了UICollectionViewFlowLayout的隱式動畫!
所謂隱式動畫,就是系統自帶的動畫了,其實也不是什么高大上的東西,關鍵是我怎么就沒想到可以這么用!
研究了半天人家的源碼,基本了解了他實現的思路,然后發現他的這個庫用起來比較不方便,需要繼承他的collectionView,而且不能自定義cell大小,不能很好的適配各種尺寸的屏幕,
但這個庫我確實很喜歡,所以就自己仿寫了一個,就是LXMExpandLayout,順便解決下上面發現的問題,地址在這里:
https://github.com/Phelthas/LXMExpandLayout
效果如圖:


還是來說說思路和原理的問題
基本思路是:
繼承系統的UICollectionViewFlowLayout,利用UICollectionViewFlowLayout已有的各種屬性和效果,對其做出適當的修改已達到自己想要的效果
具體來說就是:
1,重載 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;方法,
用 [super layoutAttributesForElementsInRect:newRect]獲取各個元素在 UICollectionViewFlowLayout中的layoutAttributes。
然后修改其屬性
2,屬性可不是隨便改的,這個得給原作者點個贊,他想出來的這個規則,讓這個屬性的修改簡單了好幾倍。
這個規則就是:
選中的item放大到這個item的旁邊只能放得下一個沒有放大的item!!!
這個規則的牛X之處在於:這樣放大的item的右邊,剛好可以放得下原來跟他同一行的剩下的item,進而需要修改屬性的item就是原來跟需要放大的item同一行的item,剩下item,只需要簡單的上下平移或者根本不用動!!!
3,知道了這個規則,就有了方向,可以大膽動手改了,選中的item用transform屬性做平移和放大,其他的簡單修改frame就可以了
這里怎么取出來和選中item同一行的其他item呢?
有個不錯的方法,就是: self.sameRowItemArray = [super layoutAttributesForElementsInRect:CGRectMake(0, selectedAttributes.frame.origin.y, self.collectionViewWidth, self.itemHeight)];注意是super的方法,不是self~~
4,關鍵的時刻到了,怎么讓collectionView執行隱形動畫呢?
這個也是從原作者那里學到的,就是調用系統的 - (void)performBatchUpdates:(void (^)(void))updates completion:(void (^)(BOOL finished))completion;方法。我把它封裝成了一個collectionView的分類,方便配合我的LXMExpandLayout使用。
這個方法會使collectionView重新調用其layout去重新布局,而且是帶動畫的哦~~
5,直接調用performBatchUpdates方法的話,動畫是線性的,那怎么樣才能有彈簧的效果呢?
這里又從原作者那里學到一招
:用一個UIView animation的block將則個performBatchUpdates包起來
這樣就可以用UIView的animation動畫代替performBatchUpdates的默認動畫。利用iOS7自帶的彈簧動畫方法
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);
效果杠杠的~~
6,發現個UICollectionView的系統bug,關於UICollectionViewFlowLayout的
UICollectionViewFlowLayout 的 - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 方法返回的數組中有indexpath重復的對象,解決辦法見:
http://stackoverflow.com/questions/12927027/uicollectionview-flowlayout-not-wrapping-cells-correctly-ios/13389461#13389461
其他還有一些細節需要注意的,代碼里我已經寫了注釋了,這里就不在說了
具體這個庫怎么使用,也已經寫在gitHub上了,也就不羅嗦了。
有問題歡迎留言討論~~
2015-06-27更新
添加拖動排序效果,參考的https://github.com/ra1028/RACollectionViewReorderableTripletLayout 的效果。
本來以為這個效果不會太難的,結果寫了寫簡直蛋都碎了。。。寫了好幾天,比我想象的復雜好多,有好多需要注意的地方,估計還有很多隱藏的bug。。。慢慢發現慢慢修復吧。
值得記錄一下的是:
1,利用手勢的代理方法,讓兩個手勢協同作用。
具體就是
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer;方法和
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer方法。
加入適當的判斷,就可以實現手勢的協同。本例就是用這種方法讓longPressGesture和panGesture協同作用實現拖動效果。
2,讓scrollView自動滑動的方法
其實是利用一個定時器在需要的時候逐步設置scrollView的contentOffset,
需要注意的是:這時候pan手勢的translation距離是不變的,但是view的位置卻應該變。。。說起來都是淚啊!
因為設置了contentOffset,view的frame其實是應該變的,但是這時候手沒動,所以pan手勢的各種值都不會變,所以只能手動記錄這個變化。。。