iOS实现SpriteAnimation

研究了一下抖音的动态贴图,抓包抖音的资源发现,其动态贴图资源包(一个zip压缩包)包括一张包含所有帧的大图,一个标识所有帧位置的json和一个lua脚本,还有其他一些配置文件,
这些最终合成了一个帧动画,lua脚本中有个什么EffectSDK,设置帧率,旋转,放大等效果,帧率一般为16。

其中把所有帧放到同一张图片然后和配置文件(可能是json或其他格式的,标明每一帧在图片中的位置的)一起使用上是游戏开发中常用的手段,一般称为spriteSheet,这种动画可以称为 spriteSheet Animation 或者spriteAnimation,可以参考 https://www.codeandweb.com/texturepacker 抖音也是用这个工具的。

有了这些资源,就可以实现动画了

其实如何实现动画texturePackers的官网上有相关教程,参考https://www.codeandweb.com/texturepacker/tutorials/uikit-animations-with-texturepacker

核心是利用了CALayer的contentsRect属性;

/* A rectangle in normalized image coordinates defining the
* subrectangle of the `contents' property that will be drawn into the
* layer. If pixels outside the unit rectangles are requested, the edge
* pixels of the contents image will be extended outwards. If an empty
* rectangle is provided, the results are undefined. Defaults to the
* unit rectangle [0 0 1 1]. Animatable. */
@property CGRect contentsRect;

contentsRect配合bounds,这两个属性就可以实现显示图片的某一部分,然后依次显示图片的指定部分,就是动画了;
他的代码自定义了一个frameIndex属性,然后通过给这个属性添加动画来实现最终的效果;(如何给CALayer添加自定义属性动画可以参考:Layer 中自定义属性的动画,这里不再复述;)
我感觉自定义frameIndex然后对其做动画没不是必须的,可以直接给layer的contentsRect和bounds做keyframeAnimation,可以达到完全一样的效果。

这里还更深入的理解了一下CALayer,主要是presentationLayer和modelLayer,参考:https://blog.csdn.net/u013282174/article/details/50388546

顺便记录一下开发过程中遇到的小问题:

1,CALayer的隐式动画

当直接修改CALayer的标为animatable的属性时,系统不是瞬间就将其变过来的,而是会经历一个动画,就是系统的隐式动画。
如果想要关闭这个隐式动画,主要有两种方式:

  1)使用CATransaction并将DisableActions设置为YES;注意不是CATransition!!!别看错了,我因为没看清这个被自己坑了半天。。。

     需要注意的是,这个方法仅针对这次修改有效,如果之后再普通的修改属性,还是会触发隐式动画

    [CATransaction begin];

    [CATransaction setValue:@(true) forKey:kCATransactionDisableActions];

    //在这里修改属性 不会触发隐式动画 

    [CATransaction commit];

   2)将对应属性的action设置为nil;

    注意:这种方法是针对整个实例生效的,设置之后,之后随便修改属性,都不会触发隐式动画

//1 如果要自定义类,且这个类的所有实例都要忽略某个属性的隐式动画,可以用这几句代码

+ (id<CAAction>)defaultActionForKey:(NSString *)event {

    if ([event isEqualToString:@"contentsRect"]) {

        return (id<CAAction>)[NSNull null];

    }

    return [super defaultActionForKey:event];

}

//2 如果仅仅是某个实例要忽略,可以用这一句代码

self.actions = @{@"contentsRect" : [NSNull null]};

2, removedOnCompletion属性,

anim1.removedOnCompletion = false;//没有这一句的话动画根本就不会开始,奇葩了
anim1.fillMode = kCAFillModeForwards;

之前没记得removedOnCompletion这么重要啊,这次在写动画的时候,只要不将其设置为false,动画就不会开始。。。
连最简单的position动画,从左移动到右的这种,没有设置removedOnCompletion都不行。。。难道是iOS12以后系统改了?

3,如果使用了自定义属性的动画,并且实现了dealloc的方法,会发现在动画过程中dealloc会不停的调用,
因为不知道CALayer的designedInit方法是什么,所以不知道是不是也在不停的创建,
难道是系统是用runtime创建了CALayer的子类,然后用子类怎么实现了自定义属性动画???
有知道原因的,望不吝赐教

4, @property(copy) CAAnimationCalculationMode calculationMode;
这次第一次注意到calculationMode这个属性,关键是用了kCAAnimationDiscrete这个值。
这个值的意思离散插值,注意了,不是不插值,而是让值保持不变,具体还是官方文档说的最清楚:https://developer.apple.com/documentation/quartzcore/kcaanimationdiscrete
而且还有一点需要注意的是:如果用了discrete,那keyframeAnimation的keyTimes要比values多一个,且第一个必须为0,最后一个必须为1,官方文档说的:

* If the calculationMode is set to kCAAnimationLinear or kCAAnimationCubic, the first value in the array must be 0.0 and the last value must be 1.0\. All intermediate values represent time points between the start and end times.

* If the calculationMode is set to kCAAnimationDiscrete, the first value in the array must be 0.0 and the last value must be 1.0\. The array should have one more entry than appears in the values array. For example, if there are two values, there should be three key times.

* If the calculationMode is set to kCAAnimationPaced or kCAAnimationCubicPaced, the values in this property are ignored.

5,CAKeyframeAnimation的keyTimes属性是可选的,如果设置了values又没有设置keyTimes的话,就会平均计算插值,
所以一般情况下来说,如果不需要精确控制帧的时间,还是不设置的为好。

6,CALayer的position一定要注意

如果单纯设置CALayer的bounds,那其position还是(0,0),如果设置CALayer的frame,那其position会被同时设置到frame的center。

7,CALayer的contents也是可以用来做动画的,网上很多现实gif的功能就是通过给contents做CAKeyframeAnimation来实现的。
这里有个坑是:这个方法会很占内存,图片在所占的内存大概是(CGImage.with * CGImage.height * 4 / 1024 / 1024 )M, 如果是UIImage的话就要乘以scale;
如果帧动画要用50张图片,每张1M的好,就要占50M,如果很不巧有一张大图是2000*2000的,那内存直接就爆了,APP就直接被kill掉了。。。。

8,在实现sprite动画的时候,想到了可以用把每一帧截出来保存成CGImage数组的方式:

NSMutableArray *imageArray = [NSMutableArray array];

    CGFloat scale = 1;

    for (int i = 0; i < self.frameArray.count; i++) {

        OJATextureFrameModel *targetFrame = [self.frameArray objectAtIndex:i];

        CGRect bounds = CGRectMake(targetFrame.frame.x * scale,

                                   targetFrame.frame.y * scale,

                                   targetFrame.frame.w * scale,

                                   targetFrame.frame.h * scale);

        CGImageRef subImage = CGImageCreateWithImageInRect(image.CGImage, bounds);

        [imageArray addObject:(__bridge id _Nonnull)(subImage)];

    }

按CGImageCreateWithImageInRect的文档

/* Create an image using the data contained within the subrectangle `rect'
   of `image'.
   The new image is created by
     1) adjusting `rect' to integral bounds by calling "CGRectIntegral";
     2) intersecting the result with a rectangle with origin (0, 0) and size
        equal to the size of `image';
     3) referencing the pixels within the resulting rectangle, treating the
        first pixel of the image data as the origin of the image.
   If the resulting rectangle is the null rectangle, this function returns
   NULL.
   If W and H are the width and height of image, respectively, then the
   point (0,0) corresponds to the first pixel of the image data; the point
   (W-1, 0) is the last pixel of the first row of the image data; (0, H-1)
   is the first pixel of the last row of the image data; and (W-1, H-1) is
   the last pixel of the last row of the image data.
   The resulting image retains a reference to the original image, so you may
   release the original image after calling this function. */

创建出来的新的CGImage会对原来图片的强引用,那应该不会重新分配内存才对,
按我的理解,比如把一张1000 * 1000 的图分解成100张100*100的图,所占的内存应该是一样的。

在iOS12上也确实是这样,直接对contents添加keyframeAnimation,所占用的内存就是一张大图的内存(貌似解析图片的时候会卡一下。。。)
但在iOS11上,内存直接就爆了,在模拟器上内存比较大,才看出来,内存的大小是 小图的个数 * 大图的内存。。。。。。感觉是系统bug呀,解决不了。

9,如果上了上面所说的方法,创建了一个CGImage的array,那一定要在dealloc的时候将里面的CGImage挨个CGImageRelease,否则就会内存泄露,而且没有任何提示,
也不会影响layer的是否,如果不是刚好看了内存变化,根本发现不了。。。。

10,beginTime属性,或者timeOffset属性,
如果想动画迟计秒再开始,可以利用beginTime属性来实现,
如果想要实现动画的暂停与继续,就稍微麻烦一点了,可以参考的代码

- (void)pause

{

    if(state == TPSPRITE_RUNNING)

    {

        state = TPSPRITE_PAUSED;

        CFTimeInterval pausedTime = [self convertTime:CACurrentMediaTime() fromLayer:nil];

        self.speed = 0.0;

        self.timeOffset = pausedTime;

    }

}

- (void)resume

{

    if(state == TPSPRITE_PAUSED)

    {

        state = TPSPRITE_RUNNING;

        CFTimeInterval pausedTime = [self timeOffset];

        self.speed = 1.0;

        self.timeOffset = 0.0;

        self.beginTime = 0.0;

        CFTimeInterval timeSincePause = [self convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;

        self.beginTime = timeSincePause;

    }

}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容