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

1,438 阅读4分钟

先上图,看效果 这里写图片描述

这是之前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;
}

上面的代码先看下,以上并没有完全结束,虽然写好了这个带头像的弹性动画,但是把个人中心的数据放上去又是一个大问题,只有我们自定义的这个弹性视图会响应下拉的动画效果,如果类似这样:
这里写图片描述
最下面是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下载地址:点击前往下载