模仿是学习的必经阶段,所以接下来决定学习开源的代码,来发现自己的不足,学习别人的优点
第一个学习的框架是LHLoadingView开源代码
首先看看项目的代码结构
项目结构很简单,主要包活
1.自定义的view,view里面实现了动画效果
2.view的controller
首先看看controller,里面主要做的使view定时执行动画,代码就只有几行,不复杂
然后我们看看自定义的view,view只有一个 show 方法
@interface LeoLoadingView : UIView
-(void)showView:(BOOL)show;
@end
下面贴上实现代码
#import "LeoLoadingView.h"
#define AnimationTime 0.3
#define kDotSize (CGSizeMake(0.4 * self.frame.size.width, 0.4 * self.frame.size.height))
#define LeftTopPosition (CGPointMake(0, 0))
#define LeftTBottomPosition (CGPointMake(0, 0.6 * self.frame.size.height))
#define RightBottomPosition (CGPointMake(0.6 * self.frame.size.width, 0.6 * self.frame.size.height))
#define RightTopPosition (CGPointMake(0.6 * self.frame.size.width, 0))
#define kDotColor [UIColor colorWithRed:200/255.0 green:206/255.0 blue:221/255.0 alpha:1.0]
@interface LeoLoadingView()
@property (strong, nonatomic) UIView *dotView0,*dotView1,*dotView2;
@property (strong, nonatomic) NSArray *dotViews;
@property (assign, nonatomic) NSInteger dotIndex;
@property (strong, nonatomic) NSTimer *timer;
@end
@implementation LeoLoadingView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.hidden = YES;
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self initView];
}
- (void)initView
{
self.backgroundColor = [UIColor clearColor];
// self.clipsToBounds = YES;
self.userInteractionEnabled = NO;
_dotView0 = [[UIView alloc]initWithFrame:(CGRect){RightBottomPosition, kDotSize}];
_dotView0.backgroundColor = kDotColor;
[self addSubview:_dotView0];
_dotView1 = [[UIView alloc]initWithFrame:(CGRect){LeftTBottomPosition, kDotSize}];
_dotView1.backgroundColor = kDotColor;
[self addSubview:_dotView1];
_dotView2 = [[UIView alloc]initWithFrame:(CGRect){LeftTopPosition, kDotSize}];
_dotView2.backgroundColor = kDotColor;
[self addSubview:_dotView2];
_dotViews = @[_dotView0, _dotView1, _dotView2];
_dotIndex = 0;
}
-(void)showView:(BOOL)show
{
if (show) {
self.hidden = NO;
if (!_timer) {
_timer = [NSTimer timerWithTimeInterval:AnimationTime target:self selector:@selector(beginAnimation) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop]addTimer:_timer forMode:NSRunLoopCommonModes];
}
}
else{
[_timer invalidate];
_timer = nil;
self.hidden = YES;
}
}
-(void)beginAnimation
{
[UIView animateWithDuration:AnimationTime delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
UIView *dotView = _dotViews[_dotIndex];
[self moveDotViewToNextPosition:dotView];
_dotIndex ++;
_dotIndex = _dotIndex > 2 ? 0 : _dotIndex;
} completion:nil];
}
-(void)moveDotViewToNextPosition:(UIView*)dotView
{
if (CGPointEqualToPoint(dotView.frame.origin, LeftTopPosition)) {
dotView.frame = (CGRect){LeftTBottomPosition, dotView.frame.size};
}
else if (CGPointEqualToPoint(dotView.frame.origin, LeftTBottomPosition)) {
dotView.frame = (CGRect){RightBottomPosition, dotView.frame.size};
}
else if (CGPointEqualToPoint(dotView.frame.origin, RightBottomPosition)) {
dotView.frame = (CGRect){RightTopPosition, dotView.frame.size};
}
else if (CGPointEqualToPoint(dotView.frame.origin, RightTopPosition)) {
dotView.frame = (CGRect){LeftTopPosition, dotView.frame.size};
}
}
@end
- (void)layoutSubviews
{
[super layoutSubviews];
[self initView];
}
当自定义view首次加载到父view中时(或者当view是rootview时,屏幕发生旋转;或者view的frame发生改变;又或往view添加subview时),会调用这个方法,通过initView来初始化自定义view。这里有一个有趣的问题,大家猜猜当这个工程启动的时候,layoutSubviews会被调用多少次?通过打断点,我们可以发现是两次。为什么呢?那是因为我们在storyboard里面添加了两个自定义的view,既然有两个view,那么当这两个view添加进父view的时候,自然就会调用两次了~
第二个要关注的方法是
-(void)showView:(BOOL)show
{
if (show) {
self.hidden = NO;
if (!_timer) {
_timer = [NSTimer timerWithTimeInterval:AnimationTime target:self selector:@selector(beginAnimation) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop]addTimer:_timer forMode:NSRunLoopCommonModes];
}
}
else{
[_timer invalidate];
_timer = nil;
self.hidden = YES;
}
}
NSRunLoop是平时比较少接触的概念,下面说说自己查阅资料后的理解
NSRunLoop和线程是分不开的,ios里面每一个线程(包活自己新建的和应用的mian线程)都有一个NSRunLoop对象
一般来说,一个线程当执行一段代码后,就会退出,那如果我想在线程里面循环执行某项任务,那怎么办呢,我们可能会这样写
void threadMethod()
{
while(true)
{
do some job...
sleep(一段时间)
}
}
而ios也提供了类似这样的机制,这就是NSRunLoop了,当NSRunLoop运行起来后,他就会类似上面那段代码那样工作,当有数据源或timer事件向它发送消息的时候,线程就会被唤醒工作,调用我们注册的handler(函数)去执行我们自定义的任务。就像上面showView方法一样,方法里面新建了一个timer,timer会定时地在UIView上执行动画,然后把timer注册到主线程(主线程一般负载界面的更新和重绘)的NSRunLoop里面,所以每隔一定时间时,timer就能在主线程上执行动画
最后就是通过UIView来实现动画的绘制
-(void)beginAnimation
{
[UIView animateWithDuration:AnimationTime delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
UIView *dotView = _dotViews[_dotIndex];
[self moveDotViewToNextPosition:dotView];
_dotIndex ++;
_dotIndex = _dotIndex > 2 ? 0 : _dotIndex;
} completion:nil];
}
-(void)moveDotViewToNextPosition:(UIView*)dotView
{
if (CGPointEqualToPoint(dotView.frame.origin, LeftTopPosition)) {
dotView.frame = (CGRect){LeftTBottomPosition, dotView.frame.size};
}
else if (CGPointEqualToPoint(dotView.frame.origin, LeftTBottomPosition)) {
dotView.frame = (CGRect){RightBottomPosition, dotView.frame.size};
}
else if (CGPointEqualToPoint(dotView.frame.origin, RightBottomPosition)) {
dotView.frame = (CGRect){RightTopPosition, dotView.frame.size};
}
else if (CGPointEqualToPoint(dotView.frame.origin, RightTopPosition)) {
dotView.frame = (CGRect){LeftTopPosition, dotView.frame.size};
}
}
代码就分析完了,那总得练一下手,于是仿照上面的实现,我自己也些了一段小小的代码,实现的是一个指示器的功能,三个正方形轮流从左到右放大然后缩小
贴上完整的代码
#import "MyOwnView.h"
#define AnimationTime 0.3
#define TimerTime 0.5
#define ViewSize (CGSizeMake(20, 20))
#define View1Position (CGPointMake((self.frame.size.width / 4.0) * 1, 20))
#define View2Position (CGPointMake((self.frame.size.width / 4.0) * 2, 20))
#define View3Position (CGPointMake((self.frame.size.width / 4.0) * 3, 20))
@interface MyOwnView()
@property(nonatomic, strong) UIView* view1;
@property(nonatomic, strong) UIView* view2;
@property(nonatomic, strong) UIView* view3;
@property(nonatomic, strong) NSArray *views;
@property(nonatomic, strong) NSTimer* timer;
@property(nonatomic, assign) int count;
-(void)doAnimation;
@end
@implementation MyOwnView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self initViews];
}
return self;
}
- (void)initViews
{
_view1 = [[UIView alloc]initWithFrame:(CGRect){View1Position, ViewSize}];
_view1.backgroundColor = [UIColor greenColor];
[self addSubview:_view1];
_view2 = [[UIView alloc]initWithFrame:(CGRect){View2Position, ViewSize}];
_view2.backgroundColor = [UIColor greenColor];
[self addSubview:_view2];
_view3 = [[UIView alloc]initWithFrame:(CGRect){View3Position, ViewSize}];
_view3.backgroundColor = [UIColor greenColor];
[self addSubview:_view3];
_views = @[_view1, _view2, _view3];
}
- (void)drawRect:(CGRect)rect
{
NSLog(@"drawRect");
}
-(void)layoutSubviews
{
// [self initViews];
NSLog(@"layoutSubviews");
}
-(void)showView:(BOOL)show
{
if(show) {
_count = 0;
_timer = [NSTimer timerWithTimeInterval:0.5 target:self selector:@selector(doAnimation) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop]addTimer:_timer forMode:NSDefaultRunLoopMode];
}else {
[_timer invalidate];
}
}
-(void)doAnimation
{
UIView *view = [_views objectAtIndex:_count];
CGAffineTransform t = view.transform;
[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionAutoreverse animations:^{
[view setTransform:CGAffineTransformScale(view.transform, 1.5, 1.5)];
} completion:^(BOOL finish){
[view setTransform:t];
}];
_count++;
_count = _count > 2 ? 0 : _count;
}
@end