Swipe-to-Select照片滑動(dòng)選擇實(shí)現(xiàn)

前言


我們的產(chǎn)品突然提出一個(gè)需求,希望讓用戶更快地選擇照片,通過(guò)滑動(dòng)的方式而不是一張一張點(diǎn)擊選擇,并且給了我們一個(gè)參考對(duì)象,iPhone手機(jī)相冊(cè)。

一開始準(zhǔn)備從UITouch和響應(yīng)鏈入手,然后根據(jù)坐標(biāo)各種計(jì)算。實(shí)際操作后發(fā)現(xiàn)工程量太大,不好實(shí)現(xiàn)。后來(lái)打算用UISwipeGesture,但是上下左右控制十分麻煩,不得不放棄。

最后github上找到一個(gè)類似的demo:Swipe to Select GridView,總算為該事件的實(shí)現(xiàn)打開思路。不過(guò),原demo和我們的項(xiàng)目實(shí)際需求相差甚遠(yuǎn),于是自己動(dòng)手實(shí)現(xiàn)了該效果。我們先來(lái)看看效果:

2017-04-08 16_11_12.gif

demo


demo地址:https://pan.baidu.com/s/1nvBcN8l

核心思想


UIPanGestureRecognizer

其實(shí)一開始看原項(xiàng)目中是用UIPanGestureRecognizer手勢(shì)來(lái)實(shí)現(xiàn)滑動(dòng)定位的時(shí)候還是很吃驚的,一直以為UIPanGestureRecognizer是用來(lái)做縮放之類的手勢(shì),沒(méi)想到滑動(dòng)手勢(shì)也能勝任。更神奇的是,如果添加到view上,而view存在UICollection,縱向滑動(dòng)優(yōu)先觸發(fā)scrollView的上下滑動(dòng),橫向滑動(dòng)就觸發(fā)PanGesture事件后又能縱向滑動(dòng)了,不需要自己寫代碼控制,簡(jiǎn)直和iPhone相冊(cè)一模一樣。(后來(lái)根據(jù)響應(yīng)鏈的思路想想也應(yīng)該是這樣。。collectionView在View的前面嘛。。)

gestureRecognizer 只要設(shè)置了最大和最小觸點(diǎn)都是1就能識(shí)別單點(diǎn)滑動(dòng)事件。只要響應(yīng)了該手勢(shì),就能拿到UIPanGestureRecognizer對(duì)象,通過(guò) [gestureRecognizer locationInView:collectionView]方法就能獲得當(dāng)前觸點(diǎn)在collectionView中的位置,然后進(jìn)一步比較,判斷選擇不選擇。

  • UIGestureRecognizerStateBegan
  • UIGestureRecognizerStateEnded

UIPanGestureRecognizer有一個(gè)state屬性,當(dāng)手指觸發(fā)事件的時(shí)候,state == UIGestureRecognizerStateBegan,這時(shí)就能進(jìn)行一些手勢(shì)開始的操作,比如標(biāo)記進(jìn)入滑動(dòng)狀態(tài)等。當(dāng)手指離開屏幕的時(shí)候,state == UIGestureRecognizerStateEnded,這時(shí)進(jìn)行手勢(shì)結(jié)束操作等。其他時(shí)刻可以根據(jù)點(diǎn)的位置進(jìn)行判斷cell選中不選中。

選中 & 不選中

仔細(xì)分析iPhone相冊(cè)cell選中不選中的實(shí)現(xiàn)可以發(fā)現(xiàn)規(guī)律:

  • 找到第一個(gè)進(jìn)入?yún)^(qū)域的cell和最后一個(gè)進(jìn)入?yún)^(qū)域的cell,然后將2個(gè)cell位置之間的cell改變狀態(tài)

如圖,我只選中紅色區(qū)域,藍(lán)色區(qū)域也跟著選中。

區(qū)域選中
  • 改變的值是第一個(gè)cell變化的值

如果第一個(gè)cell變成選中,那么后面變化的cell全都是選中。如果第一個(gè)cell變成不選中,那么后面變化的cell全都不選中。

  • cell先進(jìn)入選中區(qū)域,然后離開選中區(qū)域,那么選中與否與cell進(jìn)入選中區(qū)域之前保持一致

這一點(diǎn)就比較復(fù)雜了,也就是說(shuō)手指滑動(dòng)進(jìn)入狀態(tài)后,需要產(chǎn)生一個(gè)臨時(shí)值來(lái)保存當(dāng)前選中狀態(tài)(tmpIsSelected)而不是最終選中狀態(tài)(isSelected)。這時(shí)候就需要結(jié)合UIGestureRecognizerStateBeganUIGestureRecognizerStateEnded進(jìn)行判斷。

大致思路如下(參考demo):

  • 1、進(jìn)入滑動(dòng)狀態(tài),將所有model的isSelected的值賦值給tmpIsSelected,界面打鉤不打鉤的依據(jù)完全按照tmpIsSelected屬性的值來(lái)顯示。

  • 2、在滑動(dòng)狀態(tài)中(手指滑動(dòng)),cell狀態(tài)改變都修改model的tmpIsSelected值。

  • 3、結(jié)束滑動(dòng)狀態(tài)(手指離開),將model的tmpIsSelected的值賦值給isSelected,是否打鉤都依據(jù)isSelected值顯示。

區(qū)域判斷

tmp2da927f9.png

知道cell選中與不選中的規(guī)則之后,我們的任務(wù)就是找到首尾兩個(gè)cell的位置,然后將之間的cell的狀態(tài)改變就好。首先就是要找到第一個(gè)cell。

1、查找第一個(gè)cell

第一個(gè)cell的判斷比較簡(jiǎn)單,就是看觸點(diǎn)(x,y)坐標(biāo)是否落入cell的區(qū)域內(nèi)。這里需要遍歷collectionView.visibleCells,因?yàn)槭謩?shì)滑到的地方肯定在可視范圍內(nèi),因此要找的cell肯定也在visibleCells里面。只要遍歷一遍,找到點(diǎn)的區(qū)域在cell的frame里面的cell即可。記錄下cell要改變的狀態(tài)firstSelectedCellChoose、cell的坐標(biāo)firstChooseCellRect還有cell的位置firstChooseCellIndexPath。

2、查找第二個(gè)cell

第二個(gè)cell就要根據(jù)第一個(gè)cell的位置劃分成5個(gè)區(qū)域:上側(cè)、下側(cè)、同行左側(cè)、同行右側(cè)、cell中,如上圖所示。前4個(gè)區(qū)域都要根據(jù)坐標(biāo)判斷,然后遍歷collectionView.visibleCells,找到最后一個(gè)滿足區(qū)域的cell就是第二個(gè)cell,如果不滿足,就把model的tmpIsSelected值改回isSelected的值,達(dá)到劃出區(qū)域選擇恢復(fù)的效果。(這里需要注意collectionView.visibleCells并不是按上到下左到右返回的,因此還需要排個(gè)序)而在cell中這個(gè)區(qū)域不可能存在第二個(gè)cell(與第一個(gè)cell重復(fù)),因此只要把collectionView.visibleCells中所有的cell的選中狀態(tài)恢復(fù)即可。具體算法可以參照demo。

自動(dòng)滾動(dòng)的實(shí)現(xiàn)

tmp4b396b1a.png

在滑動(dòng)觸發(fā)事件中,我們還得認(rèn)為的添加2個(gè)區(qū)域以實(shí)現(xiàn)自動(dòng)上滑和自動(dòng)下滑功能。

想要做到控制同步自動(dòng)上滑和自動(dòng)下滑功能,我們可以設(shè)置一個(gè)參數(shù),scrollSpeed,當(dāng)scrollSpeed > 0 代表下滑,當(dāng)scrollSpeed < 0代表下滑,scrollSpeed == 0代表不滑動(dòng)。這樣,滑動(dòng)的動(dòng)畫就可以用公式表示出來(lái)。

  • collectionView.ContentOffset.y = collectionView.ContentOffset.y + scrollSpeed;

這樣做的好處是可以用一個(gè)變量就控制上下滑動(dòng),還能適當(dāng)改變scrollSpeed的值加快或者減慢滑動(dòng)速度。

  • 注意 滑動(dòng)的時(shí)候不會(huì)觸發(fā)滑動(dòng)手勢(shì)方法,必須自己調(diào)用處理方法。

相關(guān)代碼:

- (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:關(guān)于滑動(dòng)這塊我后來(lái)又改進(jìn)了下,使用UIView的動(dòng)畫更加流暢

總結(jié)


說(shuō)了那么多,其實(shí)有很多東西只有自己去嘗試后才知道是什么意思,用文字很難表達(dá)出來(lái)。

由于這個(gè)demo也是我第一次嘗試,如果有什么更好方式或者效率更高的改進(jìn),歡迎在評(píng)論區(qū)提出來(lái)~

我是翻滾的牛寶寶,歡迎大家評(píng)論交流~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 iOS里的UI控件其實(shí)沒(méi)有幾個(gè),界面基本就是圍繞那么幾個(gè)控件靈活展開,最難的應(yīng)屬UICollectionVi...
    alenpaulkevin閱讀 32,526評(píng)論 9 176
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,692評(píng)論 4 61
  • 最近將 UICollectionView 進(jìn)行了一個(gè)全面的學(xué)習(xí)及總結(jié),參考了網(wǎng)上大量的文章,把官方文檔進(jìn)行了大概翻...
    varlarzh閱讀 22,161評(píng)論 3 93
  • 人生很短,愿你遇見(jiàn),所有的美好,日子很長(zhǎng),愿你深情不會(huì)被辜負(fù) 001 最高級(jí)的成熟在于自身修養(yǎng) 其實(shí)成熟無(wú)關(guān)年齡的...
    橙琳媽閱讀 715評(píng)論 0 1
  • 枯木泣城門,驚心憶浮沉, 不為三兩語(yǔ),許作一心人。
    荒漠香果海閱讀 340評(píng)論 0 0

友情鏈接更多精彩內(nèi)容