最近在网上看到一个很酷的下拉刷新效果(http://iostuts.io/2015/10/17/elastic-bounce-using-uibezierpath-and-pan-gesture/)。自己试着实现了一下其中的果冻回弹效果。
效果
DEMO
由于文笔不太好-.- ,建议先下载demo,再结合下面的分析,会好理解点。地址https://github.com/Resory/RYCuteView
逻辑
下图p1,蓝色部分图形是一个CAShapeLayer,他的形状由UIBezierPath的路径组成的。
这个路径是由r1,r2,r3,r4,r5这5个红点确定的。其中r1,r2,r3,r4都是不动点,
唯一可以动的是r5点
。根据上面的动态图可以看出,
CAShapeLayer的形状是随着r5红点的移动而相应变化的
,所以只要获得r5的坐标变化就可以用UIBezierPath做出相应的路径,然后就可以形成相应的形状。
实现
初始化CAShapeLayer
1
2
3
4
5
6
|
- (void)configShapeLayer { _shapeLayer = [CAShapeLayer layer];
_shapeLayer.fillColor = [UIColor colorWithRed:57/255.0 green:67/255.0 blue:89/255.0 alpha:1.0].CGColor;
[self.layer addSublayer:_shapeLayer];
} |
初始化r5点
1
2
3
4
5
6
7
8
9
|
- (void)configCurveView { // _curveView就是r5点
_curveX = SYS_DEVICE_WIDTH/2.0; // r5点x坐标
_curveY = MIN_HEIGHT; // r5点y坐标
_curveView = [[UIView alloc] initWithFrame:CGRectMake(_curveX, _curveY, 3, 3)];
_curveView.backgroundColor = [UIColor redColor];
[self addSubview:_curveView];
} |
添加移动手势&CADisplayLink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
- (void)configAction { _mHeight = 100; // 手势移动时相对高度
_isAnimating = NO; // 是否处于动效状态
// 手势
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanAction:)];
self.userInteractionEnabled = YES;
[self addGestureRecognizer:pan];
// calculatePath方法是算出在运行期间_curveView的坐标,从而确定_shapeLayer的形状
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(calculatePath)];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
// 在手势结束的时候才调用calculatePath方法,所以一开始是暂停的
_displayLink.paused = YES;
} |
-
手势解析
手势移动时,r5红点跟着手势移动,_shapeLayer则根据r5的坐标来扩大自己的区域
手势结束时,r5红点通过UIView的动画方法来改变r5的坐标,同时_shapeLayer根据r5的坐标缩小自己的区域并最终返回原形。
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
|
- (void)handlePanAction:(UIPanGestureRecognizer *)pan { if (!_isAnimating)
{
if (pan.state == UIGestureRecognizerStateChanged)
{
// 手势移动时,_shapeLayer跟着手势向下扩大区域
CGPoint point = [pan translationInView:self];
// 这部分代码使r5红点跟着手势走
_mHeight = point.y*0.7 + MIN_HEIGHT;
_curveX = SYS_DEVICE_WIDTH/2.0 + point.x;
_curveY = _mHeight > MIN_HEIGHT ? _mHeight : MIN_HEIGHT;
_curveView.frame = CGRectMake(_curveX,
_curveY,
_curveView.frame.size.width,
_curveView.frame.size.height);
// 根据r5坐标,更新_shapeLayer形状
[self updateShapeLayerPath];
}
else if (pan.state == UIGestureRecognizerStateCancelled ||
pan.state == UIGestureRecognizerStateEnded ||
pan.state == UIGestureRecognizerStateFailed)
{
// 手势结束时,_shapeLayer返回原状并产生弹簧动效
_isAnimating = YES;
_displayLink.paused = NO; //开启displaylink,会执行方法calculatePath.
// 弹簧动效
[UIView animateWithDuration:1.0
delay:0.0
usingSpringWithDamping:0.5
initialSpringVelocity:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
// 曲线点(r5点)是一个view.所以在block中有弹簧效果.然后根据他的动效路径,在calculatePath中计算弹性图形的形状
_curveView.frame = CGRectMake(SYS_DEVICE_WIDTH/2.0, MIN_HEIGHT, 3, 3);
} completion:^(BOOL finished) {
if (finished)
{
_displayLink.paused = YES;
_isAnimating = NO;
}
}];
}
}
} |
根据r5的位置,更新_shapeLayer形状
1
2
3
4
5
6
7
8
9
10
11
12
|
- (void)updateShapeLayerPath { // 更新_shapeLayer形状
UIBezierPath *tPath = [UIBezierPath bezierPath];
[tPath moveToPoint:CGPointMake(0, 0)]; //r1点
[tPath addLineToPoint:CGPointMake(SYS_DEVICE_WIDTH, 0)]; // r2点
[tPath addLineToPoint:CGPointMake(SYS_DEVICE_WIDTH, MIN_HEIGHT)]; //r4点
[tPath addQuadCurveToPoint:CGPointMake(0, MIN_HEIGHT)
controlPoint:CGPointMake(_curveX, _curveY)]; // r3,r4,r5确定的一个弧线
[tPath closePath];
_shapeLayer.path = tPath.CGPath;
} |
计算弹簧效果坐标
1
2
3
4
5
6
7
8
|
- (void)calculatePath { // 由于手势结束时,r5执行了一个UIView的弹簧动画,把这个过程的坐标记录下来,并相应的画出_shapeLayer形状
CALayer *layer = _curveView.layer.presentationLayer;
_curveX = layer.position.x;
_curveY = layer.position.y;
[self updateShapeLayerPath];
} |
末
r5点的作用非常重要,因为直接对CAShapeLayer实现动效不太好实现。所以通过对r5点实现弹簧动效,记录r5点的坐标,再用UIBezierPath形成路径,最后赋予CAShapeLayer,间接的完成了CAShapeLayer的弹簧动效。
链接: