SVPullToRefresh深入理解

背景

SVPullToRefresh是iOS上实现下拉刷新和上提加载的一个组件。使用场景仅限UIScrollView,只需要一行代码,就能添加下拉刷新的功能。

[tableView addPullToRefreshWithActionHandler:^{
    // 预加载数据到dataSource, 向tableview插入cell
    // 完成时调用[tableView.pullToRefreshView stopAnimating] 
}];

准备研究之前google了一把SVPullToRefresh,发现除了github的官方地址,排在最前面的居然是我去年写的一篇博客。重新看了一遍,发现自己当时讲的很浅,很多关键的东西都是一笔带过,没有讲清楚。所以今天重写一篇,深入地介绍一下这个优秀的组件。

铺垫

Associative(关联)

一个不可修改的类,如果要增加方法,可以用Category;但如果要添加属性,Category就无能为力了,只能使用Associative。Associative和Category同属于Runtime的运用,只是Category属于语法级别的封装,而要使用Associative必须显示地引用头文件<objc/runtime.h>。
Associative一共包含三个方法:

//设置属性
//第一个参数是原始对象,第二个参数是关联对象的key,第三个参数是关联对象本身,第四个参数是关联的策略。
//如果第三个参数是nil,则清空当前关联。
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);

//访问属性
//第一个参数是原始对象,第二个参数是关联对象的key
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);

//移除所有属性
//参数是原始对象
//这个方法会把对象重置回初始状态,因为无法保证别的地方是否也为该对象设置为关联对象。
//所以在尽量使用objc_setAssociatedObject,并把第三个参数设成nil来替代。
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_1);

关联策略(objc_AssociationPolicy)一共有5种类型,参照Property的定义,应该不难理解它们之间的区别。

enum {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

更多参考

@dynamic属性声明

正常的属性都是用@synthesize来声明的(XCode4.0之后不需要了,编译时会自动生成get、set方法)。但是有时候我们的get、set方法要在运行时才动态绑定,这时就要用@dynamic。

KVO

KVO是用来检测对象的某个属性是否发生变化的机制。

//加入观察者
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;

//移除观察者
- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

PullToRefresh实现机制

PullToRefresh就是在UIScrollView上面add了一个subview:PullToRefreshView。这个PullToRefreshView就是一个普通的View,宽度和UIScrollView相同,高度可以自定义,默认是60。

PullToRefreshView有四种状态:

typedef NS_ENUM(NSUInteger, SVPullToRefreshState) {
    SVPullToRefreshStateStopped = 0,
    SVPullToRefreshStateTriggered,
    SVPullToRefreshStateLoading,
    SVPullToRefreshStateAll = 10
};
  • SVPullToRefreshStateStopped:刷新停止状态,还没有触发刷新操作。
  • SVPullToRefreshStateTriggered:刷新操作被触发,但还没有开始发送请求(只要用户手还没松开,不会发送请求)
  • SVPullToRefreshStateLoading:已经发送请求,但数据还没回来,这时菊花一直在转。这时候UITableView的contentInset已经修改,使PullToRefreshView可见。
  • SVPullToRefreshStateAll:初始化的时候用的。

如果不需要自定义,PullToRefreshView内置了一个默认版本,包含箭头、title、subtitle、time。

自定义也可以,在PullToRefreshView的viewForState数组中加入三中状态的视图,然后就可以显示自定义的UI了。

PullToRefreshView是UIScrollView的一个属性,这个属性是通过关联的方式绑定的。当用户滚动UIScrollView的时候,ContentOffset会发生变化,这时候UIScrollView的观察者PullToRefreshView就会收到通知,然后根据PullToRefreshView自身的状态和ContentOffset决定是否切换状态,并执行相应动画。

自定义

SVPullToRefresh自带的自定义功能比较鸡肋,就是根据不同的状态换不同的View。当你想做更多个性化的动画时,就比较捉急了。所以,我准备只保留其最核心的部分,比如关联、KVO、四个状态,然后整体的动画都重新写。效果图如下(话说这个动画设计的实在是low):

loading.gif
loading.gif

动画分成两个阶段:

  • 第一个阶段是还没进入加载阶段,这时候随着下拉的高度,中间的图片逐渐变大,扇形区域也逐渐变大。
  • 第二个阶段是加载过程中,中间的图片开始旋转,同时像两侧发射出几个小图,旋转的同时透明度也发生变化,并来回震荡,直到加载结束。

代码是基于SVPullToRefresh修改的,把用不着的代码全删了。项目已上传:Github

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
现在第三方下拉刷新的库非常多,但是比较常用的就是PullToRefresh了,因为支持的控件比较多 但是缺点就是代码量很大 原生可定制性比较差,所以花了一些时间研究了下这个库。 话不多说,说干就干。我们先集成PullToRefresh到自己的项目中。集成后项目的结构 集成完库,开始写一个简单的ListView看一下默认的效果。 1、PullToRefreshListView快速创建,看效果 MainActivity布局文件 控件初始化和加载适配器 模拟联网,两秒后设置加载完成 Adapter就是基础listView写法就不贴代码了和item自己随便写个简单的布局就好,接下来就可以看效果了。 看到这个上拉和下拉的效果,估计好多小伙伴的需求并不是要这个效果,都会要加一些自己的文字或者动画。下面我们就开始简单的定制这个上拉和下拉的布局 2、文字定制 修改文字比较简单,控件已经提供了相应的方法,直接上代码 紧紧文字的修改是远远不够的,我还要修改动画怎么办呢?下面就是引入动画 3、动画定制 接下来的定制都是需要去库里面修改代码了,可要打起精神,防止出错。 在PullTorefreshBase这个类中扎到 方法;修改以下 //此处实现了我们需要 修改的动画方法: //修改代码实现 自己的下载刷新动画 LoadingLayout createLoadingLayout(Context context, Mode mode, Orientation scrollDirection, TypedArray attrs) { switch (this) { case ROTATE: default: //帧动画 frameAnimationLayout为 自定义动画类 //通过mode判断是header还是footer if (Mode.PULL_FROM_START == mode) { return new FrameAnimationLayout(context, mode, scrollDirection, attrs); } else if (Mode.PULL_FROM_END == mode) { return new RotateLoadingLayout(context, mode, scrollDirection, attrs); } // return new RotateLoadingLayout(context, mode, scrollDirection, attrs); case FLIP: return new FlipLoadingLayout(context, mode, scrollDirection, attrs); } } 重点就在通过mode参数判断是下拉还是上拉,我可以分别修改动画。但是我还想在控件中加入一些其他文字,这就需要修改Header布局文件,进入资源文件找到,问项目中用的是ListView,所以要修改vertical这个布局,具体修改就看你公司需求自己定制了。 这里要说的是重点!因为这个布局文件header和footer共用的,如果有控件要在代码中操作它的显示与隐藏,要在LoadingLayout类的构造器中初始化对象。 在下面几行代码中,还是通过mode这个参数判断是上拉还是下拉,来区分你的操作,来显示不同的头和脚。 接下来修复添加自己的控件后会有显示方面的bug, 需要在showInvisibleViews和hideAllViews两个方法中分别把需要操作的控件分别显示和隐藏,要不下拉或者上拉距离过 大会导致控件偶尔不隐藏的bug。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值