绘图与动画之使用CAKeyframeAnimation实现仿WP水滴加载动画

基础知识

本文所用到的是CAKeyframeAnimation即关键帧动画,CAKeyframeAnimation和CABasicAnimation同为CAPropertyAnimation的子类,都可以通过keyPath来为view的某一属性实现常用动画。
不同的是,CABasicAnimation只能指定属性的起始值(fromValue)、最终值(toValue)和步进值(byValue);而CAKeyframeAnimation却可以指定属性的每一个关键值(values或path)、关键值对应的时间节点(keyTimes)以及在这些关键值之间所用的时间函数(timingFunctions)。
相比之下,CAKeyframeAnimation要更加的灵活多变,而使用CAKeyframeAnimation的关键就在于寻找合适的节点和时间。

引言

最近在看关于绘图和动画方面的东西,看到一些不错的效果也总是想如何才能实现,而本人平时用的手机是nokia,感觉wp系统的加载动画有点意思,所以也尝试做了一个实现。
其效果是一串小点陆续从左侧进入,在中间集合后再陆续从右侧滑出,实现效果如下。
效果图

思路与算法

从图中可以看出,动画的主体是五个圆点,而动画效果就是通过圆点位置的移动来实现的,基于这一点我也总结了几种方案。

  • 最开始想到的是写出每个点的x坐标关于时间的函数,开始匀减速,中间匀速,最后是匀加速,需要注意的是每个点开始加载的间隔时间。
  • 通过观察发现,在滑入和滑出的过程中几个点之间的距离大概呈等差数列状,但是比较困难的是第一点向右滑出后后续点得滑出如何处理。第二个方案不太靠谱。
  • 同样经过观察发现,从左侧滑入和从右侧滑出时圆点之间的距离基本是一致的,而在中间集合时各点之间的距离也是一致的,只是在相聚和相离的过程中点之间距离有所不同。于是可以得出,点之间的距离可以分为最大和最小两种,而介于两者中间的距离则交给动画来实现。

这里写图片描述
最终采用了第三种方案,通过点间最大距离可以得到在屏幕上的关键分割点,而点在中间集合效果可以通过在中点左右增加关键点来实现。用到的参数如下:
- 加载视图宽度:width
- 点间最大距离:maxDistance
- 点数:ptCount

于是最初关键分割点数=width/maxDistance+1;
中点索引=width/2/maxDistance+1;
由于各个点加载时间有所延迟,在第一个点到达中间和右侧时都需要等待其它点,即在中点左右与终点位置都要增加(ptCount-1)个关键分割点,所以关键分割点数+=2*(ptCount -1 );
为了避免中心分割点位置超出其它分割点,可以让点间最小距离minDistance=maxDistance/ptCount;
某分割点是否中心分割点可以通过该点索引来计算:
int offset=pos-center;//pos起始值为1
若offset>=0 && offset 小于ptCount则为中心分割点。
各个分割点的x坐标则是以上各参数的函数计算值。

代码实现

动画中的各个小圆点只是用来显示图形而并不涉及操作,所以在此用自定义CALayer来创建。

@interface IPZPointLayer : CAShapeLayer

@end


@implementation IPZPointLayer

-(instancetype)init{
    self=[super init];
    if (self) {
        self.bounds = CGRectMake(0, 0, 3, 3);
        self.path = [UIBezierPath bezierPathWithOvalInRect:self.bounds].CGPath;
        self.fillColor = [UIColor blackColor].CGColor;
    }
    return self;
}

@end

在view中定义动画,可以根据不同的加载点数和最大点间隔定义不同动画:

-(void) initAnimationByPtCount:(int)ptCount andMaxDistance:(int)maxDistance{
    double width=self.bounds.size.width;
    double height=self.bounds.size.height;
    int posCount=2*(ptCount -1 ) +ceil(width/maxDistance)+1;
    int center=floor(width/2/maxDistance)+1;
    int minDistance=maxDistance/ptCount;

    NSMutableArray *positions;
    for (int i=0; i<ptCount; i++) {
        positions=[NSMutableArray arrayWithCapacity:posCount];
        for (int j=1; j<=posCount; j++) {
            int pos=j-i;
            if (pos<=1) {//第一分割点
                [positions addObject:@(-1)];
                continue;
            }

            int offset=pos-center;
            if (offset<0) {//中点以前
                pos =(pos-1) *maxDistance;
                [positions addObject:@(pos)];
            }else if (offset>=0 && offset <ptCount) {//中心分割点
                if (offset<floor(ptCount/2)+1) {//中点前
                    pos=(center-1) *maxDistance +minDistance*(  offset-floor(ptCount/2)-1);
                    [positions addObject:@(pos)];
                }else{//中点后
                    pos=(center-1) *maxDistance +minDistance*(  offset-floor(ptCount/2)-1);
                    [positions addObject:@(pos)];
                }

            }else{//中心分割点以后
                pos=pos-ptCount;
                pos *= maxDistance;
                [positions addObject:@(pos)];
            }

        }

        IPZPointLayer *firPT=[[IPZPointLayer alloc]init];
        firPT.position=CGPointMake(-1, height/2);
        [self.layer addSublayer:firPT];
        CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
        animation.keyPath = @"position.x";
        animation.values = positions;      //各级值
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
        animation.duration = 3;
        animation.repeatCount= HUGE_VALF;
        [firPT addAnimation:animation forKey:@"load"];
    }
}

使用动画,指定五个加载点以及最大距离为50;

 [self initAnimationByPtCount:5 andMaxDuration:50];
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值