前言
一直在觀察各種App的LoadingView
崭别,比較有代表性的是MBProgressVHUD
冬筒,SVProgressHUD
,這兩個(gè)使用得非常廣泛茅主,大到QQ舞痰,支付寶,小到各種不知道名的App暗膜,長(zhǎng)時(shí)間的迭代讓它們的邏輯非常完善匀奏,同時(shí)也導(dǎo)致了有些累贅,如果把它們當(dāng)一個(gè)產(chǎn)品來分析学搜,可以看出娃善,它們?cè)诓粩嗟卦黾有枨蠛褪褂脠?chǎng)景论衍,有沒有一個(gè)非常簡(jiǎn)潔的HUD沒有我們不需要的那些多余的邏輯只是負(fù)責(zé)顯示指示和隱藏呢?
好像沒有聚磺,所有今天動(dòng)手準(zhǔn)備自己封裝一個(gè)LoadingView
坯台,靈感來自Airbnb,用過Airbnb的同學(xué)都知道瘫寝,它的LoadingView
很有風(fēng)格,Airbnb是幾張圖片循環(huán)翻轉(zhuǎn)切換蜒蕾,當(dāng)然我不準(zhǔn)備復(fù)制他們的idea,我準(zhǔn)備做一個(gè)循環(huán)左右上下切換的LoadingView
焕阿。
廢話不多說咪啡,先來看一下,最終效果的原型圖
思路
分析一下思路:
- 四條虛線交叉形成的區(qū)域就是我們能夠看到的圖片
- 首先準(zhǔn)備兩張圖片暮屡,位置 中+上
- 開始第一段動(dòng)畫撤摸,向下切換,位置 變成 中+下
- 第一段動(dòng)畫結(jié)束褒纲,將下面的圖片移動(dòng)到右邊准夷,準(zhǔn)備開始第二段動(dòng)畫
- 第二動(dòng)畫跟第一段類似,只是方向是從右向左莺掠,動(dòng)畫結(jié)束后 位置變成 中+左
- 將左邊的圖片移動(dòng)到上方位置衫嵌,完成一個(gè)循環(huán)
為了使動(dòng)畫更流暢不至于生硬,我們使用iOS7推出的帶彈簧效果的API
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);
API接口
在API接口設(shè)計(jì)上彻秆,我希望盡量簡(jiǎn)單實(shí)用楔绞,封裝了兩個(gè)方法,一個(gè)用來顯示掖棉,一個(gè)用來隱藏墓律,可以指定顯示到某個(gè)view,以及指定顯示和隱藏時(shí)是否使用動(dòng)畫幔亥。
+ (void)showViewAddedTo:(UIView *)view animated:(BOOL)animated;
+ (void)hideViewForView:(UIView *)view animated:(BOOL)animated;
API實(shí)現(xiàn)
根據(jù)剛才的分析,核心的動(dòng)畫實(shí)現(xiàn)已經(jīng)有了思路察纯,現(xiàn)在就是怎么設(shè)計(jì)內(nèi)部代碼實(shí)現(xiàn)帕棉,為了方便顯示蒙版阻止加載的時(shí)候用戶交互,我把view的背景顏色設(shè)置了一個(gè)淡灰色饼记,view的中間有三個(gè)子view香伴,一個(gè)是位于中間的容器centralView
,負(fù)責(zé)顯示我們所看到的區(qū)域,方便實(shí)現(xiàn)圓角和動(dòng)畫效果具则,里面加入兩個(gè)子view即纲,firstView
和secondView
用于顯示動(dòng)畫切換的圖片。
內(nèi)部接口大概這樣
@property (nonatomic, strong) UIView *centralView;
@property (nonatomic, strong) UIImageView *firstView;
@property (nonatomic, strong) UIImageView *secondView;
為了防止多次添加LoadingView
博肋,在每次添加前低斋,我們會(huì)查找該view是否存在蜂厅,如果不存在,創(chuàng)建一個(gè)新的對(duì)象膊畴,如果存在直接跳過添加操作掘猿。反向遍歷,快速查找唇跨。
+ (RFLoadingView *)loadingViewForView:(UIView *)view
{
NSEnumerator *subviewsEnum = [view.subviews reverseObjectEnumerator];
for (UIView *subview in subviewsEnum) {
if ([subview isKindOfClass:self]) {
return (RFLoadingView *)subview;
}
}
return nil;
}
動(dòng)畫核心實(shí)現(xiàn),兩端動(dòng)畫稠通,封裝成兩個(gè)方法
- (void)animatedImageFromTopToBottom
- (void)animatedImageFromTopToBottom
具體實(shí)現(xiàn)如下
- (void)animatedImageFromTopToBottom
{
[UIView animateWithDuration:0.3 delay:0.5 usingSpringWithDamping:0.7 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
_firstView.centerY += kCenterViewSize;
_secondView.centerY += kCenterViewSize;
} completion:^(BOOL finished) {
_firstView.centerX += kCenterViewSize;
_firstView.centerY -= kCenterViewSize;
[self changeFirstImage];
[self animatedImageFromRightToLeft];
}];
}
- (void)animatedImageFromRightToLeft
{
[UIView animateWithDuration:0.3 delay:0.5 usingSpringWithDamping:0.7 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
_firstView.centerX -= kCenterViewSize;
_secondView.centerX -= kCenterViewSize;
} completion:^(BOOL finished) {
_secondView.centerX += kCenterViewSize;
_secondView.centerY -= kCenterViewSize;
if (self.alpha && self) {
[self changeSecondImage];
[self animatedImageFromTopToBottom];
}
}];
需要非常注意的是,一定要寫上終止動(dòng)畫的條件买猖,不然會(huì)無限循環(huán)改橘,影響性能
show和hide
- (void)showAnimated:(BOOL)animated
{
RFMainThreadAssert();
self.alpha = 1;
[UIView animateWithDuration:animated ? 0.3 : 0 animations:^{
_centralView.alpha = 1;
} completion:^(BOOL finished) {
[self animatedImageFromTopToBottom];
}];
}
- (void)hideAnimated:(BOOL)animated
{
RFMainThreadAssert();
[UIView animateWithDuration:animated ? 0.3 : 0 animations:^{
self.alpha = 0;
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
}
最終效果
- Github Demo地址:
RFLoadingViewDemo