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

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

先上图,看效果

image.png

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

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

1.png

最下面是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下载地址:点击前往下载

目录
相关文章
|
iOS开发
iOS 动画绘制圆形
iOS 动画绘制圆形
92 1
|
编译器 iOS开发 异构计算
读iOS核心动画笔记
读iOS核心动画笔记
62 0
|
1月前
|
Swift iOS开发 UED
如何使用Swift和UIKit在iOS应用中实现自定义按钮动画
本文通过一个具体案例,介绍如何使用Swift和UIKit在iOS应用中实现自定义按钮动画。当用户点击按钮时,按钮将从圆形变为椭圆形,颜色从蓝色渐变到绿色;释放按钮时,动画以相反方式恢复。通过UIView的动画方法和弹簧动画效果,实现平滑自然的过渡。
59 1
|
2月前
|
Swift iOS开发 UED
如何使用Swift和UIKit在iOS应用中实现自定义按钮动画
【10月更文挑战第18天】本文通过一个具体案例,介绍如何使用Swift和UIKit在iOS应用中实现自定义按钮动画。当用户按下按钮时,按钮将从圆形变为椭圆形并从蓝色渐变为绿色;释放按钮时,动画恢复原状。通过UIView的动画方法和弹簧动画效果,实现平滑自然的动画过渡。
62 5
|
3月前
|
Swift iOS开发 UED
揭秘一款iOS应用中令人惊叹的自定义动画效果,带你领略编程艺术的魅力所在!
【9月更文挑战第5天】本文通过具体案例介绍如何在iOS应用中使用Swift与UIKit实现自定义按钮动画,当用户点击按钮时,按钮将从圆形变为椭圆形并从蓝色渐变到绿色,释放后恢复原状。文中详细展示了代码实现过程及动画平滑过渡的技巧,帮助读者提升应用的视觉体验与特色。
71 11
|
4月前
|
Swift iOS开发 UED
【绝妙创意】颠覆你的视觉体验!揭秘一款iOS应用中令人惊叹的自定义动画效果,带你领略编程艺术的魅力所在!
【8月更文挑战第13天】本文通过一个具体案例,介绍如何使用Swift与UIKit在iOS应用中创建独特的按钮动画效果。当按钮被按下时,其形状从圆形变化为椭圆形,颜色则从蓝色渐变为绿色;释放后,动画反向恢复原状。利用UIView动画方法及弹簧动画效果,实现了平滑自然的过渡。通过调整参数,开发者可以进一步优化动画体验,增强应用的互动性和视觉吸引力。
61 7
|
iOS开发
iOS 常用阅读软件打开书籍的转场动画
iOS 常用阅读软件打开书籍的转场动画
103 0
|
7月前
|
iOS开发
iOS设备功能和框架: 如何使用 Core Animation 创建动画效果?
iOS设备功能和框架: 如何使用 Core Animation 创建动画效果?
146 0
|
API iOS开发
iOS 自定义转场动画 UIViewControllerTransitioning
iOS 自定义转场动画 UIViewControllerTransitioning
111 0
|
API iOS开发
iOS 核心动画
iOS 核心动画
91 0