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;
}

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

最下面是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开发CFBundleDisplayName找不到项目名的问题

2021-2-2 9:01:11

iOS文章

iOS开发获取网关IP,运营商,位置,可判断是在国内还是国外

2021-2-2 9:54:53

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索