您的位置:首页 > 移动开发 > Objective-C

所闻所获4:下拉刷新控件2

2015-08-08 16:16 375 查看
  在上一篇博文讨论了下拉刷新控件的框架,这一篇博文将会主要讨论刷新过程中控件的动画效果。

 

1、首先回顾一下在GMPullToRefresh类中的初始化方法:

- (id)initWithScrollView:(UIScrollView *)scrollView {
//初始化
...

//定制提示文字
...

//矩形上升动画图
self.activityView=[self activityIndicatorView];

//圆圈转动动画
self.circleView=[[CircleProgessView alloc] initWithFrame:CGRectMake(self.titleLabel.frame.origin.x-30-5, self.bounds.size.height*0.5-15, 30, 30)];
[self addSubview:self.circleView];

//指定state
...

return self;
}


  其中分别有两个动画,一个是矩形上升动画,一个是圆形转圈动画。先来看看矩形上升动画,这个动画的基本原理是这样的:它放置了两张图片,蓝色图片在后、白色图片在前,动画的过程就是让白色图片的高度变小,让它从下往上地缩小,造成蓝色图片从下往上上升的效果。它的设置方法如下:

- (GMActivityView *)activityIndicatorView {
if(!_activityView) {
_activityView = [[GMActivityView alloc] initWithFrame:CGRectMake(self.titleLabel.frame.origin.x-30, self.bounds.size.height*0.5-10, 20, 20)];
_activityView.hidesWhenStopped = NO;
[self addSubview:_activityView];
}
return _activityView;
}


  可以看到,这是实例化了一个GMActivityView类的对象。

 

2、在GMActivityView类中的初始化方法如下:

- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self configSubViews];
}
return self;
}

- (void)configSubViews {
//用一张白色的图片先挡在蓝色图片上面
self.frontView=[[UIView alloc] initWithFrame:self.bounds];
UIImageView *fImage=[[UIImageView alloc] initWithFrame:self.frontView.bounds];;
fImage.image=[UIImage imageNamed:FRONT_IMAGE];
[self.frontView addSubview:fImage];
self.frontView.backgroundColor=[UIColor clearColor];
self.frontView.clipsToBounds=YES;

//蓝色图片放后面
self.backView=[[UIView alloc] initWithFrame:self.bounds];
UIImageView *bImage=[[UIImageView alloc] initWithFrame:self.backView.bounds];;
bImage.image=[UIImage imageNamed:BACK_IMAGE];
[self.backView addSubview:bImage];
self.backView.backgroundColor=[UIColor clearColor];
self.backView.clipsToBounds=YES;

[self addSubview:self.backView];
[self addSubview:self.frontView];
}


 

3、那么就初始化好了这个页面,在上一篇博文中已经提到,在scrollView拖动的过程中,会不断调用到GMPullToRefresh类的scrollViewDidScroll:方法,并且将scrollView实时的contentOffset传进来。而在scrollViewDidScroll:方法中,会不断根据实时的contentOffset判断控件应处的state,如有需要就调用setState:方法修改控件的state。我们通过这两个方法来解析矩形的所有动画效果:

(1)、scrollViewDidScroll:方法,它除了根据实时的contentOffset修改state之外,还有一部分代码是用来根据contentOffset和state去定制不同的动画:

- (void)scrollViewDidScroll:(CGPoint)contentOffset {
//根据实时的contentOffset设置state
...

if (self.state==GMPullToRefreshStateHidden||self.state==GMPullToRefreshStateVisible) {
//取绝对值
float moveY = fabs(self.scrollView.contentOffset.y);
if (moveY > kFrameHeight)
moveY = kFrameHeight;
//根据拖动距离和触发距离的比例,来定制圆弧的长度
...
//根据拖动距离和触发距离的比例,来定制矩形的高度
[self.activityView drawViewWithProgress:(moveY/kFrameHeight)];

} else if (self.state==GMPullToRefreshStateTriggered) {
...
}
}


  在上面的方法中,使用了drawViewWithProgress:方法,这个方法是根据拖动距离和触发距离的比例来定制矩形高度的。效果就是,在拖动到触发之前,矩形会随着拖动这个动作上升,它的代码如下:

- (void)drawViewWithProgress:(CGFloat)progress{
if (progress>1) {
progress=1;
}
if (progress<0) {
progress=0;
}

CGRect frontViewFrame = self.frontView.frame;
CGFloat frontViewHeight = self.frame.size.height*(1-progres
ec1d
s);
[self.frontView setFrame:CGRectMake(frontViewFrame.origin.x, frontViewFrame.origin.y, frontViewFrame.size.width, frontViewHeight)];
}


 

(2)、setState:方法,这个方法在设定state之后,就会根据不同的state去定制动画了。这个方法中关于动画的代码如下:

- (void)setState:(GMPullToRefreshState)newState {
_state = newState;
switch (newState) {
case GMPullToRefreshStateHidden:
...
[self.activityView stopAnimation];
self.activityView.isFull = NO;
...
break;

case GMPullToRefreshStateVisible:
...
[self.activityView stopAnimation];
self.activityView.isFull = NO;
break;

case GMPullToRefreshStateTriggered:
...
self.activityView.isFull = YES;
break;

case GMPullToRefreshStateLoading:
...
[self.activityView startAnimation];
...
break;
}
}


  其中总共调用到了GMActivityView 类的3个方法:startAnimation、isFull和stopAnimation。这3个方法就是矩形图形的所有动画效果了,它们的代码如下:

- (void)startAnimation {
__weak GMActivityView *weakSelf = self;
self.hidden = NO;
self.isFull = NO;
self.isStop = NO;
CGRect rect = self.bounds;
rect.size.height = 0;

[UIView animateWithDuration:SPEED_TIME delay:SPEED_DELAY options:UIViewAnimationOptionCurveLinear animations:^{
//frontView白色图片从完全显示变化到高度为0,出现了蓝色图片高度增加的效果
weakSelf.frontView.frame = rect;
} completion:^(BOOL finished) {
if (weakSelf.isStop) {
return;
}
//不断循环
[weakSelf startAnimation];
}];
}

//这个方法指定的是白色图片的高度:当!isFull的时候,白色图片完全显示,蓝色图片看不见;当isFull的时候,白色图片不见,蓝色图片完全显示。
- (void)setIsFull:(BOOL)isFull {
_isFull = isFull;
if (!isFull) {
self.frontView.frame = self.bounds;
}else {

CGRect rect = self.bounds;
rect.size.height = 0;
self.frontView.frame = rect;
}
}

- (void)stopAnimation {
self.isStop = YES;
self.isFull = YES;
if (self.hidesWhenStopped) {
self.hidden = YES;
}
}


  这就是矩形图形的整个动画过程。

 

4、接下来看看转圈圆形的动画,首先回到1中的GMPullToRefresh类的初始化方法:

- (id)initWithScrollView:(UIScrollView *)scrollView {
//初始化
...

//定制提示文字
...

//矩形上升动画图
...

//圆圈转动动画
self.circleView=[[CircleProgessView alloc] initWithFrame:CGRectMake(self.titleLabel.frame.origin.x-30-5, self.bounds.size.height*0.5-15, 30, 30)];
[self addSubview:self.circleView];

//指定state
...

return self;
}


 

5、那么下一步就看看CircleProgessView类的初始化方法:

- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
progress = 0;
}
return self;
}


 

6、这样就把圆弧初始化好了,并指定了它圆弧的完整度为0。它的动画效果和矩形上升图类似,同样是在scrollViewDidScroll:方法中,不断根据实时的contentOffset判断控件应处的state,如有需要就调用setState:方法修改控件的state。同样地,我们通过这两个方法来解析矩形的所有动画效果:

(1)、scrollViewDidScroll:方法,这个方法中定制动画的代码中,关于圆弧的代码不只是转动的。当下拉距离未达到触发距离的过程中,圆弧的动画是从短变长的,在达到触发距离的时候圆弧刚好画完。这个时候如果继续往下拖动,圆弧会开始转动,根据拖动的速度和距离决定圆弧转动的速度和角度。如果放开手,圆弧进入自主转动的动画,这个效果定义在setState:方法里:

- (void)scrollViewDidScroll:(CGPoint)contentOffset {
...

if (self.state==GMPullToRefreshStateHidden||self.state==GMPullToRefreshStateVisible) {
//取绝对值
float moveY = fabs(self.scrollView.contentOffset.y);
if (moveY > kFrameHeight)
moveY = kFrameHeight;
//根据拖动距离和触发距离的比例,来定制圆弧的长度
[self.circleView drawCircleWithProgress:(moveY-kFrameHeight*0.5) / (kFrameHeight*0.5)];
//用同样的方法来定制方形的高度
...

} else if (self.state==GMPullToRefreshStateTriggered) {
[self.circleView drawCircleWithProgress:1.];
//超过触发距离了,圆圈继续转
float moveY=fabs(self.scrollView.contentOffset.y);
self.circleView.transform = CGAffineTransformMakeRotation((moveY-kFrameHeight)/60*360*M_PI/180);
}
}


  可以看到,它引用了CircleProgessView类的drawCircleWithProgress:方法,方法内容如下:

-(void)drawCircleWithProgress:(CGFloat)mProgress{
progress = mProgress;
[self setNeedsDisplay];//这个方法会调用drawRect:方法
}

- (void)drawRect:(CGRect)rect {
if (progress>1) {
progress=1;
}
if (progress<0) {
progress=0;
}
CGContextRef context = UIGraphicsGetCurrentContext();

CGContextSetLineWidth(context, 1);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGFloat startAngle = 0;
CGFloat step = 11.4*M_PI/6 * progress;//弧线对应的角度
CGContextAddArc(context, self.bounds.size.width/2, self.bounds.size.height/2, self.bounds.size.width/2-1, startAngle, startAngle+step, 0);
//CGContextAddArc(CGContextRef c, CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise)
//x和y是原点位置
//radius是半径
//startAngle为0,即是以坐标原点为圆心时x轴的方向
//endAngle为45*(M_PI/180)是往下方偏移45度
//clockwise为0是顺时针画,为1是逆时针画

CGContextStrokePath(context);
}


 

(2)、然后再来看看setState:方法,它会根据不同的state定制不同的动画:

- (void)setState:(GMPullToRefreshState)newState {
_state = newState;
switch (newState) {
case GMPullToRefreshStateHidden:
[self.circleView stopAnimation];
...
break;

case GMPullToRefreshStateVisible:
...
[self.circleView stopAnimation];
...
break;

case GMPullToRefreshStateTriggered:
...
self.circleView.transform = CGAffineTransformMakeRotation(0);
...
break;

case GMPullToRefreshStateLoading:
...
self.circleView.transform = CGAffineTransformMakeRotation(0);
[self.circleView startAnimation];
...
break;
}
}


  主要调用到了startAnimation方法和stopAnimation方法,它们的代码如下:

-(void)startAnimation {
[self drawCircleWithProgress:1];

//绕z轴旋转
CABasicAnimation* rotate =  [CABasicAnimation animationWithKeyPath: @"transform.rotation.z"];
rotate.removedOnCompletion = FALSE;
rotate.fillMode = kCAFillModeForwards;
[rotate setToValue: [NSNumber numberWithFloat: M_PI / 2]];
rotate.repeatCount = HUGE_VALF;

rotate.duration = 0.25;
rotate.cumulative = TRUE;

//控制动画速度
rotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
//kCAMediaTimingFunctionLinear 线性(匀速)|
//kCAMediaTimingFunctionEaseIn 先慢|
//kCAMediaTimingFunctionEaseOut 后慢|
//kCAMediaTimingFunctionEaseInEaseOut 先慢 后慢 中间快|
//kCAMediaTimingFunctionDefault 默认|

[self.layer addAnimation:rotate forKey:@"rotateAnimation"];
}

-(void)stopAnimation{
[self.layer removeAnimationForKey:@"rotateAnimation"];
}


  至此完成了圆弧转圈的动画。

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息