先上图,看效果
这是之前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下载地址:点击前往下载