先上图,看效果

iOS - 个人中心果冻弹性下拉动画_动画

这是之前App里买呢一个功能,当初没有写完,今天想起来,把这个功能给完善了,首先来讲讲这个功能的原理:

1.上面浅绿色那一部分是利用贝塞尔曲线画出来的,这个问题不大;
2.下面的那条弧线就是那条贝塞尔曲线,贝塞尔曲线有一个点叫顶点,大
概位置就在头像最顶端那里,这个点和左右两边到屏幕的点组成了这条贝塞
尔曲线;
3.头像中点始终在贝塞尔曲线中点位置;
4.曲线中点的坐标是有个公式可以算出啦跌,奈何不是科班出身,大学学
的也忘干净了,所以不会算,小伙伴有兴趣的自行google,但是幸运的是
我们这里左右对称,睡一个特殊的贝塞尔曲线,所以,中点坐标就是顶点垂
直到左右两点连线的中点,这条线段的中间位置;
5.知道了这个公式,足够我们来写这个功能了;

看核心部分代码:

- (void)handlePanAction:(UIPanGestureRecognizer *)pan
{
CGPoint point = [pan translationInView:self];
NSLog(@"%f",point.y);

if (point.y < 0) {
return;
}
if(!_isAnimating)
{
if(pan.state == UIGestureRecognizerStateChanged)
{
// 手势移动时,_shapeLayer跟着手势向下扩大区域

_mHeight = point.y + 60;

self.curveX = KWIDTH/2.0;
//拉动时获取到手指位移距离,并限制顶点最大位置区间
self.curveY = _mHeight > 240 ? 240 : _mHeight;
_curveView.frame = CGRectMake(_curveX,
self.curveY,
_curveView.frame.size.width,
_curveView.frame.size.height);
_headerImage.center = CGPointMake(KWIDTH / 2, 105 + (self.curveY - 60) / 2);
//60就是顶点y坐标
[self.cuteDelegate backMeWithHeaderCenterY:105 + (self.curveY - 60) / 2];
}
else if (pan.state == UIGestureRecognizerStateCancelled ||
pan.state == UIGestureRecognizerStateEnded ||
pan.state == UIGestureRecognizerStateFailed)
{
// 手势结束时,_shapeLayer返回原状并产生弹簧动效
_isAnimating = YES;
_displayLink.paused = NO; //开启displaylink,会执行方法calculatePath.

// 弹簧动效
[UIView animateWithDuration:1
delay:0.0
usingSpringWithDamping:0.3
initialSpringVelocity:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{

_curveView.frame = CGRectMake(KWIDTH/2, 60, 0.1, 0.1);
//回到最初的顶点坐标位置,这个_curveView就是顶点,为的是给你看位置,博主为了美观设置小了,你可以尝试改大frame
_headerImage.center = CGPointMake(KWIDTH / 2, 105);//最终还是要回到中点
NSLog(@"%f",_headerImage.center.y);

[self.cuteDelegate backMeWithHeaderCenterY:105];//把最后的位置传回去给子视图变化位置

} completion:^(BOOL finished) {

if(finished)
{
_displayLink.paused = YES;
_isAnimating = NO;
//这里是结束后给个标记,当然,也可以不给,需要刷新数据的话这里还是要给的,代表结束状态,你也可以自己通过回调来传。 [self.cuteDelegate backMeWithHeaderCenterY:1000];

}

}];
}
}
}

- (void)updateShapeLayerPath
{
// 更新_shapeLayer形状,这里是对贝塞尔曲线和画图的运用,不是很难
UIBezierPath *tPath = [UIBezierPath bezierPath];
[tPath moveToPoint:CGPointMake(0, 0)];
[tPath addLineToPoint:CGPointMake(KWIDTH, 0)];
[tPath addLineToPoint:CGPointMake(KWIDTH, MIN_HEIGHT)];
[tPath addQuadCurveToPoint:CGPointMake(0, MIN_HEIGHT)
controlPoint:CGPointMake(_curveX, _curveY)];
[tPath closePath];
_shapeLayer.path = tPath.CGPath;
}




- (void)calculatePath
{
// 由于手势结束时的弹簧动画,把这个过程的坐标记录下来,并相应的画出_shapeLayer形状,这里获取到顶点变化坐标
CALayer *layer = _curveView.layer.presentationLayer;
self.curveX = layer.position.x ;
self.curveY = layer.position.y;
}

上面的代码先看下,以上并没有完全结束,虽然写好了这个带头像的弹性动画,但是把个人中心的数据放上去又是一个大问题,只有我们自定义的这个弹性视图会响应下拉的动画效果,如果类似这样:

iOS - 个人中心果冻弹性下拉动画_ios_02


最下面是scrollView,下面的view下拉没反应,如果强行加上上面方法中的手势,scrollView又不会滑动,总之博主在这里做了很多尝试,往往解决一个问题,又出现一个,很难解决。最后,选定的方案是最初的UItableView,把弹性动画视图作为tableHeaderView,同样的,tableView也不会响应下拉的动画效果,所以强制加上手势,看下代码实现:

#import "ViewController.h"
#import "LHCuteView.h"
#import "MyTableView.h"

@interface ViewController ()<CuteViewDelegate,UITableViewDelegate,UITableViewDataSource,UIScrollViewDelegate,UIGestureRecognizerDelegate>
{
MyTableView *tableView;
LHCuteView *cuteView;
UIView *subView;
BOOL isCute;
}
@end
#define KWIDTH ([[UIScreen mainScreen] bounds].size.width) // 屏幕宽度
#define KHEIGHT ([[UIScreen mainScreen] bounds].size.height) // 屏幕长度
@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
[self.navigationController setNavigationBarHidden:YES animated:YES];
// [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];

[self methodTwo];
}

- (void)methodOne{

cuteView = [[LHCuteView alloc] initWithFrame:CGRectMake(0, 0, KWIDTH, KHEIGHT)];
cuteView.backgroundColor = [UIColor whiteColor];
cuteView.cuteDelegate = self;
[self.view addSubview:cuteView];


cuteView.headerImage.image = [UIImage imageNamed:@"cute.jpg"];

subView = [[UIView alloc]initWithFrame:CGRectMake(0, 170, KWIDTH, KHEIGHT - 170)];
[cuteView addSubview:subView];

[self creatSubView];

}

- (void)methodTwo
{
cuteView = [[LHCuteView alloc] initWithFrame:CGRectMake(0, 0, KWIDTH, 170)];
cuteView.backgroundColor = [UIColor whiteColor];
cuteView.cuteDelegate = self;
[self.view addSubview:cuteView];

isCute = YES;
cuteView.headerImage.image = [UIImage imageNamed:@"cute.jpg"];

tableView = [[MyTableView alloc]initWithFrame:CGRectMake(0, 0, KWIDTH, KHEIGHT) style:UITableViewStylePlain];
tableView.delegate = self;
tableView.bounces = NO;
tableView.dataSource = self;
[self.view addSubview:tableView];
tableView.tableHeaderView = cuteView;

UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanAction:)];
[tableView addGestureRecognizer:pan];
}

- (void)handlePanAction:(UIPanGestureRecognizer *)pan
{
CGPoint point = [pan translationInView:tableView];
if (point.y > 0) {
if (isCute == YES) {
[cuteView handlePanAction:pan];
}
}
}

- (void)creatSubView
{
for (int i = 0; i < 5; i++) {
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(10, i * 44, 100, 44)];
label.text = [NSString stringWithFormat:@"个人中心%d",i];
label.textAlignment = NSTextAlignmentLeft;
[subView addSubview:label];
}
}

- (void)backMeWithHeaderCenterY:(CGFloat)centerY
{
// tableView.frame = CGRectMake(0, centerY + 30 + 50, KWIDTH, KHEIGHT - 170);
// subView.frame = CGRectMake(0, centerY + 80, KWIDTH, KHEIGHT - 170);


if (centerY == 1000) {
cuteView.frame = CGRectMake(0, 0, KWIDTH, 170);
tableView.tableHeaderView = cuteView;
[tableView reloadData];
}
else
{
cuteView.frame = CGRectMake(0, 0, KWIDTH, centerY + 65);
tableView.tableHeaderView = cuteView;

}

}

#pragma mark - UITableViewDelegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 15;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 44;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}

cell.textLabel.text = [NSString stringWithFormat:@"个人中心%ld",(long)indexPath.row];


return cell;


}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSLog(@"%f",tableView.contentOffset.y);
if (tableView.contentOffset.y > 0) {
isCute = NO;
}
if (tableView.contentOffset.y == 0) {
[self performSelector:@selector(canCute) withObject:nil afterDelay:0.1];
}
}

- (void)canCute
{
isCute = YES;
}

以上是博主最终的代码,为了解决tableView的手势冲突,需要自定义tableView并实现手势代理方法:

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if (gestureRecognizer.state != 0) {
return YES;
} else {
return NO;
}
}

同时,为了让tableView滚到下面再滚回来时偏移量恢复0时不会触发下拉的效果,需要在scrollView代理中进行处理,当isCute为YES时,才会执行动画效果,这样就满足了我们的需要。

以上,希望用到的小伙伴可以认真看下代码,仔细思考,要不然很难看懂,一开始的原理一定要看,或者自己查贝塞尔曲线中点坐标的计算方法,此处只是特例,切记。

这里其实还可以做深度的封装,不过为了大家能看懂就不做过多处理,可以根据自己需要来改造哦,这个也是博主在别人的弹性动画上加的功能,已经脱胎于原来的动画。

Demo下载地址:​​点击前往下载​