在上一篇博文討論了下拉刷新控件的框架,這一篇博文將會主要討論刷新過程中控件的動畫效果。
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-progress); [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"]; }
至此完成了圓弧轉圈的動畫。