所闻所获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、至此完成了下拉刷新控件的框架,下一篇博文会分析这个控件里的动画效果。

时间: 2024-09-29 16:29:23

所闻所获3:下拉刷新控件1的相关文章

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

在上一篇博文讨论了下拉刷新控件的框架,这一篇博文将会主要讨论刷新过程中控件的动画效果. 1.首先回顾一下在GMPullToRefresh类中的初始化方法: - (id)initWithScrollView:(UIScrollView *)scrollView { //初始化 ... //定制提示文字 ... //矩形上升动画图 self.activityView=[self activityIndicatorView]; //圆圈转动动画 self.circleView=[[CircleProg

自定义下拉刷新控件

一.功能效果 1.在很多app中,在信息展示页面,当我们向下拖拽时,页面会加载最新的数据,并有一个短暂的提示控件出现,有些会有加载进度条,有些会记录加载日期.条目,有些还带有加载动画.其基本实现原理都相仿,本文中将探讨其实现原理,并封装出一个简单的下拉刷新控件 2.自定义刷新工具简单的示例 二.系统提供的下拉刷新工具 1.iOS6.0以后系统提供了自己的下拉刷新的控件:UIRefreshControl .例如,refreshControl,作为UITableViewController中的一个属

[Android]下拉刷新控件RefreshableView的实现

需求:自定义一个ViewGroup,实现可以下拉刷新的功能.下拉一定距离后(下拉时显示的界面可以自定义任何复杂的界面)释放手指可以回调刷新的功能,用户处理完刷新的内容后,可以调用方法onCompleteRefresh()通知刷新完毕,然后回归正常状态.效果如下:     源代码:RefreshableView(https://github.com/wangjiegulu/RefreshableView) 分析: 我们的目的是不管什么控件,只要在xml中外面包一层标签,那这个标签下面的所有子标签所

Android 解决下拉刷新控件和ScrollVIew的滑动冲突问题。

最近项目要实现ScrollView中嵌套广告轮播图+RecyleView卡片布局,并且RecyleView按照header和内容的排列样式,因为RecyleView的可扩展性很强,所以我毫无疑问的选择了它,而且让RecyleView实现了可拖拽的效果, 最后我再加上了下拉刷新的效果(这里我用的下拉刷新控件是三方的SmartRefreshLayout).记得刚开始实现这个效果的时候还是十分的得心印手.可是当我测试的时候,发现RecyleView的子item的拖拽效果并不流畅,起初我以 为是由于Re

iOS:下拉刷新控件UIRefreshControl的详解

下拉刷新控件:UIRefreshControl 1.具体类信息: @interface UIRefreshControl : UIControl //继承控制类 - (instancetype)init; @property (nonatomic, readonly, getter=isRefreshing) BOOL refreshing; //是否可以刷新 @property (nonatomic, retain) UIColor *tintColor; //控件颜色 @property (

Android下拉刷新控件--PullToRefresh的简单使用

Android中很多时候都会用到上下拉刷新,这是一个很常用的功能,Android的v4包中也为我们提供了一种原生的下拉刷新控件--SwipeRefreshLayout,可以用它实现一个简洁的刷新效果,但今天我们的主角并不是它,而是一个很火的第三方的上下拉刷新控件--PullToRefresh.PullToRefresh包括PullToRefreshScrollView.PullToRefreshListView.PullToRefreshGridView等等很多为我们提供的控件,我们可以在xml

Android下拉刷新控件SwipeRefreshLayout源码浅析

SwipeRefreshLayout是Android官方的下拉刷新控件,使用简单,界面美观,不熟悉的朋友可以随便搜索了解一下,这里就不废话了,直接进入正题. 这种下拉刷新控件的原理不难,基本就是监听手指的运动,获取手指的坐标,通过计算判断出是哪种操作,然后就是回调相应的接口了.SwipeRefreshLayout是继承自ViewGroup的,根据Android的事件分发机制,触摸事件应该是先传递到ViewGroup,根据onInterceptTouchEvent的返回值决定是否拦截事件的,那么就

Android——谷歌官方下拉刷新控件SwipeRefreshLayout(转)

转自:http://blog.csdn.net/zouzhigang96/article/details/50476402 版权声明:本文为博主原创文章,未经博主允许不得转载. 前言: 如今谷歌推出了更官方的下拉刷新控件, 这无疑是对安卓开发人员来说是个好消息,很方便的使用这个SwipeRefreshLayout控件实现下拉刷新功能.Android4.0以下的版本需要用到 Android-support-v4.jar包才能用到 android-support-v4.jar 包下载地址:http:

Android仿苹果版QQ下拉刷新实现(一) ——打造简单平滑的通用下拉刷新控件

前言: 因为公司人员变动原因,导致了博主四个月没有动安卓,一直在做IOS开发,如今接近年前,终于可以花一定的时间放在安卓上了.好了,废话不多说,今天我们要带来的效果是苹果版本的QQ下拉刷新.首先看一下目标效果以及demo效果:      因为此效果实现的步骤较多,所以今天博主要实现以上效果的第一步——打造一个通用的下拉刷新控件,具体效果如下: GIF图片比较大,还希望读者能耐心等待一下下从效果图中可以看出,我们的下拉刷新的滑动还是很流畅的,可能大多数开发者用的是XListview或者PullTo