新浪微博中的“加號按鈕”點擊後的彈出動畫很有意思,每當一個人孤單寂寞冷的時候總會不停的點這個動畫,終於忍不住自己撸了一個。廢話不多說,直接上效果圖:
首先說一下通過這個動畫制作過程給大家分享的技術問題: 1.背景的毛玻璃效果 2.彈性動畫 3.動畫進行時對用戶交互的處理 4.UIControl
的 event
類型
對新浪微博中彈出動畫背景的思路分析:
新浪的背景動畫效果是有一個透明漸變的過程,並且最終漸變停止之後顯示的是一個帶有毛玻璃(半透明模糊)效果的視圖。我的模仿思路是當准備要彈出動畫的時候對整個視圖進行截屏操作,將截屏後的圖片進行毛玻璃效果渲染,然後在視圖上加一個背景 UIImageView
來顯示這張圖片,然後通過 alpha
動畫來由透明逐漸漸變出來。
UIGraphics BeginImageContextWithOptions(size, NO, scale); [view.layer renderInContext:UIGraphicsGetCurrentContext()]; UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
制作毛玻璃效果有三種選擇,一是 iOS8
後推出的 UIBlurEffect
和 UIVisualEffectView
來直接顯示一個帶有毛玻璃效果的 View
不過這種方式並不能直接生成一張帶有毛玻璃效果的圖片,而且它的模糊程度設置方法非常有限只有那幾個枚舉類型,無法滿足需求。
第二種是通過 CoreImage
添加濾鏡的方式來實現毛玻璃效果,不過這種方式有個缺點如果濾鏡使用頻繁會對主線程產生影響,如果我不斷頻繁的重復動畫效果就必須要做判斷看濾鏡是否正在起作用,否則會經常出現崩潰和內存洩漏問題。
第三種我們使用蘋果13年 WWDC
上發布的官方 sample
一個 UIImage
的分類 UIImage+ImageEffects.h
,它不但可以制作毛玻璃效果圖片,而且可以調整模糊程度和顏色渲染。下面給出代碼:
image = [image bhb_applyBlurWithRadius:15 tintColor:tintColor saturationDeltaFactor:1 maskImage:nil];//因為OC沒有命名空間,避免你的程序中使用到了這個分類導致沖突,我加了前綴
最終的顯示效果很不錯,我將模糊程度盡量的調節到與新浪微博一致了,不過在這個過程中,我發現當我頻繁的進行彈出操作時,內存會不斷攀升如下圖: 內存暴增的原因肯定是因為剛才的截圖或者毛玻璃效果導致的,我們來用 Instruments
的 Allocations
來進行內存分析,找出元凶。 Xcode -> Product -> Profile -> Allocations, 開啟之後我們來使用右下角的 Mark generation
(內存快照功能,進一步了解請移步[這片文章][1]) 在彈出 View
和移除 View
的兩個時間點加 Mark generation
。 可以觀察到,兩個時間點竟然相差了4.31M,這是一個相當恐怖的數字,怪不得我點擊幾次彈出功能之後內存會暴增,讓我們繼續跟蹤: 跟蹤到最後,我們發現大部分未釋放的內存來自於繪圖和位圖的創建,回想我們當初做的截圖操作,圖片上下文開啟後並沒有進行關閉操作,所以在程序不斷截圖的過程中開啟了無數的圖片上下文而且不會被釋放,添加下面這句關鍵的代碼就可以解決問題(NC的我竟然連這個都忘了加-.-):
UIGraphicsEndImageContext();
新浪動畫中,按鈕彈出的動畫為彈性效果,按鈕到達最終位置後不會直接停止,而是做類似彈簧的一種阻尼運動,要實現這種動畫也很簡單 iOS7
後蘋果非常給力的添加了 spring
彈性動畫的快速創建方式:
[UIView animateWithDuration:(NSTimeInterval) delay:(NSTimeInterval) usingSpringWithDamping:(CGFloat) initialSpringVelocity:(CGFloat) options:(UIViewAnimationOptions) animations:^{} completion:^(BOOL finished) {}];
通過對 Damping
阻力和 Velocity
初速度的設置可以實現彈性動畫動畫效果如下圖:
當然實現彈性動畫還可以使用[Jonathan Willing][2]大大的 JNWSpringAnimation
,你可以像使用 CABasicAnimation
一樣輕松的使用它,通過改變關鍵的3個屬性 Damping
阻力, stiffness
硬直, mass
質量來改變彈性動畫的效果代碼如下:
JNWSpringAnimation *scale = [JNWSpringAnimation animationWithKeyPath:@"transform.translation.x"]; scale.damping = 7; scale.stiffness = 7; scale.mass = 1; scale.fromValue = @(0); scale.toValue = @(400); [redBall.layer addAnimation:scale forKey:scale.keyPath]; redBall.transform = CGAffineTransformMakeTranslation(400, 0);
關鍵的三個屬性對動畫的影響如下圖:
為了減輕項目對第三方框架的依賴,我使用了 iOS7
原生的 spring
動畫,如果你想要兼容以前版本,替換成 JNWSpringAnimation
即可。回到新浪動畫的制作,動畫是6個按鈕按照順序依次出現和消失,並且點擊 more
按鈕後可以向左平移到第二屏幕,並且在第二屏幕點擊 叉號
按鈕動畫會加在第二屏幕的6個按鈕上。 效果如圖:
對此我使用了一個 UIScrollView
來承載這些按鈕,並聲明2個數組用來保存所有的按鈕和正在顯示的按鈕(在屏幕上,並且需要做動畫):
@property (nonatomic,strong) NSMutableArray * visableArray;//屏幕顯示的按鈕數組 @property (nonatomic,strong) NSMutableArray * itemsArray;//所有按鈕的數組
這樣我在給這些按鈕加動畫的時候就不會浪費性能,只把動畫加在當前顯示在屏幕的按鈕上。動畫依次按照一定的時間差來執行,解決的辦法我是用的 GCD
:
[self.visableArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { BHBCustomBtn * btn = obj; CGFloat x = btn.frame.origin.x; CGFloat y = btn.frame.origin.y; CGFloat width = btn.frame.size.width; CGFloat height = btn.frame.size.height; btn.frame = CGRectMake(x, [UIScreen mainScreen].bounds.size.height + y - self.frame.origin.y, width, height); btn.alpha = 0.0; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(idx * 0.03 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [UIView animateWithDuration:0.35 delay:0 usingSpringWithDamping:0.85 initialSpringVelocity:25 options:UIViewAnimationOptionCurveEaseIn animations:^{ btn.alpha = 1; btn.frame = CGRectMake(x, y, width, height); } completion:^(BOOL finished) { if ([btn isEqual:[self.visableArray lastObject]]) { self.superview.superview.userInteractionEnabled = YES; } }]; }); }];
這樣按鈕就會依次以彈性動畫的形式彈動出來了,是不是很簡單,對於動畫結束後所作的處理我將在下一節中說明。
動畫過程中,處理用戶交互的問題相當關鍵,視圖動畫中默認自動停止響應用戶交互,因此當按鈕進行彈性動畫時,觸摸它並不會生成任何事件。 但是當你觸摸加號按鈕的時候,會再次進行彈出框動畫,這時就會彈出兩個彈出框,這是我們不希望看到的,我們可以將加號按鈕的 enable
屬性設置為 YES
但是這樣做我們要在封裝的視圖內部獲取外部的加號按鈕,這一點違背了封裝性原則,並不是一個好的設計。所以我在所有按鈕的彈性動畫開始時,設置:
self.superview.superview.userInteractionEnabled = NO;
注意視圖的層級關系,我所設計的 BHBPopView
內部裝著一個 UIScrollView
,而它上面放著這些按鈕,所以你要找到父視圖的父視圖才能夠統一屏蔽用戶的交互行為,當所有的按鈕彈性動畫結束時,也就是 visableArray
數組最後一個按鈕動畫結束時,我們恢復用戶的交互:
self.superview.superview.userInteractionEnabled = YES;
這樣當動畫進行過程中,屏蔽了用戶的交互,避免發生一些意外的情況。
UIControl
的 event
類型注意到新浪動畫裡面按鈕有一個放大效果,並且當你手指放上去的時候放大,手指稍微挪動,便恢復原始大小。讓我們先來看一下動畫:
要實現這個效果只需要做一個形變動畫就可以了,關鍵是我們如何控制它放大和恢復大小。 思路如下:按鈕是繼承自 UIControl
,UIControl
有不同的事件狀態:
UIControlEventTouchDown = 1 << 0, // 手指落在按鈕的一瞬間觸發 UIControlEventTouchDownRepeat = 1 << 1, // 多點觸碰的時候,當第二根以上的手指觸摸瞬間出發 UIControlEventTouchDragInside = 1 << 2, // 手指在視圖范圍內拖動觸發 UIControlEventTouchDragOutside = 1 << 3, // 手指在視圖范圍外拖動觸發 UIControlEventTouchDragEnter = 1 << 4, // 手指從視圖外拖動到視圖內時觸發 UIControlEventTouchDragExit = 1 << 5, // 手指從視圖內部拖動到視圖外時觸發 UIControlEventTouchUpInside = 1 << 6, // 手指在視圖內部抬起時觸發 UIControlEventTouchUpOutside = 1 << 7, // 手指在視圖外部抬起時觸發 UIControlEventTouchCancel = 1 << 8, // 取消事件,放上了太多手指或者被上鎖或者電話呼叫打斷。 UIControlEventValueChanged = 1 << 12, // 當視圖的值發生改變時,發送通知。 UIControlEventEditingDidBegin = 1 << 16, // UITextField UIControlEventEditingChanged = 1 << 17, UIControlEventEditingDidEnd = 1 << 18, UIControlEventEditingDidEndOnExit = 1 << 19, // 'return key' ending editing UIControlEventAllTouchEvents = 0x00000FFF, // for touch events UIControlEventAllEditingEvents = 0x000F0000, // for UITextField UIControlEventApplicationReserved = 0x0F000000, // range available for application use UIControlEventSystemReserved = 0xF0000000, // range reserved for internal framework use UIControlEventAllEvents = 0xFFFFFFFF
我們所用到的事件是 TouchDown
和 DragInside
,手指放上去觸發 TouchDown
放大視圖,在視圖內部移動 DragInside
時恢復視圖,注意按鈕的作用范圍是整個矩形區域包含了圖片和文字,當你的手指移出圖片的時候並非一定會移出按鈕作用范圍,所以依然會觸發 TouchUpInsite
事件,這時候我們需要做一個屬性來記錄用戶拖拽之後取消按鈕的 TouchUpInsite
執行。
@property (nonatomic,assign) BOOL btnCanceled;
這樣我們就可以實現動畫效果了,具體代碼如下:
//處理按鈕有效的點擊事件,當前按鈕放大消失,其他按鈕縮小消失,回調點擊事件 [btn addTarget:self action:@selector(didClickBtn:) forControlEvents:UIControlEventTouchUpInside]; //處理手指按下事件,放大按鈕 [btn addTarget:self action:@selector(didTouchBtn:) forControlEvents:UIControlEventTouchDown]; //處理手指拖動事件,恢復按鈕大小 [btn addTarget:self action:@selector(didCancelBtn:) forControlEvents:UIControlEventTouchDragInside];
制作類似新浪微博這種彈出框動畫,我的思路是先分析邏輯,這些特效都由哪些組成,毛玻璃背景,加頂部一個 logo
,加中間 UIScrollView
和上面的很多按鈕,加底部工具條。研究透徹動畫的執行順序,動畫執行結果有哪些分支。然後針對特效中的難點,比如毛玻璃,按鈕彈性動畫等等進行逐一研究攻破,最後將這些組件整合在一起變成一個好玩的動畫,最後不要忘了動畫的內存和性能測試。這次我模仿的新浪微博動畫彈性效果並不是太理想,比起新浪原生來說不是特別一致,也希望有興趣的你來給我一些建議優化它。最終在你的項目中加入我的彈出框動畫真的只需要一句話哦:
/** * 直接顯示一個popView在某個view上 * * @param view 父view * @param imageArray 圖標數組 * @param titles 標題數組 * @param block 回調 * @return pop視圖 */ + (BHB_INSTANCETYPE)showToView:(UIView *)view andImages:(NSArray *)imageArray andTitles:(NSArray *)titles andSelectBlock:(DidSelectItemBlock)block; /** * 如果顯示一個帶more功能的,請使用此方法 * * @param view 父view * @param array BHBItem類型的集合 * @param block 回調 * @return pop視圖 */ + (BHB_INSTANCETYPE)showToView:(UIView *)view withItems:(NSArray *)array andSelectBlock:(DidSelectItemBlock)block;
hexo出點問題修復到下半夜啊(升級到3.0太蛋疼了),現在腦子暈暈的,明天還要去新公司入職,動畫中還有很多細節我不能一一分享了,歡迎大家來搞我的Demo
good luck!
原文鏈接:http://bihongbo.com/2015/08/19/sinaAnimation/