前言
我們的產品突然提出一個需求,希望讓用戶更快地選擇照片,通過滑動的方式而不是一張一張點擊選擇,并且給了我們一個參考對象,iPhone手機相冊。
一開始準備從UITouch和響應鏈入手,然后根據坐標各種計算。實際操作后發現工程量太大,不好實現。后來打算用UISwipeGesture
,但是上下左右控制十分麻煩,不得不放棄。
最后github上找到一個類似的demo:Swipe to Select GridView,總算為該事件的實現打開思路。不過,原demo和我們的項目實際需求相差甚遠,于是自己動手實現了該效果。我們先來看看效果:
demo
demo地址:https://pan.baidu.com/s/1nvBcN8l
核心思想
UIPanGestureRecognizer
其實一開始看原項目中是用UIPanGestureRecognizer
手勢來實現滑動定位的時候還是很吃驚的,一直以為UIPanGestureRecognizer
是用來做縮放之類的手勢,沒想到滑動手勢也能勝任。更神奇的是,如果添加到view上,而view存在UICollection,縱向滑動優先觸發scrollView的上下滑動,橫向滑動就觸發PanGesture事件后又能縱向滑動了,不需要自己寫代碼控制,簡直和iPhone相冊一模一樣。(后來根據響應鏈的思路想想也應該是這樣。。collectionView在View的前面嘛。。)
gestureRecognizer 只要設置了最大和最小觸點都是1就能識別單點滑動事件。只要響應了該手勢,就能拿到UIPanGestureRecognizer
對象,通過 [gestureRecognizer locationInView:collectionView]
方法就能獲得當前觸點在collectionView中的位置,然后進一步比較,判斷選擇不選擇。
- UIGestureRecognizerStateBegan
- UIGestureRecognizerStateEnded
UIPanGestureRecognizer有一個state屬性,當手指觸發事件的時候,state == UIGestureRecognizerStateBegan
,這時就能進行一些手勢開始的操作,比如標記進入滑動狀態等。當手指離開屏幕的時候,state == UIGestureRecognizerStateEnded
,這時進行手勢結束操作等。其他時刻可以根據點的位置進行判斷cell選中不選中。
選中 & 不選中
仔細分析iPhone相冊cell選中不選中的實現可以發現規律:
- 找到第一個進入區域的cell和最后一個進入區域的cell,然后將2個cell位置之間的cell改變狀態
如圖,我只選中紅色區域,藍色區域也跟著選中。
- 改變的值是第一個cell變化的值
如果第一個cell變成選中,那么后面變化的cell全都是選中。如果第一個cell變成不選中,那么后面變化的cell全都不選中。
- cell先進入選中區域,然后離開選中區域,那么選中與否與cell進入選中區域之前保持一致
這一點就比較復雜了,也就是說手指滑動進入狀態后,需要產生一個臨時值來保存當前選中狀態(tmpIsSelected)而不是最終選中狀態(isSelected)。這時候就需要結合UIGestureRecognizerStateBegan
和UIGestureRecognizerStateEnded
進行判斷。
大致思路如下(參考demo):
1、進入滑動狀態,將所有model的isSelected的值賦值給tmpIsSelected,界面打鉤不打鉤的依據完全按照tmpIsSelected屬性的值來顯示。
2、在滑動狀態中(手指滑動),cell狀態改變都修改model的tmpIsSelected值。
3、結束滑動狀態(手指離開),將model的tmpIsSelected的值賦值給isSelected,是否打鉤都依據isSelected值顯示。
區域判斷
知道cell選中與不選中的規則之后,我們的任務就是找到首尾兩個cell的位置,然后將之間的cell的狀態改變就好。首先就是要找到第一個cell。
1、查找第一個cell
第一個cell的判斷比較簡單,就是看觸點(x,y)坐標是否落入cell的區域內。這里需要遍歷collectionView.visibleCells
,因為手勢滑到的地方肯定在可視范圍內,因此要找的cell肯定也在visibleCells里面。只要遍歷一遍,找到點的區域在cell的frame里面的cell即可。記錄下cell要改變的狀態firstSelectedCellChoose
、cell的坐標firstChooseCellRect
還有cell的位置firstChooseCellIndexPath
。
2、查找第二個cell
第二個cell就要根據第一個cell的位置劃分成5個區域:上側、下側、同行左側、同行右側、cell中,如上圖所示。前4個區域都要根據坐標判斷,然后遍歷collectionView.visibleCells
,找到最后一個滿足區域的cell就是第二個cell,如果不滿足,就把model的tmpIsSelected
值改回isSelected
的值,達到劃出區域選擇恢復的效果。(這里需要注意collectionView.visibleCells并不是按上到下左到右返回的,因此還需要排個序)而在cell中這個區域不可能存在第二個cell(與第一個cell重復),因此只要把collectionView.visibleCells中所有的cell的選中狀態恢復即可。具體算法可以參照demo。
自動滾動的實現
在滑動觸發事件中,我們還得認為的添加2個區域以實現自動上滑和自動下滑功能。
想要做到控制同步自動上滑和自動下滑功能,我們可以設置一個參數,scrollSpeed,當scrollSpeed > 0 代表下滑,當scrollSpeed < 0代表下滑,scrollSpeed == 0代表不滑動。這樣,滑動的動畫就可以用公式表示出來。
- collectionView.ContentOffset.y = collectionView.ContentOffset.y + scrollSpeed;
這樣做的好處是可以用一個變量就控制上下滑動,還能適當改變scrollSpeed的值加快或者減慢滑動速度。
- 注意 滑動的時候不會觸發滑動手勢方法,必須自己調用處理方法。
相關代碼:
- (void)startScroll{
if (!startScroll ) {
return;
}
if (scrollOperationQueue.operationCount > 1) {
return ;
}
__weak typeof(self) wSelf = self;
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
if (wSelf.mainCollectionView.contentOffset.y + wSelf.mainCollectionView.frame.size.height + scrollSpeed >= wSelf.mainCollectionView.contentSize.height && scrollSpeed > 0) {
[UIView animateWithDuration:0.1 animations:^{
wSelf.mainCollectionView.contentOffset = CGPointMake(wSelf.mainCollectionView.contentOffset.x, wSelf.mainCollectionView.contentSize.height -wSelf.mainCollectionView.frame.size.height);
}];
[wSelf stopScroll];
return;
}
if (wSelf.mainCollectionView.contentOffset.y + scrollSpeed <= 0 && scrollSpeed < 0) {
[UIView animateWithDuration:0.1 animations:^{
wSelf.mainCollectionView.contentOffset = CGPointMake(wSelf.mainCollectionView.contentOffset.x, 0);
}];
[wSelf stopScroll];
return;
}
[UIView animateWithDuration:0.1 animations:^{
wSelf.mainCollectionView.contentOffset = CGPointMake(wSelf.mainCollectionView.contentOffset.x, wSelf.mainCollectionView.contentOffset.y +scrollSpeed);
}];
[wSelf dealWithPointX:scrollPoint.x pointY:scrollPoint.y];
scrollPoint = CGPointMake(scrollPoint.x, scrollPoint.y +scrollSpeed);
[wSelf performSelector:@selector(startScroll) withObject:nil afterDelay:0.1];
}];
[scrollOperationQueue addOperation:operation];
}
ps:關于滑動這塊我后來又改進了下,使用UIView的動畫更加流暢
總結
說了那么多,其實有很多東西只有自己去嘗試后才知道是什么意思,用文字很難表達出來。
由于這個demo也是我第一次嘗試,如果有什么更好方式或者效率更高的改進,歡迎在評論區提出來~
我是翻滾的牛寶寶,歡迎大家評論交流~