版本记录
版本号 | 时间 |
---|---|
V1.0 | 2017.05.28 |
前言
在我们的app项目中,为了增加和用户很好的交互能力,通常都需要加一些提示图,比如说,当我们需要网络加载数据的时候,首先要监测网络,如果网络断开的时候,我们需要提示用户;还有一个场景就是登陆的时候,需要提示用户正在登录中和登录成功;再比如清除用户的缓存数据成功的时候,也需要进行清除成功的提示的,等等。总之,用的场景很多,好的提示图可以增强和用户的交互体验,试想,如果没有网络,也不提示用户,用户还以为还在登录,过了一会还是上不去,那可能用户就疯掉了,怒删app了。最近做的几个项目中也是对这个要求的也很多,在实际应用中可以自己写,也可以使用第三方框架,比较知名的比如MBProgressHUD和SVProgressHUD,从这一篇开始我就一点一点的介绍它们以及它们的使用方法,希望对大家有所帮助,那我们就开始喽。先给出github地址:
MBProgressHUD github
感兴趣可以先看上一篇
1.提示图显示篇之MBProgressHUD(一)
2.提示图显示篇之MBProgressHUD(二)
这一篇将对MBProgreeHUD的初始化和动画效果等进行介绍。
详情
一、初始化方法
任何对象都需要进行初始化,MBProgressHUD也不例外,初始化才能在内存中分配空间,才会存取值和运行,代码才会有硬件依托。下面我们先看一下MBProgressHUD的初始化方法。
1. 第一种初始化方法
- (id)initWithWindow:(UIWindow *)window __attribute__((deprecated("Use initWithView: instead.")));
这种初始化方法已经被废弃了,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,让绘图系统优化性能
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]方法来实现的。
如果有特殊的需求需要封装的话,可以自定义一个类继承自MBProgressHUD,然后采用UIView的初始化方法进行初始化和配置,也就是用下面的方法。
- (instanceType)initWithFrame:(CGRect)frame;
二、动画效果
看MBProgressHUD.h文件前几个就是定义几个枚举,有个我们前面已经说了,那就是指示图的样式,下面我们看另外一个枚举,那就是动画效果的枚举定义,如下所示:
typedef NS_ENUM(NSInteger, MBProgressHUDAnimation) {
// 默认效果,只有透明度变化的动画效果
MBProgressHUDAnimationFade,
// 透明度变化+形变效果,其中MBProgressHUDAnimationZoom和
// MBProgressHUDAnimationZoomOut的枚举值都为1
MBProgressHUDAnimationZoom,
//拉远镜头, 先把形变放大到1.5倍,再恢复原状,产生缩小效果
MBProgressHUDAnimationZoomOut = MBProgressHUDAnimationZoom,
//拉近镜头, 先把形变缩小到0.5倍,再恢复到原状,产生放大效果
MBProgressHUDAnimationZoomIn
};
这里默认效果就是MBProgressHUDAnimationFade。
动画显示效果主要是在下面两个方法中实现的。
- 1.HUD的显示
//HUD的显示
- (void)showUsingAnimation:(BOOL)animated
{
// Cancel any scheduled hideDelayed: calls
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[self setNeedsDisplay];
// ZoomIn,ZoomOut分别理解为`拉近镜头`,`拉远镜头`
// 因此MBProgressHUDAnimationZoomIn先把形变缩小到0.5倍,再恢复到原状,产生放大效果
// 反之MBProgressHUDAnimationZoomOut先把形变放大到1.5倍,再恢复原状,产生缩小效果
// 要注意的是,形变的是整个`MBProgressHUD`,而不是中间可视部分
if (animated && animationType == MBProgressHUDAnimationZoomIn) {
// 在初始化方法中, 已经定义了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;
// 从形变状态回到初始状态
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指示器的实现
- (void)updateIndicators
{
// 读源码的时候,类似这种局部变量直接忽略,等代码用到它,我们再"懒加载"
BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]];
BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]];
// 如果模式是MBProgressHUDModeIndeterminate,将使用系统自带的菊花系列指示器
if (mode == MBProgressHUDModeIndeterminate) {
// 再看回最上面的两条语句
// 初始化的时候进来,indicator是空的,对空对象发送消息返回的布尔值是NO
// 因为在初始化完毕后,用户可能会设置mode属性,那时还会进入这个方法,所以这两个布尔变量除了第一次以外是有用的
if (!isActivityIndicator) {
// 默认第一次会进入到这里,对nil发送消息不会发生什么事
// 为什么要removeFromSuperview呢,因为这方法并不会只进入一次
// 不排除有些情况下先改变了mode到其他模式,之后又改回来了,这时候如果不移除
// MBProgressHUD就会残留子控件在subviews里,虽然界面并不会显示它
[indicator removeFromSuperview];
// 使用系统自带的巨大白色菊花
// 系统菊花有三种
//typedef NS_ENUM(NSInteger, UIActivityIndicatorViewStyle) {
// UIActivityIndicatorViewStyleWhiteLarge, // 大又白
// UIActivityIndicatorViewStyleWhite, // 小白
// UIActivityIndicatorViewStyleGray, // 小灰
//};
self.indicator = MB_AUTORELEASE([[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]);
[(UIActivityIndicatorView *)indicator startAnimating];
[self addSubview:indicator];
}
// 系统菊花能设置颜色是从iOS5开始(NS_AVAILABLE_IOS(5_0)),这里用宏对手机版本进行了判断
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 50000
[(UIActivityIndicatorView *)indicator setColor:self.activityIndicatorColor];
#endif
}
// 源码实现了两种自定义视图
// 一种是MBBarProgressView(进度条),另一种是MBRoundProgressView(圆饼or圆环)
else if (mode == MBProgressHUDModeDeterminateHorizontalBar) {
// 进度条样式
[indicator removeFromSuperview];
self.indicator = MB_AUTORELEASE([[MBBarProgressView alloc] init]);
[self addSubview:indicator];
}
else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) {
// 这两种mode都产生MBRoundProgressView视图,MBRoundProgressView又分两种样式
// 如果你设置了mode为MBProgressHUDModeDeterminate,那么流程是这样子的
// 1)alloc init先生成系统的MBProgressHUDModeIndeterminate模式->
// 2)设置了mode为饼图,触发KVO,又进入了updateIndicators方法->
// 3)由于isRoundIndicator是No,产生饼状图
// 如果设置了MBProgressHUDModeAnnularDeterminate,那么步骤比它多了一步,
// 1)alloc init先生成系统的MBProgressHUDModeIndeterminate模式->
// 2)设置了mode为圆环,触发KVO,又进入了updateIndicators方法->
// 3)由于isRoundIndicator是No,产生饼状图->
// 4)设置[(MBRoundProgressView *)indicator setAnnular:YES]触发MBRoundProgressView的
// KVO进行重绘视图产生圆环图
if (!isRoundIndicator) {
// 个人认为这个isRoundIndicator变量纯属多余
// isRoundIndicator为Yes的情况只有从MBProgressHUDModeDeterminate换成MBProgressHUDModeAnnularDeterminate
// 或者MBProgressHUDModeAnnularDeterminate换成MBProgressHUDModeDeterminate
// 而实际上这两种切换方式产生的视图都是圆环,这是由于没有让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,会发现确实是整个屏幕的颜色都改变了。下面我们看一下子控件的布局。
- (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;
}
具体布局可以参考下面的布局,这个图是别人画的,具体参考文章和博客我下面会列出,谢谢他的分享。
上图蓝色虚线部分代表子控件们能够展示的区域,其中宽度是被限制的,其中定义了maxWidth让3个子控件中的最大宽度都不得超过它。值得注意的是,源码并没设置最大高度,如果我们使用自定义的视图,高度够大就会使蓝色虚线部分的上下底超出屏幕范围,某种程度上来讲也是设计上的一种bug,但是毕竟还是不影响正常的使用,毕竟没有人会用很大的自定义视图。
此外,绿色的label被限制为只能显示一行,黄色的detailLabel通过下面的代码来限制它不能超出屏幕上下。
// 计算出屏幕剩下的高度
// 其中减去了4个margin大小,保证了子空间和HUD的边距,HUD和屏幕的距离
CGFloat remainingHeight = bounds.size.height - totalSize.height - kPadding - 4 * margin;
// 将文字内容限制在这个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的初始化方法以及动画方法,未完,待续~~~