zoukankan      html  css  js  c++  java
  • 所闻所获3:下拉刷新控件1

      本文主要是讨论在最近项目中遇到的一个下拉刷新控件,这个控件的效果如下图:

     

      在这里会用两篇博文的篇幅来解析这个控件,第一篇解析控件的框架,第二篇解析动画。源代码可以在下面的链接下载:

    TestPullToRefresh.zip 

     

    1、这个控件由以下几个文件组成:GMPullToAction、CircleProgressView、GMActivityView,其中GMPullToAction文件包含两个类:GMPullToRefresh和UIScrollView (GMPullToAction),CircleProgressView和GMActivityView各自包含一个同名的类。

      在这4个类中,GMPullToRefresh和UIScrollView (GMPullToAction)是控件的框架,CircleProgressView和GMActivityView负责动画。

    2、这个控件定义在UIScrollView (GMPullToAction)内,所以使用方必须是UIScrollView或者它的子类的实例,使用时需要调用3个方法(假设当前控制器self有一属性scrollView):

    [self.scrollView addPullToRefreshWithActionHandler:^{
        //下拉刷新时执行的代码
        ...
    }];

      然后需要实现UIScrollViewDelegate的一个代理方法:

    //在scrollView拖动的时候会不断调用这个方法
    - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
        if (scrollView == self.scrollView) {
            [scrollView didScroll];
        }
    }

      最后在加载完成后,还要调用以下这个方法来停用控件:

    [self.scrollView.pullToRefreshView stopAnimating];

      我们就以这3个方法为入口来解析这个控件。

    3、首先来看第一个方法- (void)addPullToRefreshWithActionHandler:(void (^)(void))actionHandler方法:

    (1)、这个方法定义在UIScrollView (GMPullToAction)类中,它的代码如下:

    - (void)addPullToRefreshWithActionHandler:(void (^)(void))actionHandler {
        GMPullToRefresh *pullToRefreshView = [[GMPullToRefresh alloc] initWithScrollView:self];
        pullToRefreshView.actionHandler = actionHandler;
        self.pullToRefreshView = pullToRefreshView;
    }

      它使用initWithScrollView方法实例化了一个GMPullToRefresh类的对象pullToRefreshView,将块参数指定给pullToRefreshView.actionHandler,让pullToRefreshView可以在后续使用这段代码,最后把pullToRefreshView指定给自己的属性self.pullToRefreshView。

      这里需要注意一下,UIScrollView (GMPullToAction)类是一个分类,是不允许直接定义属性的,所以需要用到另外的方法来实现属性的效果,具体代码如下:

    @interface UIScrollView (GMPullToAction)
    @property (nonatomic, strong) GMPullToRefresh *pullToRefreshView;
    @end
    ...
    #import <objc/runtime.h>
    static char UIScrollViewPullToRefreshView;
    @implementation UIScrollView (GMPullToAction)
    @dynamic pullToRefreshView;
    ...
    - (void)setPullToRefreshView:(GMPullToRefresh *)pullToRefreshView {
        [self willChangeValueForKey:@"pullToRefreshView"];
        objc_setAssociatedObject(self, &UIScrollViewPullToRefreshView,
                                 pullToRefreshView,
                                 OBJC_ASSOCIATION_ASSIGN);
        [self didChangeValueForKey:@"pullToRefreshView"];
    }
     
    - (GMPullToRefresh *)pullToRefreshView {
        return objc_getAssociatedObject(self, &UIScrollViewPullToRefreshView);
    }
    ...
    @end

    (2)、然后继续看GMPullToRefresh类的实例化方法initWithScrollView:

    - (id)initWithScrollView:(UIScrollView *)scrollView {
        //初始化
        self = [super initWithFrame:CGRectZero];
        self.scrollView = scrollView;
        self.frame = CGRectMake(0, -kFrameHeight, scrollView.bounds.size.width, kFrameHeight);
        [_scrollView addSubview:self];
    
        //定制提示文字
        self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(kContent_Width/2.f-75/4.f, self.bounds.size.height*0.5-10, 75, 20)];
        _titleLabel.font = [UIFont boldSystemFontOfSize:14];
        _titleLabel.backgroundColor = [UIColor clearColor];
        _titleLabel.textColor = kTextColor;
        [self addSubview:_titleLabel];
       
        //矩形上升动画图
        ...
       
        //圆圈转动动画
        ...
       
        //指定state
        self.state = GMPullToRefreshStateHidden;
       
        return self;
    }

      其中两个负责动画的类先不讨论,主要看self.state。它是一个枚举值,用来记录控件的状态,它的定义如下:

    enum {
        GMPullToRefreshStateHidden = 1,  //隐藏
        GMPullToRefreshStateVisible,     //可见
        GMPullToRefreshStateTriggered,   //已触发刷新
        GMPullToRefreshStateLoading      //已在加载
    };
    
    typedef NSUInteger GMPullToRefreshState;

    4、然后看第二个入口方法- (void)scrollViewDidScroll:(UIScrollView *)scrollView:

    (1)、这个方法会在scrollView拖动的时候不断被调用,在这个方法中主要是执行了代码[scrollView didScroll]。- (void)didScroll方法也是定义在UIScrollView (GMPullToAction)类中,它的代码如下:

    - (void)didScroll {
        //这个self是指scrollView
        CGPoint point=self.contentOffset;
        if (self.pullToRefreshView) {
            [self.pullToRefreshView scrollViewDidScroll:point];
        }
    }

      在这个方法中,将scrollView实时的contentOffset传给了GMPullToRefresh的方法- (void)scrollViewDidScroll:(CGPoint)contentOffset;

    (2)、GMPullToRefresh的- (void)scrollViewDidScroll:(CGPoint)contentOffset方法代码如下:

    - (void)scrollViewDidScroll:(CGPoint)contentOffset {
        if (self.state == GMPullToRefreshStateLoading) {
            return;
        }
       
        //起点的y值(负数),它的绝对值也是pullToRefreshView的高度,也是触发刷新状态的高度
        CGFloat scrollOffsetThreshold = self.frame.origin.y;
       
        //已经触发刷新并且放开拖动了,就变成加载状态
        if(self.state == GMPullToRefreshStateTriggered && !self.scrollView.isDragging){
            self.state = GMPullToRefreshStateLoading;
           
        //scrollView开始往下拖动(<0),并且未达到触发刷新的高度(>scrollOffsetThreshold),为可见状态
        }else if(contentOffset.y < 0 &&  contentOffset.y > scrollOffsetThreshold && self.scrollView.isDragging && self.state != GMPullToRefreshStateLoading){
            self.state = GMPullToRefreshStateVisible;
           
        //scrollView往下拖动到已触发刷新的高度(<scrollOffsetThreshold)并且还没放手,为触发状态
        }else if(contentOffset.y <= scrollOffsetThreshold && self.scrollView.isDragging && self.state == GMPullToRefreshStateVisible){
            self.state = GMPullToRefreshStateTriggered;
           
        //scrollView往上推,为隐藏状态
        }else if(contentOffset.y >= 0 && self.state != GMPullToRefreshStateHidden){
            self.state = GMPullToRefreshStateHidden;
        }
       
        //拖动过程中的动画效果
        ...
    }

      这个方法根据scrollView实时的contentOffset来决定状态self.state,这会调用self.state的set方法。

    (3)、GMPullToRefresh的setState:方法代码如下:

    - (void)setState:(GMPullToRefreshState)newState {
        _state = newState;
        switch (newState) {
            case GMPullToRefreshStateHidden:
                ... //动画
                [self setScrollViewContentInsetTop:0];            
                break;
               
            case GMPullToRefreshStateVisible:
                _titleLabel.text = NSLocalizedString(@"下拉刷新...",);
                ...
                break;
               
            case GMPullToRefreshStateTriggered:
                _titleLabel.text = NSLocalizedString(@"松开刷新...",);
                ...
                break;
               
            case GMPullToRefreshStateLoading:
                _titleLabel.text = NSLocalizedString(@"正在载入...",);
                ...
                [self setScrollViewContentInsetTop:self.frame.size.height];           
                if(_actionHandler)
                    _actionHandler();
                break;
        }
    }

      可以看到,对于不同的state,会指定不同的文字(指定动画的语句省略了,会在下一篇讨论),而在加载状态,会调用第一个入口方法传进来的块代码,即是执行加载的语句。

    (4)、在上面的方法中,在隐藏状态和加载状态还会调用一个方法setScrollViewContentInset:来指定scrollView的contentInset。当状态为隐藏的时候,将contentInset的top置为0;当状态为加载的时候,将contentInset的top置为pullToRefreshView的高度。并且将这个过程做成动画效果:

    - (void)setScrollViewContentInsetTop:(CGFloat)top {
        UIEdgeInsets inset = self.scrollView.contentInset;
        inset.top = top;
        [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionAllowUserInteraction|UIViewAnimationOptionBeginFromCurrentState animations:^{
            self.scrollView.contentInset = inset;
           
        } completion:^(BOOL finished) {
        }];
    }

    5、最后一个入口方法,是在当数据加载完毕的时候调用的,调用这个方法会把state置为隐藏,让加载画面动画恢复到下拉前状态:

    - (void)stopAnimating {
        self.state = GMPullToRefreshStateHidden;
    }

    6、至此完成了下拉刷新控件的框架,下一篇博文会分析这个控件里的动画效果。

  • 相关阅读:
    client offset screen 的区别
    js中const,var,let区别
    jquery的选择器
    gulp
    JS 实现图片放大效果
    html单个标签实现跑马灯效果
    前端之HTML知识点整理
    各种纯css图标
    防止反复点击的思路
    .NET Memcached Client 扩展获取所有缓存Key
  • 原文地址:https://www.cnblogs.com/shayneyeorg/p/4713221.html
Copyright © 2011-2022 走看看