什么是PreLoader?

PreLoader是由Volodymyr Kurbatov设计的一个很有意思的HUD(平视显示效果(Head Up Display)),通过运动污点和固定污点之间的粘黏动画吸引用户的眼球跟踪,能有效分散等待注意力。

这篇文章简单剖析本人使用OC实现PreLoader的原理思路和做法。

喷出来的油污

根据这个Loading动画的粘黏特征,我把它里面这些有颜色的物体比作油污,观察这个动画发现,可将它分成两个整体,左右两边两个固定的油污,还有移动中的三个小油污点,左右两个固定的油污轮流向对方喷射油污,双方都会因为吸收油污而变大,喷射油污而变小。

首先我们从左右循环移动的污点着手,因为路径不是平滑一步到位,我这里选择使用CAKeyframeAnimation关键帧动画,先做出在左右固定点间来回运动的污点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//moving Spot
for (int i = 0; i < 3; i++) {
    Spot *movingSpot = [[Spot alloc] initWithFrame:CGRectMake(originX - UNIT_RADIUS,

self.bounds.size.height / 2 - UNIT_RADIUS , 2 * UNIT_RADIUS, 2 * UNIT_RADIUS) color:spotColor];
    //1
    CAKeyframeAnimation *anim = [CAKeyframeAnimation animationWithKeyPath:@"position.x"];
    anim.values = @[@(originX), @(originX), @(finalX), @(finalX), @(originX), @(originX)];
    anim.keyTimes = @[@(0.0), @(0.25), @(0.35), @(0.75), @(0.85), @(1.0)];//sleep 0.4 ratio
    anim.duration = PROCESS_DURING;
    anim.repeatCount = HUGE_VALF;
    anim.beginTime = CACurrentMediaTime() + i * SPOT_DELAY_RATIO * PROCESS_DURING;
    [movingSpot.layer addAnimation:anim forKey:@"movingAnim"];
    [self addSubview:movingSpot];
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    [CATransaction commit];
}

我把多余的代码删除掉了,主要我们来看//1处,先拿一个污点来做参考,我们通过CAKeyFrameAnimation控制污点layer的position.x变化,形成动画。现在我们只需要两个控制点(originX, finalX),路径可以解析为:“休息,左边出发点,到右边结束点,休息,然后回到左边出发点”,这个动作链为一个循环。看看我们的keyTimes和values为什么是这些数字?这里我们只需要保证两点:

  • 污点在左固定点 和 右固定点休息的时间相同

  • 污点从左往右 和 从右往左移动的时间相同

这里我规定了污点休息时间为0.4个单位,因此移动时间就是(1-2*0.4)/2 = 0.1个单位。这里有两个问题:

  • 为什么从0.25个单位开始移动而不是0.0呢?可能我只是想为后面保留灵活性吧,这个其实没多大关系。

  • 为什么values前面有两个重复的originX,后面又有两个重复的finalX?这个是必须的,虽然CAKeyframeAnim的value不需要从0.0开始1.0结束也可以实现相同的动画效果,但是如果没有了这两个极点,通过presentationLayer取动画实时位置时会出现超出边界不准确的负数,后面会再提到这个问题。

有些朋友可能会没有弄明白这个Animation Path对应的污点休息时间在哪里,这里再强化下,0.35-0.75在右边休息,0.85-0.25在左边休息,盯着我代码看,会看懂的。

ok,我们已经理解了一个污点的动画路径,那么我们用一个for循环,很简单就可以做出三个,一个跟一个出发的污点动画路径,保证了前面的基础以后,这里只需要保证三个路径的动画一个循环的持续时间duration相同,然后我们使用一个延迟系数SPOT_DELAY_RATIO = 0.08f,控制3个动画分别的beginTime,实现了一个跟一个的效果。

吸收油污,喷射油污

接下来我们做两个固定污点的变大变小动画,当然不可以用碰撞检测来做,那样不好控制而且受大小影响,会有很多不必要的代码。思路是在特定的时间做特定大小变化,既然我们已经定义了污点移动和休息的关键帧keyframe,为什么不继续用上他们呢?看固定油污的动画代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//Fixed Spot
Spot *leftFixedSpot = [[Spot alloc] initWithFrame:CGRectMake(originX - UNIT_RADIUS,

self.bounds.size.height / 2 - UNIT_RADIUS, 2 * UNIT_RADIUS, 2 * UNIT_RADIUS) color:spotColor];
Spot *rightFixedSpot = [[Spot alloc] initWithFrame:CGRectMake(self.bounds.size.width - margin - UNIT_RADIUS,

self.bounds.size.height / 2 - UNIT_RADIUS, 2 * UNIT_RADIUS, 2 * UNIT_RADIUS) color:spotColor];
NSValue *firstVal = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0f, 1.0f, 0)];
NSValue *secondVal = [NSValue valueWithCATransform3D:CATransform3DMakeScale(2.0f, 2.0f, 0)];
NSValue *thirdVal = [NSValue valueWithCATransform3D:CATransform3DMakeScale(3.0f, 3.0f, 0)];
NSValue *fourthVal = [NSValue valueWithCATransform3D:CATransform3DMakeScale(4.0f, 4.0f, 0)];
//发射点,先调至最大
leftFixedSpot.layer.transform = CATransform3DMakeScale(4.0f, 4.0f, 0);
//left
CAKeyframeAnimation *leftFixedSpotAnim = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
leftFixedSpotAnim.values = @[thirdVal,  thirdVal, fourthVal,   fourthVal, thirdVal,   thirdVal, secondVal, 

 secondVal, firstVal,
firstVal, secondVal,   secondVal, thirdVal,   thirdVal];
leftFixedSpotAnim.keyTimes = @[@(0.0),  @(0.01), @(0.01),    @(0.25), @(0.25),     @(0.33), @(0.33), 

   @(0.41), @(0.41),
//sleep
@(0.85), @(0.85),    @(0.93), @(0.93),     @(1.00)];//SPOT_DELAY_RATIO = 0.08
leftFixedSpotAnim.duration = PROCESS_DURING;
leftFixedSpotAnim.repeatCount = HUGE_VALF;
[leftFixedSpot.layer addAnimation:leftFixedSpotAnim forKey:@"fixedSpotScaleAnim"];
//right
CAKeyframeAnimation *rightFixedSpotAnim = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
rightFixedSpotAnim.values = @[firstVal, firstVal, secondVal,     secondVal, thirdVal,     thirdVal, fourthVal,
fourthVal, thirdVal,     thirdVal, secondVal,     secondVal,  firstVal, firstVal];
rightFixedSpotAnim.keyTimes = @[@(0.0),  @(0.25), @(0.25),     @(0.33), @(0.33),     @(0.41), @(0.41),//sleep
@(0.75), @(0.75),     @(0.83), @(0.83),     @(0.91), @(0.91),  @(1.0)];//SPOT_DELAY_RATIO = 0.08
rightFixedSpotAnim.duration = PROCESS_DURING;
rightFixedSpotAnim.repeatCount = HUGE_VALF;
//0.1 ratio needed that the spot from left to right
rightFixedSpotAnim.beginTime = CACurrentMediaTime() + PROCESS_DURING * 0.1;
[rightFixedSpot.layer addAnimation:rightFixedSpotAnim forKey:@"fixedSpotScaleAnim"];
[self addSubview:leftFixedSpot];
[self addSubview:rightFixedSpot];

我们先定义好4种Scale对应的污点value(firstVal, secondVal, thirdVal, fourthVal), 使用keyframeAnimation控制这4种value的变化,实现左右固定污点在指定时间点的变大变小。

这里需要注意什么:

  • 跟移动的污点保持一致,也是从0.25开始活动

  • 还记得移动污点一个跟一个出发的延迟系数0.08f吗?它就是固定污点变大变小的间隔时间单位

  • 因为移动污点从一边移动到另一边用的时间单位为0.1,所以右固定点的变大动画开始时间要比左固定点晚0.1,也就是rightFixedSpotAnim.beginTime = CACurrentMediaTime() + PROCESS_DURING * 0.1;

  • 移动污点休息时间在固定点关键帧上用不着,因为固定点在最后一个移动污点离开后变成最小,而在第一个移动污点来临时就要开始变大

  • 因为右固定点的动画相对于左固定点延迟了0.1开始,所以仍然是从0.25关键帧开始活动(变大)

这里举例讲解下这一part关键帧的计算依据:

看左固定点的代码//left,从0.25开始活动,每过一个延迟系数0.08,会有一个污点触发(喷出),做一次变小动画,当第三次变小动画做完,我们并不知道中间休息时间有多长,这时就要用到0.85这个关键帧时间(从移动污点代码可看出,第一个污点在0.85时回到左固定点),于是从0.85开始做变大动画,也是延迟系数0.08,最后会操作1.0,没关系影响不大,最后一个变大动画在0.01做就好了!

那右固定点的0.75关键帧是怎样算的呢?0.41是最后一次变大(最后一个污点被吸收), 那么距离第一个污点被喷出的时间间隔就是0.41+移动污点休息时间0.4-两个延迟系数0.08*2,因为,最后一个污点被吸收时,第一个来的污点已经休息了两个延迟系数时间了。

缓动胀大

之前scale的KeyframeAnim变化都是突然的,一个scale跳去另外一个scale,看看局部代码会议下:

1
2
firstVal, secondVal,   secondVal, thirdVal
@(0.25), @(0.25),      @(0.33), @(0.33)

可以看出一个Val变化到另一个Val是发生在同一个关键帧,没有做过度效果。这里为了修改方便,我们只需要引入一个比较短的动画时间间隔CGFloat ti = SPOT_MAGNIFY_ANIM_DURATION_RATIO = 0.03f即可:

1
2
3
CGFloat ti = SPOT_MAGNIFY_ANIM_DURATION_RATIO;
leftFixedSpotAnim.keyTimes = @[@(0.0),  @(0.01), @(0.01+ti),    @(0.25), @(0.25+ti),     @(0.33), @(0.33+ti),

    @(0.41), @(0.41+ti),
//sleep
@(0.85), @(0.85+ti),    @(0.93), @(0.93+ti),     @(1.00)];//SPOT_DEL

粘黏动画

这里我使用presentationLayer获取动画layer的实时frame信息,然后准备几个工具函数:

  • centerDistanceWithPoint:another:计算圆心距CD

  • faceDistanceWithCircleLayer:another:计算表面距离FD

  • circleIncirclingWithBigOne:smallOne:判断两圆是否包含关系

我使用了CAShapeLayer和UIBezierPath来做这个粘连效果,通过控制CAShapeLayer的颜色控制粘连的显示和消失,而显示/消失的依据就是两圆的表面距离FD。这里再次强调一遍KeyframeAnim的keyTimes一定要从0.0开始,1.0结束,否则获取layer实时frame时会有错误数据干扰。

那么“是否包含”用来干什么呢?我们有3个污点,一个粘连效果ShapeLayer,当第三个污点到来时根据FD计算出粘连Path,准备愉快地表现自己的时候,这个Path又会被第一个污点的FD干扰,计算出一个不正确的Path覆盖,所以我们让移动污点跟固定污点内切以后,就不对粘连Path产生影响。顺便一提,这一切的动画逻辑计算,都在CADisplayLink里完成。

路径Path:

我们采用两条曲线衔接两个圆的这种污点结合方式作为动画路径(见上图),这种方式能很好地模拟呈现液体的吸收结合效果,曲线经过固定污点的顶点Fu和移动污点的顶点Mu,再由FuMu线段中垂线上的一个controlPoint决定了一条曲线。UIBezierPath的API:addQuadCurveToPoint:controlPoint:。整个路径由曲线FuMu,曲线FdMd,和圆弧MuMd组成,最后由线段FuFd封闭。吸收效果截图:

回弹粘黏效果

做回弹效果前,我们先得让移动污点会跑到固定污点的后面,引入originRearX(出发点的后方)和finalRearX(终点的后方),修改移动污点的keyframeAnim:

1
2
3
4
anim.values = @[@(originX),    @(originX), @(finalX),    @(finalRearX), @(finalX),
@(finalX), @(originX),    @(originRearX), @(originX), @(originX)];
anim.keyTimes = @[@(0.0), @(0.25), @(0.35),    @(0.38), @(0.41),
@(0.75), @(0.85),    @(0.88), @(0.91), @(1.0)];//sleep 0.4 ratio

代码中可以看出,我定义了回弹时间为0.03 * 2,ok这里没有什么问题。

至于回弹的效果Path,因为两个污点的圆心距比较小,如果仍然沿用上一种路径方案效果会不太好,我们使用上图这种Path而不再使用模拟液体吸收的曲线方式。参考上图,我们通过两条线段,和一条与移动污点圆周重合的圆弧来组合粘黏效果Path,再由线段FuFd来闭合它,线段经过左固定污点,与溢出的移动污点相切。

还记得“是否包含”吗,移动污点从固定污点后面溢出来时也就不符合包含关系了,会影响到正面粘黏效果的作画,于是这里的回弹粘黏效果跟普通粘黏效果分开两个独立的CAShapeLayer来做的,避免干扰。回弹效果截图: 

总体思路就是这样了,主要的耗时工作就是计算path和协调几个CAShapeLayer的显示消失和互相影响,源代码Demo:http://download.csdn.net/detail/hbblzjy/9542348

利用PreLoader实现一个平视显示(HUD)效果(可以运用到加载等待效果),并进行简单的讲解的更多相关文章

  1. jquery Mobile点击显示加载等待效果

    点击某个按钮或链接时,触发等待加载效果: <script> <!-- $(document).bind("mobileinit", function(){ }); ...

  2. C#.Net网页加载等待效果漂亮并且简单

    最近网页加载数据比较多,点击后给用户就是白板很不友好,想了很久找了些资料,在网页加载中显示等待画面给客户,页面加载完成自动隐藏等待效果. 在网页后台cs代码:    protected void Pa ...

  3. 手机站全局的html+css加载等待效果

    本文只提供思路,CSS神马的自己定制吧,JS是可以优化的,比如,输出图片的JS也可以放到showdiv()里面,我没有做优化,只是实现,别笑话我,我比较懒... 基本思路:由于Html的解析是从上到下 ...

  4. js加载等待效果

    demo01: 加载首页的时候,可能会很缓慢,放一张等待图片. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN ...

  5. WPF防止界面卡死并显示加载中效果

    原文:WPF防止界面卡死并显示加载中效果 网上貌似没有完整的WPF正在加载的例子,所以自己写了一个,希望能帮到有需要的同学 前台: <Window x:Class="WpfApplic ...

  6. 一个很酷的加载loading效果--IT蓝豹

    一个很酷的加载loading效果,自定义LeafLoadingView实现,LeafLoadingView继承view, 本例子主要由以下几点构成 (1):RotateAnimation实现叶子旋转 ...

  7. 使用Bootstrap3和Ladda UI实现的多种按钮“加载中”效果体验

    在线演示 在线演示 大家在开发基于web的网站或者web应用中,常常在AJAX调用的过程中需要提示用户并且展示相关的“加载中”效果,类似的UI设计也非常多,比如,当点击一个按钮后,在它的旁边显示一个 ...

  8. ios 自定义加载动画效果

    在开发过程中,可能会遇到各种不同的场景需要等待加载成功后才能显示数据.以下是自定义的一个动画加载view效果.      在UIViewController的中加载等到效果,如下 - (void)vi ...

  9. apiCloud实现加载更多效果,基本完美~

    apiCloud实现加载更多效果 1.接口支持,加入参数page. $page = $this->_request('page','trim','1'); $pagesize = 10; // ...

随机推荐

  1. EXCEL技能之数据去重

    本篇不属于技术类博文,只是想找个地方记录而已,既然是我的博客嘛,那就自己想写什么就写什么了. CRM中有个EXCEL数据导入功能,几千条数据导入CRM后去重,那是死的心都有的.往回想想EXCEL是否有 ...

  2. Android文本框-android学习之旅(十七 )

    文本框简介 文本框属于基本的andoid控件,TextView继承了View是最基本的文本框,它的子类包括EditView和Button等,TextView的大部分方法,它的子类也可以使用. Text ...

  3. C++对象模型的那些事儿之二:对象模型(下)

    前言 上一篇博客C++对象模型的那些事儿之一为大家讲解了C++对象模型的一些基本知识,可是C++的继承,多态这些特性如何体现在对象模型上呢?单继承.多重继承和虚继承后内存布局上又有哪些变化呢?多态真正 ...

  4. 关于MySQL-python-1.2.3.tar.gz安装失败的解决方案

    关于MySQL-python-1.2.3.tar.gz安装失败的解决方案 RHEL6.4升级到python2.7.9,然后安装 MySQL-python-1.2.3.tar.gz, 报错.解决错误之后 ...

  5. ROS(indigo)ABB机器人MoveIt例子

    ROS(indigo)ABB机器人例子 参考网址: 1  http://wiki.ros.org/Industrial 2  http://wiki.ros.org/abb 3  https://gi ...

  6. UNIX网络编程——客户/服务器程序设计示范(四)

        TCP预先派生子进程服务器程序,accept使用线程上锁保护 我们使用线程上锁保护accept,因为这种方法不仅适用于同一进程内各线程之间的上锁,而且适用于不同进程之间的上锁.        ...

  7. (NO.00005)iOS实现炸弹人游戏(一):游戏的整体规划设计

    在这新的系列中,我们来尝试完成一款经典的游戏:炸弹人 这是以前红白机上的炸弹人,由于游戏可玩性强,玩法又简单,在后面的机型上陆续推出了很多款续作. 在随后的触屏设备上也出现了炸弹人的模拟版,用的是模拟 ...

  8. JPA(三)之实体关系一对多(多对一)

     1.背景介绍: 对于购买商品时,订单信息(Order)和订单商品信息(OrderItem)的关系就是一对多的关系. 2.实体bean: Order.java代码 ? 1 2 3 4 5 6 7 ...

  9. Swing——动作(Action)

    本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/41258997 Action接口扩展于ActionListe ...

  10. STL:字符串用法详解

    字符串是程序设计中最复杂的变成内容之一.STL string类提供了强大的功能,使得许多繁琐的编程内容用简单的语句就可完成.string字符串类减少了C语言编程中三种最常见且最具破坏性的错误:超越数组 ...