听传言说公司准备使用自定义的上拉和下拉控件,以前研究过,但没有用到实际项目中,不管传言是真是假,重新学习,继续进步!这里也贴出我写的DEMO,欢迎使用和Star!
刷新和加载控件原理是一样,这里就只讲刷新控件,我们先不去研究效果,先来分析一下功能实现,一个常用的刷新控件工作分以下几步:
1.拖动表视图下拉,控件跟随滑动视图慢慢出现
2.滑动到限定距离,直接松手就触发刷新动作,开始刷新,刷新效果停留顶端;若未松手继续滑动到限定距离以内,不执行操作
3.刷新完成,去掉刷新效果,视图恢复原位
添加控件到每个滑动视图
首先如果是给一个成熟的项目加上刷新控件,不可能在每个有滑动视图的类中都去添加刷新控件,怎么办呢?滑动视图都有个共同点,都继承自UIScroolView
,所以这里咱们采用给UIScroolView
类新建一个TJRefresher
的分类,再给分类添加咱们需要的header
和footer
属性,再在我们需要的滑动视图类中调用这两个属性即可。但是分类中是不能直接添加属性的,咋办,直接不行来间接呗,这里就要用到运行时了,前面讲过如何动态添加属性,这里不再细讲。上代码:
static const void *TJRefreshHeaderKey = &TJRefreshHeaderKey;
- (void)setHeader:(TJRefreshHeader *)header {
if (self.header != header) {
[self.header removeFromSuperview];
[self insertSubview:header atIndex:0];
}
objc_setAssociatedObject(self, TJRefreshHeaderKey, header, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (TJRefreshHeader *)header {
return objc_getAssociatedObject(self, &TJRefreshHeaderKey);
}
这里我用的是insertSubview: atIndex:
方法而不是addSubview:
,他们的区别是前者是将视图插入同一个层级,并且可以插队,而后者只能是一层一层往上叠。当一个滑动视图同时添加刷新和加载控件时,前者可以保证他们是在一个视图层级,方便统一处理。
监听滑动视图,改变控件状态
这里需要使用KVO监听滑动视图的滑动距离,有两个选择,一个是放在上面的分类中实现,另一个是放在刷新控件类中实现。前者需要刷新控件暴露很多方法和属性供外部调用,后者只需要将当前滑动视图传递给刷新控件类进行操作即可,权衡后选后者。
首先重写UIView
类的方法,将滑动视图取到当前类暂存,并根据该视图调整控件的位置:
- (void)willMoveToSuperview:(UIView *)newSuperview {
[super willMoveToSuperview:newSuperview];
if (newSuperview && [newSuperview isKindOfClass:[UIScrollView class]]) {
[self removeObserver];
self.scrollView = (UIScrollView *)newSuperview;
self.center = CGPointMake(self.scrollView.bounds.size.width*0.5, -TJRefreshPullLen*0.5);
[self addObserver];
}else {
[self removeObserver];
}
}
这里添加KVO时添加了两个属性监听,contentOffset
和contentSize
,需要说明的是,在刷新控件中其实只需要监听前者即可,后者的监听是在做加载控件的时候,需要根据父滑动视图的内容大小来确定控件位置和计算控件所在状态。这也是两个控件最大的不同。
[self.scrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
[self.scrollView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil];
接下来将监听到的偏移值进行处理,小于极限值什么都不做,大于极限值时松手则进行刷新操作:
CGFloat value = (layerStrokenValue-(TJRefreshPullLen-TJRefreshHeaderHeight)*0.5)/((TJRefreshPullLen+TJRefreshHeaderHeight)*0.5);
//如果不是正在刷新,则渐变动画
if (!self.animating) {
self.draftShapeLayer.strokeEnd = value;
self.squareShapeLayer.strokeEnd = value;
self.lineShapeLayer1.strokeEnd = value*2;
self.lineShapeLayer2.strokeEnd = (value-0.5)*2;
self.wordsShapeLayer.strokeEnd = value;
}
//如果到达临界点,则执行刷新动画
if (value > 1 && !self.animating && !self.scrollView.dragging){
[self startAnimation];
if (self.handle) {
self.handle();
}
}
这里控件需要暴露一个方法在刷新结束时供滑动视图调用:
- (void)endRefreshing {
[self stopAnimation];
NSLog(@"结束刷新");
}
加载控件的UI
这里我仿了一个今日头条的刷新控件效果,用到了iOS9和swift3.0后才推出的CAShapeLayer
,结合UIBezierPath
绘制出了图形的线条和路径,再通过上面的滑动监听去控制线条颜色的填充百分比。这一步骤没有太复杂的逻辑,熟悉上面两个类的使用即可,不细讲,有兴趣可以研究demo。我还添加了一个文字转贝塞尔曲线的方法,也可以研究一下。
到这里一个刷新控件就定制完成了,只贴出了一些需要注意的地方的代码,加载控件添加也类似。这里只实现了一个效果,后期如果碰到了好的效果,我还会继续添加的(前提是我会😁)。
广告时间:觉得不错,欢迎Star!