1、先看几张效果图
2、设计思路
2.1、对比MJRefresh下拉刷新,类淘宝下拉效果去掉了上下调换箭头及菊花加载效果,添加了动态画圆圈的轨迹动画;
2.2、线上有很多下拉刷新播放动态图的动画效果,那些是通过帧动画来实现,在UIImageView中多张图片快速替换实现;
2.3、上述效果不能控制动画“播放进度”,只能循环播放,无法实现类淘宝“画圈”效果;
2.4、图片无法实现就只能通过程序来动态画,这里涉及到一个关键性的类CAShapeLayer;
2.5、CAShapeLayer有两个重要属性strokeStart与strokeEnd,这两个属性可以实现画线的起始点,支持动画;
2.6、通过贝塞尔曲线画圆圈轨迹,将轨迹赋值给CAShapeLayer;
2.7、在UITableView下拉时,监听下拉距离(MJRefresh已实现)调整画圈的弧度;
2.8、下拉刷新的其他状态通过修改背景小图标来辅助动画效果。
3、关键代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | // // SARefreshHeader.m // fisher // // Created by 余西安 on 16/1/29. // Copyright ? 2016年 Sian. All rights reserved. // #define SAColor(rgb) [UIColor colorWithRed:((rgb&0xFF0000)>>16)/255.0 green:((rgb&0xFF00)>>8)/255.0 blue:(rgb&0xFF)/255.0 alpha:1.0] #import "SARefreshHeader.h" @implementation SARefreshHeader - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]){ self.stateLabel.font = [UIFont systemFontOfSize:11]; self.stateLabel.textColor = [UIColor lightGrayColor]; self.lastUpdatedTimeLabel.font = [UIFont systemFontOfSize:11]; self.lastUpdatedTimeLabel.textColor = [UIColor lightGrayColor]; [self setTitle:@"下拉刷新" forState:MJRefreshStateIdle]; [self setTitle:@"释放刷新" forState:MJRefreshStatePulling]; [self setTitle:@"更新中..." forState:MJRefreshStateRefreshing]; [self setValue:@"0" forKeyPath:@"arrowView.alpha"]; } return self; } // 重新布局子控件 - (void)placeSubviews { [super placeSubviews]; self.stateLabel.mj_y = MJRefreshHeaderHeight * 0.3; self.lastUpdatedTimeLabel.mj_y = MJRefreshHeaderHeight * 0.65; CGFloat refreshX = self.mj_w * 0.5 - 40; CGFloat refreshY = MJRefreshHeaderHeight * 0.55; self.refreshIcon.center = CGPointMake(refreshX, refreshY); } // RefreshState状态 - (void)setState:(MJRefreshState)state { [super setState:state]; // 隐藏原控件LoadingView(小菊花效果) [self setValue:@(YES) forKeyPath:@"loadingView.hidden"]; // 不动的状态给出不同的底部图标,辅助动画效果 switch (state) { case MJRefreshStateIdle:{ // 下拉刷新 self.refreshIcon.image = [UIImage imageNamed:@"icon_public_refresh_normal.png"]; }break; case MJRefreshStatePulling:{ // 释放刷新 self.refreshIcon.image = [UIImage imageNamed:@"icon_public_refresh_highlight.png"]; }break; case MJRefreshStateRefreshing:{ // 更新中... self.refreshIcon.image = [UIImage imageNamed:@"icon_public_refresh_highlight.png"]; }break; default:break; } } // KVO监听ScrollView ContentOffset值 - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change { [super scrollViewContentOffsetDidChange:change]; // 当下拉时执行相关绘图动画 if (self.state == MJRefreshStateIdle){ CGFloat distance = self.scrollView.contentOffset.y; [self refreshWithDistance:distance]; } } // 下拉距离(根据该值来画圆的弧度大小) - (void)refreshWithDistance:(CGFloat)distance { // 下拉最大距离为常量MJRefreshHeaderHeight,strokeEnd取值0~1 CGFloat strokeEnd = distance / MJRefreshHeaderHeight; if (strokeEnd < 0) strokeEnd *= -1; self->_shapeLayer.strokeEnd = strokeEnd; [self.shapeLayer setNeedsDisplay]; } // 修复自动刷新时无效果的Bug - (void)beginRefreshing { [super beginRefreshing]; self.state = MJRefreshStateRefreshing; } #pragma mark - 懒加载成员属性 - (UIImageView *)refreshIcon { if (self->_refreshIcon == nil){ UIImage *image = [UIImage imageNamed:@"icon_public_refresh_normal.png"]; self->_refreshIcon = [[UIImageView alloc] initWithImage:image]; [self addSubview:self->_refreshIcon]; } return self->_refreshIcon; } // 动画图层 - (CAShapeLayer *)shapeLayer { if (self->_shapeLayer == nil){ // 创建形状图层,图层大小与覆盖的小图标大小一致 self->_shapeLayer = [CAShapeLayer layer]; self->_shapeLayer.frame = self.refreshIcon.bounds; // 从0度开始、线度1像素、边框蓝色、内填充无色 self->_shapeLayer.strokeStart = 0; self->_shapeLayer.lineWidth = 1.0f; self->_shapeLayer.fillColor = [[UIColor clearColor] CGColor]; self->_shapeLayer.strokeColor = [SAColor(0x007AFF) CGColor]; // 动画轨迹通过贝塞尔曲线实现,慢慢调出与图标重叠的轨迹 CGFloat width = self.refreshIcon.bounds.size.width - 4; CGRect rect = CGRectMake(2, 2, width, width); UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:rect]; self->_shapeLayer.path = path.CGPath; [self.refreshIcon.layer addSublayer:self->_shapeLayer]; // 逆时钟旋转90度,使动画从最顶端开始 [self->_shapeLayer setTransform:CATransform3DMakeRotation(M_PI * -0.5, 0, 0, 1)]; } return self->_shapeLayer; } @end |
4、Demo下载SARefresh
关于要隐藏菊花和小箭头,可以重写父类的懒加载方法简单实现
// 隐藏原控件LoadingView(小菊花效果)
– (UIActivityIndicatorView *)loadingView
{
return nil;
}
– (UIImageView *)arrowView
{
return nil;
}