上一篇文章《仿映客刷禮物效果---基本邏輯實現》中,分析了刷禮物效果的基本流程與具體實現代碼。但還有一些BUG和一些可優化的地方沒有處理,現在我們就來分析下這些遺留的問題。當然個人的能力是有限的,肯定還有很多我沒有發現到的問題,如果大家在使用過程中有遇到其它的問題,歡迎大家及時指出,我好及時完善。
優化後的效果圖如下:
廢話不多說,先看問題。
問題效果圖如下圖:
從圖中可以看到:當一個禮物動畫組正在執行隱藏動畫時,這時恰好收到一個新的與之相同類型的禮物消息,按正常邏輯來看,這個新的禮物消息應該應該作為一個新的動畫組開始展示。但是,從GIF圖中可以看到,連送按鈕再次出現的時,新的動畫組並沒有開始展示,那新接收到禮物消息去哪裡了呢?
如果將隱藏動畫的動畫時間設置長一點,重復上面的問題流程。你就會發現,其實新接收到的禮物消息並不是消失了,而是被判定為一次連乘動畫。所以這時其實是一邊執行隱藏動畫,一遍執行連乘動畫,這就導致連乘動畫很難被看到,從而造成了新接收到的禮物消息消失了。
知道了問題原因,解決問題就非常簡單了。這裡我的解決方法是:讓cell在執行隱藏動畫時不被判定為正在執行動畫,因此我給cell設置了幾種動畫狀態,其邏輯關系如下圖所示:
有了動畫狀態之後,只用修改動畫檢測的判斷條件就可以了,修改後的代碼如下:
- (PresentViewCell *)examinePresentingCell:(id)obj { for (PresentViewCell *cell in self.showCells) { if ([cell.sender isEqualToString:[obj sender]] && [cell.giftName isEqualToString:[obj giftName]]) { //當前正在展示動畫並且不是隱藏動畫 if (cell.state != AnimationStateNone && cell.state != AnimationStateHiding) return cell; } } return nil; }
如果當前沒有不為空閒並且也沒有在隱藏動畫,就判定當前cell可以執行連乘動畫。
運行程序,再次測試,就會發現當cell正在執行隱藏動畫時收到一條相同類型消息,新的消息會在新的動畫組中展示,或是等有了空閒的cell時在展示。(修改後的具體效果可以在Demo中驗證,下同)
問題效果圖如下:
從圖中可以看到:當連續多次快速的點擊同一個發送按鈕時,連乘的動畫效果就消失了。
很明顯這裡的問題原因就是因為:上一次點擊的連乘動畫還沒執行完,就開始了下一次連乘動畫,從而造成了這種效果。
從問題原因中很容易想到這裡需要用到緩存機制,即等到上一次動畫執行完了再執行下一次動畫。首先我們很容易想到的就是NSOperationQueue和dispatch_group_t,這是系統封裝的兩個任務隊列,很容易實現上面的需求,而且還特別簡單,只用在shakeAnimationWithNumber:裡面實現緩存機制就行了。這裡介紹下dispatch_group_t隊列的實現方法,其代碼如下:
- (void)shakeAnimationWithNumber:(NSInteger)number { if (!_queue && !_group) { _queue = dispatch_queue_create("com.shakeCache.queue", DISPATCH_QUEUE_SERIAL); _group = dispatch_group_create(); dispatch_group_notify(_group, dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self hiddenAnimationOfShowShake:YES]; }); }); } dispatch_group_async(_group, _queue, ^{ [self startShakeAnimationWithNumber:number completion:nil]; }); }
代碼很簡單,就是創建一個全局的串行隊列,如果group任務完成就延時執行隱藏動畫,每次調用都想group中添加一個連乘動畫任務。
運行程序,你會發現問題還是存在,這是為什麼呢?其實原因很簡單,就是因為:UIView封裝的動畫是在子線程中執行,與添加任務操作不在同一個線程中。所以雖然任務是順序添加的,但動畫的執行並不是串行執行的。
這裡的解決辦法是:自己實現緩存--收到連乘動畫先緩存,如果有緩存並且沒有正在執行連乘動畫,就取緩存,開始動畫,動畫完成就刪除緩存,再去取緩存,只到沒有緩存為止。具體實現代碼如下:
- (void)shakeAnimationWithNumber:(NSInteger)number { if (number > 0) [self.caches addObject:@(number)]; if (self.caches.count > 0 && _state != AnimationStateShaking) { NSInteger cache = [self.caches.firstObject integerValue]; [self.caches removeObjectAtIndex:0];//不能刪除對象,因為可能有相同的對象 __weak typeof(self) ws = self; [self startShakeAnimationWithNumber:cache completion:^(BOOL finished) { [ws shakeAnimationWithNumber:-1];//傳-1是為了緩存不被重復添加 }]; } }
再次運行程序,就會發現連乘動畫是串行執行了。
在開始下一次動畫去緩存時,這時剛好收到一個與取出的緩存相同類型的消息,又會去取緩存。在極端的情況下,這會造成兩個相同類型的禮物動畫同時展示。
因為這是一個邏輯上特別極端的情況,所以這裡沒有給出效果圖。
這個問題原因其實就是因為:取緩存到動畫開始這段時間內收到一條與取出緩存相同類型的消息,導致這個新的消息在做動畫檢測時沒有檢測出來,所以新的消息可能也會作為新的動畫組開始展示。
這裡的解決辦法是將取緩存到動畫開始這個時間縮減到最短,也就是在開始展示動畫前就將cell的動畫狀態從AnimationStateNone設置成AnimationStateShowing。
關於這個問題,有興趣的可以自行測試,測試時記得要增大取緩存到開始動畫這段時間。具體操作就是:增大cell的顯示動畫時間,並且等cell的顯示動畫完成再將cell的動畫狀態從AnimationStateNone設置成AnimationStateShowing。(如果有測試出問題的,記得將問題效果的gif圖分享給我!拜謝了!)
解決一個問題,就會帶來新的問題。這裡給動畫做了這麼多的緩存之後帶來的新的問題是什麼呢!想必測試過的應該已經發現了:連送按鈕已經隱藏了,但是連乘動畫還在執行,這肯定是不合邏輯的。
對於這個問題,只有在項目中有要求出現連送按鈕才會出現,而且出現了也不一定算是問題(映客也沒解決這個問題),所以這裡就不解決這個問題了,如果確實有要求的,這裡提供下思路:因為展示動畫和連乘動畫的動畫時間都是確定的,所以很容易計算出執行完當前動畫組需要的時間。然後每接收到一個連乘動畫就對這個時間進行更新,並用代理或其它的形式將這個時間值傳遞出去,最後根據這個值對連送按鈕進行控制就可以了。(當然了,有實現了這個思路或是有更好思路的,方便的話,也請分享給我,再次拜謝了!)
一個好的功能應該除了具有易用性之外,還應該具有良好的擴展性。
因為項目的需求不同,所以對展示動畫與隱藏動畫的要求也各部相同。這裡就提供了對這連個動畫自定義的接口,接口名如下:
/** * 自定義展示動畫 * * @param flag 是否帶有連乘動畫 */- (void)customDisplayAnimationOfShowShakeAnimation:(BOOL)flag;/** * 自定義隱藏動畫 * * @param flag 是否帶有連乘動畫 */- (void)customHideAnimationOfShowShakeAnimation:(BOOL)flag;
如果想對這兩動畫進行簡單的自定義,就可以在自定義cell中重寫這兩方法。需要注意的是,這兩方法是在UIView封裝的動畫的animations回調中調用,所以只用修改cell的frame就夠了。
一般項目中通過這種方法自定義cell的展示和隱藏動畫就可以滿足需求,如果項目中確實需要更絢麗的動畫效果,就需要修改PrentViewCell的showAnimationWithModel:showShakeAnimation:prepare:completion:方法和hiddenAnimationOfShowShake:方法中的動畫代碼。
當然,還有其它的地方可以擴展,這裡就不一一對其封裝了。優化後的Demo下載地址
最後,還是開篇那句話:個人的能力有限,肯定還有很多我沒有發現到的問題,如果大家在使用過程中有遇到其它的問題,歡迎大家及時指出,我好及時完善。