所聞所獲4:下拉刷新控件2

  在上一篇博文討論了下拉刷新控件的框架,這一篇博文將會主要討論刷新過程中控件的動畫效果。

 

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"];
}

  至此完成了圓弧轉圈的動畫。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章