读项目工程的时候读到了 MBProgressHUD
,所以仔细研究了一下它的原理,以及里面使用到的各种 api。我所看的源码是 MBProgressHUD 1.0.0
构造与显示方法
首先是 MBProgressHUD
的构造显示方法:
1 | + (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated { |
该方法先创建了一个 MBProgressHUD
实例,然后将其添加到 view 上,并将其显示出来。removeFromSuperViewOnHide
是个标记,暂时不用管它。
构造方法
进入上面的 initWithView:
方法,一层层进入,最终通过 commonInit
实现:
1 | - (void)commonInit { |
这个方法构造了整个 view。上部设置了很多默认项,还是等到用到的时候再细说。后面又调用了三个方法 setupViews
,updateIndicators
,registerForNotifications
。
创建 view
进入 setupVies
方法:
1 | - (void)setupViews { |
这个方法里创建了 HUD 中要显示的各个部分。首先创建了两个 MBBackgroundView
,一个是整个 view 的背景,另一个则用来容纳指示器。进入它的构造方法,在 updateForBackgroundStyle
中用到了 UIBlurEffect
。这是 iOS 中提供的用来实现高斯模糊的 API,不熟悉的朋友可以参见 UIVisualEffectView 实现高斯模糊
接下来是 updateBezelMotionEffects
方法。这个方法能够让 view 跟随屏幕倾斜而位移(看源码前还真没发现可以动)。这个方法里用到的是 UIInterpolatingMotionEffect
的 API,不熟悉的朋友可以参见 UIInterpolatingMotionEffect 视图运动
后面就是创建各个提示内容的 view,也就是后面经常用到的 NSMutableArray *subviews = [NSMutableArray arrayWithObjects:self.topSpacer, self.label, self.detailsLabel, self.button, self.bottomSpacer, nil];
这里只是将 view 加入到 MBBackgroundView
中,约束设置在 updateConstraints
方法中,如果你对这个方法不太熟悉,可以参见图像显示过程与一些注意事项。
设置指示器
现在执行到 setupViews
方法:
1 | - (void)updateIndicators { |
这个方法设置指示器到底是什么样的,通过前面设置的 mode 来区别。可以是系统自带的菊花型 UIActivityIndicatorView
,也可以是自定义的条状指示器 MBBarProgressView
,还可以是自定义的环状的 MBRoundProgressView
,当然,也可以是用户自定义的 view 作为视图指示器。
如果是自定义的指示器,外部可以随时改变 customView,这会触发内部的 set 方法,更新约束:
1 | - (void)setCustomView:(UIView *)customView { |
可以把 customView
设置为 UIImageView
来实现加载时的动态效果,详情看用 UIImageView 播放动图
在 MBBarProgressView
和 MBRoundProgressView
的 drawRect:
方法中实现了指示器的绘制。使用了 UIBezierPath
和 CoreGraphics
的 API,如果不熟,可以参见 绘制图形。进度显示是以 _progress
变量为准的。
注册通知
这里的注册通知其实挺无关紧要的:
1 | - (void)registerForNotifications { |
其实就是注册一个监听屏幕旋转的通知。而且现在大部分机型都是 iOS8 之后,基本不用设置什么。
显示方法
看一下显示方法 showAnimated:
:
1 | - (void)showAnimated:(BOOL)animated { |
可以看一下 graceTime
是一个显示的延迟时间。如果设置了这个时间,就会创建一个 NSTimer
的定时器,在 graceTime
之后再执行 showUsingAnimation
方法,将 hud 显示出来。finished
就是用来监控在这段时间还没有被显示出来的时间内,hud 是否已经被设置隐藏了的标记。
接下来看 showUsingAnimation:
方法:
1 | - (void)showUsingAnimation:(BOOL)animated { |
这里 showStarted
用来记录一个显示开始的时间,这个将会在后面用到,将通过这个时间来设置 hud 的最短显示时间。下面两个方法 setNSProgressDisplayLinkEnabled:
和 animateIn:withType:completion:
方法,一个用来设置 progressObject
进度(注意,不是所有进度变化都会用到这个方法),一个用来自定义展示动画。
我们来看看设置 progressObject 进度的方法:
1 | - (void)setNSProgressDisplayLinkEnabled:(BOOL)enabled { |
之所以说是设置 progressObject
的进度,是因为显示的进度是以 _progress
为准的。如果你仔细看上面的 drawRect:
方法,就能发现所有的图形都是以 _progress
显示的。那么这里的 progressObject
有什么用呢?
如果使用了 progressObject
,那么就会创建一个 CADisplayLink
实例。CADisplayLink
适合于高精度的定时刷新,具体使用方法可见:CADisplayLink 方式的定时器
progressObject
是一个 NSProgress
对象,如果对其不了解可以参见iOS进度指示器——NSProgress,它将进度设置给 _progress
。我们可以看一下 _progress
的 set 方法:
1 | - (void)setProgress:(float)progress { |
这样就会触发指示器的 progress
的 set 方法:
1 | - (void)setProgress:(float)progress { |
只要 progress
变化了,就会设置重绘视图。
至于动画的执行方法,没什么特别的地方,不太了解的话可以参考 核心动画
隐藏方法
来看一下隐藏方法:
1 | + (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated { |
先找到 MBProgressHUD
类,然后调动 hideAnimated:
方法:
1 | - (void)hideAnimated:(BOOL)animated { |
这个方法的解构和上面的 show 类似,其中 minShowTime
表示最少显示的时间,避免加载太快,hud 一闪而过的情况出现。这里就用到了上面说过的 showStarted
了。往下走,来看看 hideUsingAnimation:
方法:
1 | - (void)hideUsingAnimation:(BOOL)animated { |
其实也和 show 的时候差不多,有动画显示隐藏动画,没有动画直接就隐藏掉。只不过这里要做一些扫尾操作:执行 done
方法:
1 | - (void)done { |
这里就是执行一下外部传进来的 completionBlock
回调。如果外部设置了代理方法,并且重写了 hudWasHidden
那么就调用执行。
总结
MBProgressHUD
确实是一个比较简单的框架。看的时候我还是蛮仔细的注意每一个知识点的,诸如 view 的绘制显示过程、贝塞尔曲线的绘制、UIInterpolatingMotionEffect
的使用 等等等等。所以还是花了不少时间去搞懂的。写到这里再想想,整个框架的结构还是非常清晰易懂的。