版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2017.05.28 |
前言
在我們的app項目中,為了增加和用戶很好的交互能力,通常都需要加一些提示圖,比如說,當我們需要網(wǎng)絡加載數(shù)據(jù)的時候,首先要監(jiān)測網(wǎng)絡,如果網(wǎng)絡斷開的時候,我們需要提示用戶;還有一個場景就是登陸的時候,需要提示用戶正在登錄中和登錄成功;再比如清除用戶的緩存數(shù)據(jù)成功的時候,也需要進行清除成功的提示的,等等。總之,用的場景很多,好的提示圖可以增強和用戶的交互體驗,試想,如果沒有網(wǎng)絡,也不提示用戶,用戶還以為還在登錄,過了一會還是上不去,那可能用戶就瘋掉了,怒刪app了。最近做的幾個項目中也是對這個要求的也很多,在實際應用中可以自己寫,也可以使用第三方框架,比較知名的比如MBProgressHUD和SVProgressHUD,從這一篇開始我就一點一點的介紹它們以及它們的使用方法,希望對大家有所幫助,那我們就開始嘍。先給出github地址:
MBProgressHUD github
感興趣可以先看上一篇
1.提示圖顯示篇之MBProgressHUD(一)
2.提示圖顯示篇之MBProgressHUD(二)
這一篇將對MBProgreeHUD的初始化和動畫效果等進行介紹。
詳情
一、初始化方法
任何對象都需要進行初始化,MBProgressHUD也不例外,初始化才能在內(nèi)存中分配空間,才會存取值和運行,代碼才會有硬件依托。下面我們先看一下MBProgressHUD的初始化方法。
1. 第一種初始化方法
- (id)initWithWindow:(UIWindow *)window __attribute__((deprecated("Use initWithView: instead.")));
這種初始化方法已經(jīng)被廢棄了,Window修改為View。
2. 這個才是常用的初始化方法
/**
* A convenience constructor that initializes the HUD with the view's bounds. Calls the designated constructor with
* view.bounds as the parameter.
*
* @param view The view instance that will provide the bounds for the HUD. Should be the same instance as
* the HUD's superview (i.e., the view that the HUD will be added to).
*/
- (instancetype)initWithView:(UIView *)view;
下面我們就以一個小的demo來說明下MBProgressHUD的初始化方法。
UIImageView *imageView = [[UIImageView alloc] init];
imageView.image = [UIImage imageNamed:@"global"];
MBProgressHUD *HUD = [[MBProgressHUD alloc] initWithView:self.view];
HUD.mode = MBProgressHUDModeCustomView;
// 顯示隱藏時的動畫模式
HUD.animationType = MBProgressHUDAnimationFade;
// 關閉繪制的"性能開關",如果alpha不為1,最好將opaque設為NO,讓繪圖系統(tǒng)優(yōu)化性能
HUD.opaque = NO;
HUD.customView = imageView;
HUD.label.text = @"全球化";
HUD.detailsLabel.text = @"我們一起參與";
[self.view addSubview:HUD];
[HUD showAnimated:YES];
[HUD hideAnimated:YES afterDelay:10.0];
上面的就是利用MBProgressHUD *HUD = [[MBProgressHUD alloc] initWithView:self.view]方法來實現(xiàn)的。
如果有特殊的需求需要封裝的話,可以自定義一個類繼承自MBProgressHUD,然后采用UIView的初始化方法進行初始化和配置,也就是用下面的方法。
- (instanceType)initWithFrame:(CGRect)frame;
二、動畫效果
看MBProgressHUD.h文件前幾個就是定義幾個枚舉,有個我們前面已經(jīng)說了,那就是指示圖的樣式,下面我們看另外一個枚舉,那就是動畫效果的枚舉定義,如下所示:
typedef NS_ENUM(NSInteger, MBProgressHUDAnimation) {
// 默認效果,只有透明度變化的動畫效果
MBProgressHUDAnimationFade,
// 透明度變化+形變效果,其中MBProgressHUDAnimationZoom和
// MBProgressHUDAnimationZoomOut的枚舉值都為1
MBProgressHUDAnimationZoom,
//拉遠鏡頭, 先把形變放大到1.5倍,再恢復原狀,產(chǎn)生縮小效果
MBProgressHUDAnimationZoomOut = MBProgressHUDAnimationZoom,
//拉近鏡頭, 先把形變縮小到0.5倍,再恢復到原狀,產(chǎn)生放大效果
MBProgressHUDAnimationZoomIn
};
這里默認效果就是MBProgressHUDAnimationFade。
動畫顯示效果主要是在下面兩個方法中實現(xiàn)的。
- 1.HUD的顯示
//HUD的顯示
- (void)showUsingAnimation:(BOOL)animated
{
// Cancel any scheduled hideDelayed: calls
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[self setNeedsDisplay];
// ZoomIn,ZoomOut分別理解為`拉近鏡頭`,`拉遠鏡頭`
// 因此MBProgressHUDAnimationZoomIn先把形變縮小到0.5倍,再恢復到原狀,產(chǎn)生放大效果
// 反之MBProgressHUDAnimationZoomOut先把形變放大到1.5倍,再恢復原狀,產(chǎn)生縮小效果
// 要注意的是,形變的是整個`MBProgressHUD`,而不是中間可視部分
if (animated && animationType == MBProgressHUDAnimationZoomIn) {
// 在初始化方法中, 已經(jīng)定義了rotationTransform = CGAffineTransformIdentity.
// CGAffineTransformIdentity也就是對view不進行變形,對view進行仿射變化總是原樣
// CGAffineTransformConcat是兩個矩陣相乘,與之等價的設置方式是:
// self.transform = CGAffineTransformScale(rotationTransform, 0.5f, 0.5f);
self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f));
} else if (animated && animationType == MBProgressHUDAnimationZoomOut) {
// self.transform = CGAffineTransformScale(rotationTransform, 1.5f, 1.5f);
self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f));
}
self.showStarted = [NSDate date];
// 開始做動畫
if (animated) {
// 在初始化方法或者`hideUsingAnimation:`方法中,alpha被設置為0.f,在該方法中完成0.f~1.f的動畫效果
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.30];
self.alpha = 1.0f;
// 從形變狀態(tài)回到初始狀態(tài)
if (animationType == MBProgressHUDAnimationZoomIn || animationType == MBProgressHUDAnimationZoomOut) {
self.transform = rotationTransform;
}
[UIView commitAnimations];
}
else {
self.alpha = 1.0f;
}
}
- 2.HUD的隱藏
// HUD的隱藏
- (void)hideUsingAnimation:(BOOL)animated
{
// Fade out
if (animated && showStarted) {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.30];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(animationFinished:finished:context:)];
// 當alpha小于0.01時,就會被當做全透明對待,全透明是接收不了觸摸事件的.
// 所以設置0.02防止hud在還沒結束動畫并調用done方法之前傳遞觸摸事件.
// 在完成的回調animationFinished:finished:context:才設為0
if (animationType == MBProgressHUDAnimationZoomIn) {
self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(1.5f, 1.5f));
} else if (animationType == MBProgressHUDAnimationZoomOut) {
self.transform = CGAffineTransformConcat(rotationTransform, CGAffineTransformMakeScale(0.5f, 0.5f));
}
self.alpha = 0.02f;
[UIView commitAnimations];
}
else {
self.alpha = 0.0f;
[self done];
}
self.showStarted = nil;
}
- 3.HUD指示器的實現(xiàn)
- (void)updateIndicators
{
// 讀源碼的時候,類似這種局部變量直接忽略,等代碼用到它,我們再"懶加載"
BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]];
BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]];
// 如果模式是MBProgressHUDModeIndeterminate,將使用系統(tǒng)自帶的菊花系列指示器
if (mode == MBProgressHUDModeIndeterminate) {
// 再看回最上面的兩條語句
// 初始化的時候進來,indicator是空的,對空對象發(fā)送消息返回的布爾值是NO
// 因為在初始化完畢后,用戶可能會設置mode屬性,那時還會進入這個方法,所以這兩個布爾變量除了第一次以外是有用的
if (!isActivityIndicator) {
// 默認第一次會進入到這里,對nil發(fā)送消息不會發(fā)生什么事
// 為什么要removeFromSuperview呢,因為這方法并不會只進入一次
// 不排除有些情況下先改變了mode到其他模式,之后又改回來了,這時候如果不移除
// MBProgressHUD就會殘留子控件在subviews里,雖然界面并不會顯示它
[indicator removeFromSuperview];
// 使用系統(tǒng)自帶的巨大白色菊花
// 系統(tǒng)菊花有三種
//typedef NS_ENUM(NSInteger, UIActivityIndicatorViewStyle) {
// UIActivityIndicatorViewStyleWhiteLarge, // 大又白
// UIActivityIndicatorViewStyleWhite, // 小白
// UIActivityIndicatorViewStyleGray, // 小灰
//};
self.indicator = MB_AUTORELEASE([[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]);
[(UIActivityIndicatorView *)indicator startAnimating];
[self addSubview:indicator];
}
// 系統(tǒng)菊花能設置顏色是從iOS5開始(NS_AVAILABLE_IOS(5_0)),這里用宏對手機版本進行了判斷
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 50000
[(UIActivityIndicatorView *)indicator setColor:self.activityIndicatorColor];
#endif
}
// 源碼實現(xiàn)了兩種自定義視圖
// 一種是MBBarProgressView(進度條),另一種是MBRoundProgressView(圓餅or圓環(huán))
else if (mode == MBProgressHUDModeDeterminateHorizontalBar) {
// 進度條樣式
[indicator removeFromSuperview];
self.indicator = MB_AUTORELEASE([[MBBarProgressView alloc] init]);
[self addSubview:indicator];
}
else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) {
// 這兩種mode都產(chǎn)生MBRoundProgressView視圖,MBRoundProgressView又分兩種樣式
// 如果你設置了mode為MBProgressHUDModeDeterminate,那么流程是這樣子的
// 1)alloc init先生成系統(tǒng)的MBProgressHUDModeIndeterminate模式->
// 2)設置了mode為餅圖,觸發(fā)KVO,又進入了updateIndicators方法->
// 3)由于isRoundIndicator是No,產(chǎn)生餅狀圖
// 如果設置了MBProgressHUDModeAnnularDeterminate,那么步驟比它多了一步,
// 1)alloc init先生成系統(tǒng)的MBProgressHUDModeIndeterminate模式->
// 2)設置了mode為圓環(huán),觸發(fā)KVO,又進入了updateIndicators方法->
// 3)由于isRoundIndicator是No,產(chǎn)生餅狀圖->
// 4)設置[(MBRoundProgressView *)indicator setAnnular:YES]觸發(fā)MBRoundProgressView的
// KVO進行重繪視圖產(chǎn)生圓環(huán)圖
if (!isRoundIndicator) {
// 個人認為這個isRoundIndicator變量純屬多余
// isRoundIndicator為Yes的情況只有從MBProgressHUDModeDeterminate換成MBProgressHUDModeAnnularDeterminate
// 或者MBProgressHUDModeAnnularDeterminate換成MBProgressHUDModeDeterminate
// 而實際上這兩種切換方式產(chǎn)生的視圖都是圓環(huán),這是由于沒有讓annular設置成No
[indicator removeFromSuperview];
self.indicator = MB_AUTORELEASE([[MBRoundProgressView alloc] init]);
[self addSubview:indicator];
}
if (mode == MBProgressHUDModeAnnularDeterminate) {
[(MBRoundProgressView *)indicator setAnnular:YES];
}
}
else if (mode == MBProgressHUDModeCustomView && customView != indicator) {
// 自定義視圖
[indicator removeFromSuperview];
self.indicator = customView;
[self addSubview:indicator];
}
else if (mode == MBProgressHUDModeText) {
// 只有文字的模式
[indicator removeFromSuperview];
self.indicator = nil;
}
}
- 4.HUD布局
??下面我們看一下MBProgressHUD的子控件布局,它的子控件包括指示器、label、detailLabel,還要注意的是MBProgressHUD的背景是整個屏幕的,當我們設置MBProgressHUD的backgroundColor,會發(fā)現(xiàn)確實是整個屏幕的顏色都改變了。下面我們看一下子控件的布局。
- (void)layoutSubviews
{
[super layoutSubviews];
// MBProgressHUD是一個充滿整個父控件的控件
// 使得父控件的交互完全被屏蔽
UIView *parent = self.superview;
if (parent) {
self.frame = parent.bounds;
}
CGRect bounds = self.bounds;
.......
// 如果用戶設置了square屬性,就會盡量讓它顯示成正方形
if (square) {
// totalSize為下圖藍色框框的size
CGFloat max = MAX(totalSize.width, totalSize.height);
if (max <= bounds.size.width - 2 * margin) {
totalSize.width = max;
}
if (max <= bounds.size.height - 2 * margin) {
totalSize.height = max;
}
}
if (totalSize.width < minSize.width) {
totalSize.width = minSize.width;
}
if (totalSize.height < minSize.height) {
totalSize.height = minSize.height;
}
size = totalSize;
}
具體布局可以參考下面的布局,這個圖是別人畫的,具體參考文章和博客我下面會列出,謝謝他的分享。
??上圖藍色虛線部分代表子控件們能夠展示的區(qū)域,其中寬度是被限制的,其中定義了maxWidth讓3個子控件中的最大寬度都不得超過它。值得注意的是,源碼并沒設置最大高度,如果我們使用自定義的視圖,高度夠大就會使藍色虛線部分的上下底超出屏幕范圍,某種程度上來講也是設計上的一種bug,但是畢竟還是不影響正常的使用,畢竟沒有人會用很大的自定義視圖。
??此外,綠色的label被限制為只能顯示一行,黃色的detailLabel通過下面的代碼來限制它不能超出屏幕上下。
// 計算出屏幕剩下的高度
// 其中減去了4個margin大小,保證了子空間和HUD的邊距,HUD和屏幕的距離
CGFloat remainingHeight = bounds.size.height - totalSize.height - kPadding - 4 * margin;
// 將文字內(nèi)容限制在這個size中,超出部分省略號
CGSize maxSize = CGSizeMake(maxWidth, remainingHeight);
CCGSize detailsLabelSize = MB_MULTILINE_TEXTSIZE(detailsLabel.text, detailsLabel.font, maxSize, detailsLabel.lineBreakMode);
// 7.0開始使用boundingRectWithSize:options:attributes:context:方法計算
// 7.0以前使用sizeWithFont:constrainedToSize:lineBreakMode:計算
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
#define MB_MULTILINE_TEXTSIZE(text, font, maxSize, mode) [text length] > 0 ? [text \
boundingRectWithSize:maxSize options:(NSStringDrawingUsesLineFragmentOrigin) \
attributes:@{NSFontAttributeName:font} context:nil].size : CGSizeZero;
#else
#define MB_MULTILINE_TEXTSIZE(text, font, maxSize, mode) [text length] > 0 ? [text \
sizeWithFont:font constrainedToSize:maxSize lineBreakMode:mode] : CGSizeZero;
#endif
下圖是另一種沒達到maxSize的情況。
參考文章和博客
后記
??這一篇主要寫的是MBProgressHUD的初始化方法以及動畫方法,未完,待續(xù)~~~