MBProgressHUD提示性的框架,每个app都有,用起来挺方便,最近想读读源码,一看别的框架那么多文件,就发愁,所以先找个简单的读。基本上都通读了吧,UI的实现及逻辑。用时三天左右。
MBProgressHUD的样式
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
hud.label.text = @"Loading...";
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
[self doSomeWork];
dispatch_async(dispatch_get_main_queue(), ^{
[hud hideAnimated:YES];
});
});
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
hud.label.text = @"Loading...";
hud.detailsLabel.text = @"等着吧!";
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
[self doSomeWork];
dispatch_async(dispatch_get_main_queue(), ^{
[hud hideAnimated:YES];
});
});
- (void)determinateExample {
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
//模式是圆环
hud.mode = MBProgressHUDModeDeterminate;
hud.label.text = @"Loading...";
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
[self doSomeWorkWithProgress];
dispatch_async(dispatch_get_main_queue(), ^{
[hud hideAnimated:YES];
});
});
}
- (void)doSomeWorkWithProgress {
self.canceled = NO;
// This just increases the progress indicator in a loop.
float progress = 0.0f;
while (progress < 1.0f) {
if (self.canceled)
{
NSLog(@"dfdfdfdf");
break;
}
progress += 0.01f;
dispatch_async(dispatch_get_main_queue(), ^{
// Instead we could have also passed a reference to the HUD
// to the HUD to myProgressTask as a method parameter.
[MBProgressHUD HUDForView:self.navigationController.view].progress = progress;
});
usleep(50000);
}
}
- (void)annularDeterminateExample {
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
hud.mode = MBProgressHUDModeAnnularDeterminate;
hud.label.text = NSLocalizedString(@"Loading...", @"HUD loading title");
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
[self doSomeWorkWithProgress];
dispatch_async(dispatch_get_main_queue(), ^{
[hud hideAnimated:YES];
});
});
}
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
// Set the bar determinate mode to show task progress.
hud.mode = MBProgressHUDModeDeterminateHorizontalBar;
hud.label.text = NSLocalizedString(@"Loading...", @"HUD loading title");
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
// Do something useful in the background and update the HUD periodically.
[self doSomeWorkWithProgress];
dispatch_async(dispatch_get_main_queue(), ^{
[hud hideAnimated:YES];
});
});
首先它把所有的类都写在一个文件了。看起来文件不多。其实人家是没分开。我把它分开了。如图
我们一步一步的看它怎么走。这是最普通的,也是默认的样式,菊花转哈哈,大体的逻辑就是show之后hidden。
// 类方法创建一个hud
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
// 开启多线程执行一个任务
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//耗时操作
[self doSomeWork];
// 主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
[hud hideAnimated:YES];
});
});
咱们先看看show。我们走进showHUDAddedTo看看
#pragma mark- 将hud加到这个view上,咱们传的是导航控制器的view,然后有动画效果
+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated {
//创建hud
MBProgressHUD *hud = [[self alloc] initWithView:view];
//如果hud显示完了,消失后将hud移除
hud.removeFromSuperViewOnHide = YES;
//将hud加到view上
[view addSubview:hud];
//显示动画效果
[hud showAnimated:animated];
return hud;
}
去initWithView看看
- (id)initWithView:(UIView *)view {
//断言,如果view是nil,就直接奔溃,NSAssert后面传两个参数如果第一个参数是no,直接奔了。yes那就继续走。
NSAssert(view, @"View must not be nil.");
//看: 它又去调用initWithFrame了,initWithView把它包装起来了,我看头文件把这个接口也开了,用户可以直接调用initWithView去创建一个hud。
//view.bounds,咱们的hud跟传进来的view一样大
return [self initWithFrame:view.bounds];
}
去initWithFrame看看,发现就是初始化commonInit
- (instancetype)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
//公共的初始化
[self commonInit];
}
return self;
}
- (void)commonInit {
// 1.设置一些默认的值
//动画类型默认的是淡入淡出
_animationType = MBProgressHUDAnimationFade;
//hud模式:默认的菊花转
_mode = MBProgressHUDModeIndeterminate;
//边缘的距离
_margin = 20.0f;
//hud的不透明度1,就是yes
_opacity = 1.f;
//启用时,边框中心稍微有些设备加速度计数据的影响。在iOS < 7.0没有影响。默认值为YES,意思就是你晃动手机,hud会变化。
_defaultMotionEffectsEnabled = YES;
//默认的内容颜色,根据系统的版本确定
BOOL isLegacy = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0;
_contentColor = isLegacy ? [UIColor whiteColor] : [UIColor colorWithWhite:0.f alpha:0.7f];
//透明的背景,先让hud看不见,等着show的时候再看到
//opaque的讲解http://www.cnblogs.com/xiaofeixiang/p/5149765.html,如果是透明的,应该设置opaque为NO,默认是yes的
self.opaque = NO;
self.backgroundColor = [UIColor clearColor];
self.alpha = 0.0f;
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.layer.allowsGroupOpacity = NO;
//初始化子控件
[self setupViews];
//更新指示器
[self updateIndicators];
// 通知监听屏幕的旋转,来改变hud的frame
[self registerForNotifications];
}
去setupViews看看,就是hud里的一些子控件,如文本了啥的。
- (void)setupViews {
UIColor *defaultColor = self.contentColor;
//没有模糊效果,透明的背景,跟hud一样大
//这个类就是设置hud的背景,如果是iOS7以下背景就是一个普通颜色,如果是iOS7就用UIToolbar的毛玻璃效果,如果iOS7以上就用UIVisualEffectView作为毛玻璃
MBBackgroundView *backgroundView = [[MBBackgroundView alloc] initWithFrame:self.bounds];
backgroundView.style = MBProgressHUDBackgroundStyleSolidColor;
backgroundView.backgroundColor = [UIColor clearColor];
backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
backgroundView.alpha = 0.f;
[self addSubview:backgroundView];
//read属性,所以不能调用set方法,@property (strong, nonatomic, readonly) MBBackgroundView *backgroundView;不能再外头改。
_backgroundView = backgroundView;
// 边框view,里头放有菊花,文字,进度条,模糊的
MBBackgroundView *bezelView = [MBBackgroundView new];
bezelView.translatesAutoresizingMaskIntoConstraints = NO;
bezelView.layer.cornerRadius = 5.f;
bezelView.alpha = 0.f;
[self addSubview:bezelView];
_bezelView = bezelView;
// 给bezelView加运动效果,当设备上下左右倾斜会产生视差效果
[self updateBezelMotionEffects];
//标题
UILabel *label = [UILabel new];
label.adjustsFontSizeToFitWidth = NO;
label.textAlignment = NSTextAlignmentCenter;
label.textColor = defaultColor;
label.font = [UIFont boldSystemFontOfSize:MBDefaultLabelFontSize];
label.opaque = NO;
label.backgroundColor = [UIColor clearColor];
_label = label;
//标题详情
UILabel *detailsLabel = [UILabel new];
detailsLabel.adjustsFontSizeToFitWidth = NO;
detailsLabel.textAlignment = NSTextAlignmentCenter;
detailsLabel.textColor = defaultColor;
detailsLabel.numberOfLines = 0;
detailsLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize];
detailsLabel.opaque = NO;
detailsLabel.backgroundColor = [UIColor clearColor];
_detailsLabel = detailsLabel;
UIButton *button = [MBProgressHUDRoundedButton buttonWithType:UIButtonTypeCustom];
button.titleLabel.textAlignment = NSTextAlignmentCenter;
button.titleLabel.font = [UIFont boldSystemFontOfSize:MBDefaultDetailsLabelFontSize];
[button setTitleColor:defaultColor forState:UIControlStateNormal];
_button = button;
for (UIView *view in @[label, detailsLabel, button]) {
view.translatesAutoresizingMaskIntoConstraints = NO;
[view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal];
[view setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical];
[bezelView addSubview:view];
}
UIView *topSpacer = [UIView new];
topSpacer.translatesAutoresizingMaskIntoConstraints = NO;
topSpacer.hidden = YES;
[bezelView addSubview:topSpacer];
_topSpacer = topSpacer;
UIView *bottomSpacer = [UIView new];
bottomSpacer.translatesAutoresizingMaskIntoConstraints = NO;
bottomSpacer.hidden = YES;
[bezelView addSubview:bottomSpacer];
_bottomSpacer = bottomSpacer;
}
去updateIndicators看看,这个里头主要是创建指示器的,比如你看到的菊花,进度条,选择不同的MBProgressHUDMode模式,展示的指示器不同。
- (void)updateIndicators {
// typedef NS_ENUM(NSInteger, MBProgressHUDMode) {
// /// 默认模式,使用系统自带的指示器 ,不能显示进度,只能是菊花不停地转呀转
// MBProgressHUDModeIndeterminate,
// /// 用饼图显示进度
// MBProgressHUDModeDeterminate,
// /// 进度条
// MBProgressHUDModeDeterminateHorizontalBar,
// /// 圆环
// MBProgressHUDModeAnnularDeterminate,
// /// 自定义视图
// MBProgressHUDModeCustomView,
// /// 只显示文字
// MBProgressHUDModeText
// };
//先拿到指示器
UIView *indicator = self.indicator;
//指示器是菊花吗?
BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]];
// 指示器是进度条吗?
BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]];
// 获取hud的模式
MBProgressHUDMode mode = self.mode;
// 默认模式
if (mode == MBProgressHUDModeIndeterminate) {
//如果不是菊花就走下面的代码
if (!isActivityIndicator) {
// Update to indeterminate indicator
[indicator removeFromSuperview];
indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[(UIActivityIndicatorView *)indicator startAnimating];
[self.bezelView addSubview:indicator];
}
}
// 进度条
else if (mode == MBProgressHUDModeDeterminateHorizontalBar) {
// 进度条
[indicator removeFromSuperview];
indicator = [[MBBarProgressView alloc] init];
[self.bezelView addSubview:indicator];
}
// 用饼图显示进度或者圆环
else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) {
if (!isRoundIndicator) {
// Update to determinante indicator
[indicator removeFromSuperview];
indicator = [[MBRoundProgressView alloc] init];
[self.bezelView addSubview:indicator];
}
if (mode == MBProgressHUDModeAnnularDeterminate) {
[(MBRoundProgressView *)indicator setAnnular:YES];
}
}
// 自定义视图
else if (mode == MBProgressHUDModeCustomView && self.customView != indicator) {
// Update custom view indicator
[indicator removeFromSuperview];
indicator = self.customView;
[self.bezelView addSubview:indicator];
}
// 只显示文字
else if (mode == MBProgressHUDModeText) {
[indicator removeFromSuperview];
indicator = nil;
}
indicator.translatesAutoresizingMaskIntoConstraints = NO;
self.indicator = indicator;
// 如果是进度条就设置进度---用kvc
if ([indicator respondsToSelector:@selector(setProgress:)]) {
[(id)indicator setValue:@(self.progress) forKey:@"progress"];
}
[indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal];
[indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical];
// 给hud的内容设置颜色。换皮肤
[self updateViewsForColor:self.contentColor];
// 1.[layoutView setNeedsUpdateConstraints]:告诉layoutView需要更新约束,在下次计算或者更新约束会更新约束
// 2.[layoutView updateConstraintsIfNeeded]:告诉layoutView立即更新约束,
// 3.updateConstraints:系统更新约束的实际方法
// 4.[layoutView setNeedsLayout]:告诉layoutView页面需要更新,但不立即执行
// 5.[layoutView layoutIfNeeded]:告诉layoutView页面布局立即更新
// 6.layoutSubviews:系统重写布局的实际方法
// stackoverflow上有关于上面几个方法的深入解答并分享了作者的实用经验:
// 如果仅想要立即改变约束,调用setNeedsLayout
// 如果改变view的一些属性(如offsets)可能会导致布局的改变,那么调用setNeedsUpdateConstraints,更多的时候后面需要加setNeedsLayout。
// 如果想要立即改变布局,如会形成新的frame,那么需要在调用layoutIfNeeded。
// http://www.vienta.me/2015/05/18/AutoLayout-%E6%B5%85%E6%9E%90%E5%8A%A8%E7%94%BB%EF%BC%88III%EF%BC%89/
[self setNeedsUpdateConstraints];
}
初始化说了一大堆。东西有点多啊。要有点耐心,我来回看了好几遍呢MBProgressHUD *hud = [[self alloc] initWithView:view];
算是说完了,咱们接下来就看看这个[hud showAnimated:animated];
看看hud怎么做动画的。
- (void)showAnimated:(BOOL)animated {
//#define MBMainThreadAssert() NSAssert([NSThread isMainThread], @"MBProgressHUD needs to be accessed on the main thread.");
//如果不是在主线程执行这段代码就直接奔溃了。
MBMainThreadAssert();
[self.minShowTimer invalidate];
// 全局的,记录下时候要动画
self.useAnimation = animated;
// hud--show没有完成。
// get方法用self.finished和self.hasFinished都可以,set方法必须是finished。作用语意上更好理解。是否完成。
// @property(nonatomic,assign,getter=hasFinished) BOOL finished;
self.finished = NO;
// graceTime默认是0,当你设置了值之后,如2s,那么hud会在2s之后显示。它的作用是防止任务非常短,hud显示后立马消失,体验效果不好。如果你的任务是在1s内完成,那么hud是不显示的。
if (self.graceTime > 0.0) {
// graceTimer作用就是延迟的去show HUD。
// NSTimer的深入讲解看我的这个文章http://www.jianshu.com/p/19aab8570ce3
NSTimer *timer = [NSTimer timerWithTimeInterval:self.graceTime target:self selector:@selector(handleGraceTimer:) userInfo:nil repeats:NO];
//这种方式创建的timer是不会自动加入NSRunLoop的,得自己添加
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
self.graceTimer = timer;
}
// 如果graceTime是0的话,也就是没设置值,那就直接展示hud
else {
[self showUsingAnimation:self.useAnimation];
}
}
去showUsingAnimation看看
- (void)showUsingAnimation:(BOOL)animated
{
//删除之前的所有动画,防止动画引用view导致内存泄漏,我遇到过这个坑
[self.bezelView.layer removeAllAnimations];
[self.backgroundView.layer removeAllAnimations];
[self.hideDelayTimer invalidate];
//记录hud显示的日期,用来跟hide的date做计算,得出hud展示的时间
self.showStarted = [NSDate date];
self.alpha = 1.0f;
//如果是进度条,那就用CADisplayLink来刷新它,每秒60次的频率来体现进度的变化。
[self setNSProgressDisplayLinkEnabled:YES];
if (animated) {
[self animateIn:YES withType:self.animationType completion:nil];
}else{
// 取消警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
self.bezelView.alpha = self.opacity;
#pragma clang diagnostic pop
self.backgroundView.alpha = 1.f;
}
}
去[self animateIn:YES withType:self.animationType completion:nil];
看看
/**
@param animatingIn show就是yes,hide是no
@param type 动画类型三种,淡入淡出,放大,缩小
*/
- (void)animateIn:(BOOL)animatingIn withType:(YBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion
{
//自动确定正确的缩放动画类型
if (type == YBProgressHUDAnimationZoom) {
type = animatingIn ? YBProgressHUDAnimationZoomIn : YBProgressHUDAnimationZoomOut;
}
CGAffineTransform small = CGAffineTransformMakeScale(0.5f, 0.5f);
CGAffineTransform large = CGAffineTransformMakeScale(1.5f, 1.5f);
//设置开始的状态
UIView *bezelView = self.bezelView;
if (animatingIn && bezelView.alpha == 0.f && type == YBProgressHUDAnimationZoomIn) {
bezelView.transform = small;
}else if (animatingIn && bezelView.alpha == 0.f && type == YBProgressHUDAnimationZoomOut)
{
bezelView.transform = large;
}
//执行动画
dispatch_block_t animations = ^{
if (animatingIn) {
bezelView.transform = CGAffineTransformIdentity;
} else if (!animatingIn && type == YBProgressHUDAnimationZoomIn) {
bezelView.transform = large;
} else if (!animatingIn && type == YBProgressHUDAnimationZoomOut) {
bezelView.transform = small;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
bezelView.alpha = animatingIn ? self.opacity : 0.f;
#pragma clang diagnostic pop
self.backgroundView.alpha = animatingIn ? 1.f : 0.f;
};
//如果是iOS7+就执行spring动画,否则就执行下面的动画
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) {
[UIView animateWithDuration:0.3 delay:0. usingSpringWithDamping:1.f initialSpringVelocity:0.f options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion];
return;
}
#endif
[UIView animateWithDuration:0.3 delay:0. options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion];
}
show方法就说完了。重点啥的都在代码的注释里了。
hide 让HUD消失
- (void)hideAnimated:(BOOL)animated {
//如果要不是在主线程就奔溃(断言)
YBMainThreadAssert();
//把延迟显示hud的定时器销毁,因为showAnimated的时候如果graceTime>0会被创建.
[self.graceTimer invalidate];
self.useAnimation = animated;
self.finished = YES;
//如果设置了minShow时间,计算出HUD显示的时间长短interv,如果interv小于minShow,那么推迟隐藏,也就是minShow之后隐藏。这么做也是为了防止hud刚显示就隐藏导致效果不好。
if (self.minShowTime > 0.0 && self.showStarted)
{
//任务所用的时间,这个就是通过show的日期和hide的日期差值得出来的
NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:self.showStarted];
if (interv < self.minShowTime)
{
//推迟隐藏hud
NSTimer *timer = [NSTimer timerWithTimeInterval:(self.minShowTime - interv) target:self selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
self.minShowTimer = timer;
return;
}
}
//立即隐藏hud
[self hideUsingAnimation:self.useAnimation];
}
去hideUsingAnimation看看, [self animateIn:NO withType:self.animationType completion:^(BOOL finished)这个在show里讲了
- (void)hideUsingAnimation:(BOOL)animated {
if (animated && self.showStarted) {
self.showStarted = nil;
[self animateIn:NO withType:self.animationType completion:^(BOOL finished) {
[self done];
}];
}else{
self.showStarted = nil;
self.bezelView.alpha = 0.0f;
self.backgroundView.alpha = 1.0f;
[self done];
}
}
看看done
- (void)done {
//这个hideDelayTimer是hide方法执行后,延迟隐藏hud的,
[self.hideDelayTimer invalidate];
//将CADisplayLink给释放了
[self setNSProgressDisplayLinkEnabled:NO];
if (self.hasFinished) {
self.alpha = 0.0f;
//将hud移除
if (self.removeFromSuperViewOnHide) {
[self removeFromSuperview];
}
}
//hide完hud后的回调,有block和delegate两种。
MBProgressHUDCompletionBlock completionBlock = self.completionBlock;
if (completionBlock) {
completionBlock();
}
id delegate = self.delegate;
if ([delegate respondsToSelector:@selector(hudWasHidden:)]) {
[delegate performSelector:@selector(hudWasHidden:) withObject:self];
}
}
CADisplayLink刷新
- (void)setNSProgressDisplayLinkEnabled:(BOOL)enabled {
// 这里使用 CADisplayLink 来刷新progress的变化。因为如果使用kvo机制来监听的话可能会非常消耗主线程(因为频率可能非常快)。
if (enabled && self.progressObject) {
// Only create if not already active.
if (!self.progressObjectDisplayLink) {
self.progressObjectDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateProgressFromProgressObject)];
}
} else {
self.progressObjectDisplayLink = nil;
}
}
- (void)setProgressObjectDisplayLink:(CADisplayLink *)progressObjectDisplayLink {
if (progressObjectDisplayLink != _progressObjectDisplayLink) {
[_progressObjectDisplayLink invalidate];
_progressObjectDisplayLink = progressObjectDisplayLink;
//添加到NSRunLoop,主消息循环
[_progressObjectDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
}
参考文章progresshttp://www.jianshu.com/p/c35a81c3b9eb
绘制进度条
MBProgressHUD的绘制都是在drawRect里的然后调用setNeedsDisplay刷新。这个没啥好说的。http://www.jianshu.com/p/d379dbcaa867
保护老属性
为了保护老的属性,做成@interface MBProgressHUD (Deprecated)分类。在分类的属性的set和get方法里设置新属性和返回新属性,在你用分类的属性的时候给你提示这个属性弃用,用新的。如这个demo
#import
@interface Person : NSObject
@property(nonatomic,strong)NSString *name;
@end
@interface Person (Dep)
@property(nonatomic,strong)NSString *namehaha;
@end
#import "Person.h"
@implementation Person
@end
@implementation Person (Dep)
- (NSString *)namehaha{
return self.name;
}
- (void)setNamehaha:(NSString *)namehaha
{
self.name = namehaha;
}
@end
收获的知识
哪些接口哪些属性需要开放。哪些是在属性里强制read的。保护其封装性。
graceTimer,hideDelayTimer,minShowTimer用的到位。
高频率刷新CADisplayLink的使用。
NSAssert断言的使用。
5.为了保护老的属性,做成@interface MBProgressHUD (Deprecated)
分类。在分类的属性的set和get方法里设置新属性和返回新属性,在你用分类的属性的时候给你提示这个属性弃用,用新的,这个第一次看见。很好支持之后的版本。还可以这么用啊。佩服。
总结
好几次看的实在是麻烦,想想如果我看完了它,会成长好多,以后其他的三方简单的控件就会简单很多。其实看框架也是磨性子的过程。让我能够静下心来去学习一些东西。