需求:
实现动态tom猫案例。
分析
tom猫的动画本质多张图片快速播放,大概几十张。背景是图片框。涉及组件有:按钮、图片视图类UIImageView 。
需要实现功能是图片快速切换。涉及动画,与之前案例的放大、缩小不同。
其中,点击Tom猫的头能触发动画的本质是有一个没有文字和没有背景图片的UIButton。
动画播放相关的API:
animationImages:需要播放的图片组数
animationDuartion:动画持续时间
animationRepeatCount:执行次数
startAnimating:开始执行
stopAnimating:停止执行
isAnimating:判断是否在执行状态
要播放的图片组所有名称需要先用NSArray拼接,然后传给UIImage类实例,再把实例再传入相关动画的API。
实现:
- 创建组件:
多个可见按钮(带显示图的)、头部的按钮(没有文字和图片)、放tom猫的背景图视图。
@interface TomView : UIView
@property(nonatomic, strong) UIImageView* imgView;
@property(nonatomic, strong) UIButton* headbtn;
@property(nonatomic, strong) UIButton* btn1;
@property(nonatomic, strong) UIButton* btn2;
@property(nonatomic, strong) UIButton* btn3;
@property(nonatomic, strong) UIButton* btn4;
@property(nonatomic, strong) UIButton* btn5;
@property(nonatomic, strong) UIButton* btn6;
@end
- 初始化背景、组件
1> 初始化背景猫,该图片填充满整个视图。
2> 左侧三个可视按钮。
3> 添加每个按钮的事件
4> 加入子视图
-(instancetype) initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if(self){
// 初始化背景图
_imgView = [[UIImageView alloc] initWithFrame: self.bounds]; // self.view.bounds:本身是vie为,所以用self即可
_imgView.contentMode = UIViewContentModeScaleAspectFit;
_imgView.image = [UIImage imageNamed:@"angry_00.jpg"];
_imgView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_headbtn = [[UIButton alloc] initWithFrame:CGRectMake(85, 200, 220, 220)];
// 三个可视按钮
_btn1 = [[UIButton alloc] initWithFrame:CGRectMake(30, 500, 50, 50)];
_btn2 = [[UIButton alloc] initWithFrame:CGRectMake(30, 570, 50, 50)];
_btn3 = [[UIButton alloc] initWithFrame:CGRectMake(30, 640, 50, 50)];
// 隐藏头部的按钮
[_btn1 setBackgroundImage:[UIImage imageNamed:@"drink.png"] forState:UIControlStateNormal];
[_btn2 setBackgroundImage:[UIImage imageNamed:@"fart.png"] forState:UIControlStateNormal];
[_btn3 setBackgroundImage:[UIImage imageNamed:@"eat.png"] forState:UIControlStateNormal];
[_headbtn addTarget:self action:@selector(head1) forControlEvents:UIControlEventTouchUpInside];
[_btn1 addTarget:self action:@selector(drink1) forControlEvents:UIControlEventTouchUpInside];
[_btn2 addTarget:self action:@selector(fart1) forControlEvents:UIControlEventTouchUpInside];
[_btn3 addTarget:self action:@selector(eat) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_imgView];
[self addSubview:_btn1];
[self addSubview:_btn2];
[self addSubview:_btn3];
[self addSubview:_headbtn];
}
return self;
}
- 动画的实现
tom猫有多种动作:喝奶、拍脑袋就晕、放P,这些动作的本质都是加载图片,将图片赋值给动画API。具体过程如下:
1> 图片加载到数组中
2> 设置UIImageView的animationImages属性
3> 动画持续时间
4> 重复次数
5> 启动动画
以下提供了三个函数的拆分写法以及封装写法,在上面的按钮绑定的事件函数为封装后的代码。
注意加载图片是否会占用内存,初始化UIImage类有两种方法。
#import "TomView.h"
@implementation TomView
// 对三个函数进行拆分写法
-(void) drink{
// 旧的方式:图片加载到数组,使用缓存,不建议这么实现,图片太多,全加载到内存中占用太大
// 将图片名称全部读取出:由于图片资源序号占两位,所以用字符串填充
// 把图片全装入数组,用可变数组,可以装任意内容
NSMutableArray *arrayM = [NSMutableArray array];
for(int i = 0; i <= 80; i++){
NSString *imgName = [NSString stringWithFormat:@"drink_%02d.jpg", i];
UIImage *imgCat = [UIImage imageNamed:imgName];
[arrayM addObject:imgCat];
}
// 1. 图片数组赋值给动画API:
self.imgView.animationImages = arrayM;
// 2 持续时间
self.imgView.animationDuration = self.imgView.animationImages.count * 0.1;
// 3. 设置是否重复播放
self.imgView.animationRepeatCount = 1;
// 4. 开启动画
[self.imgView startAnimating];
}
-(void) fart{
// bundle方式,不占缓存:要求是资源文件不能放在Assets中
//[];
NSMutableArray *arrayM = [NSMutableArray array];
for(int i = 0; i <= 27; i++){
NSString *imgName = [NSString stringWithFormat:@"fart_%02d.jpg", i];
// 通过图片名称去App安装路径中寻找资源文件
NSString *path = [[NSBundle mainBundle] pathForResource:imgName ofType:nil];
// 以路径获得图片对象:从磁盘上读速度虽然慢,但是不占内存
UIImage *imgCat = [UIImage imageWithContentsOfFile:path];
[arrayM addObject:imgCat];
}
// OC中:UIImage加载图片时有两种主要方法:imageWithContentsOfFile: 和 imageNamed:
// 如drink函数中用UIImage的方式会使UIImage 会留在内存中,
// 且点击新按钮,会不断加到内存中去
// 1. 设置UIImageView
self.imgView.animationImages = arrayM;
// 2. 动画时长
self.imgView.animationDuration = self.imgView.animationImages.count*0.1;
// 3. 重复次数
self.imgView.animationRepeatCount = 1;
// 4. 开启
[self.imgView startAnimating];
// 清除缓存的方式探究::
// 存图片的数组中仍然占内存 释放图片数组内存的方式
// 4. 直接销毁会因为运行太快动画没显示就把数组清空了
// 以下不可取
//self.imgView.animationImages = nil;
// 如果做判断才销毁则不会销毁,因为那一时刻监测到正在执行
// 以下不可取
// if(!self.imgView.isAnimating){
// self.imgView.animationImages = nil;
// }
// 所以有专门的动画延迟释放API:
// 停顿3秒后销毁的API:
[self.imgView performSelector:@selector(setAnimationImages:) withObject:nil afterDelay:self.imgView.animationImages.count*0.1];
//
}
-(void) head
{
// 1. 图片数组
NSMutableArray *arrayM = [NSMutableArray array];
// 2 根据名称图片、利用bundle获取所有图片并添加到array中
for(int i = 0; i <= 80; i++){
NSString *imgName = [NSString stringWithFormat:@"knockout_%02d.jpg", i];
// 通过图片名称去App安装路径中寻找资源文件
NSString *path = [[NSBundle mainBundle] pathForResource:imgName ofType:nil];
// 以路径获得图片对象:从磁盘上读速度虽然慢,但是不占内存
UIImage *imgCat = [UIImage imageWithContentsOfFile:path];
[arrayM addObject:imgCat];
}
// 动画设置
self.imgView.animationImages = arrayM;
// 动画时长
self.imgView.animationDuration = self.imgView.animationImages.count*0.1;
// 重复次数
self.imgView.animationRepeatCount = 1;
// 开启
[self.imgView startAnimating];
// 清空数组,释放内存
[self.imgView performSelector:@selector(setAnimationImages:) withObject:nil afterDelay:self.imgView.animationImages.count*0.1];
}
- (void) drink1{
[self startAnimation:80 picName:@"drink"];
}
- (void) fart1{
[self startAnimation:27 picName:@"fart"];
}
- (void) head1{
[self startAnimation:80 picName:@"knockout"];
}
// 封装写法:动画执行次数、需要在路径中寻找的名称
-(void) startAnimation:(int)count picName:(NSString *)btnName
{
// 当动画正在执行,点击其它按钮应该无反应
if(self.imgView.isAnimating){
return;
}
// 1. 把图片加载到数组中
NSMutableArray *arrayM = [NSMutableArray array];
// 2. 拼接图片名称
for(int i = 0 ; i <= count; i++)
{
NSString *imgName = [NSString stringWithFormat:@"%@_%02d.jpg", btnName, i];
// 从手机安装路径中读取该名的图片资源为对象,不占内存
NSString *path = [[NSBundle mainBundle] pathForResource:imgName ofType:nil];
//
UIImage *imgCat = [UIImage imageWithContentsOfFile:path];
//
[arrayM addObject:imgCat];
}
// 设置作为动画的图片数组
self.imgView.animationImages = arrayM;
// 3. 动画持续时间
self.imgView.animationDuration = 3;
// 4. 重复次数
self.imgView.animationRepeatCount = 1;
// 5. 启动动画:
[self.imgView startAnimating];
// 6. 清空缓存
[self.imgView performSelector:@selector(setAnimationImages:) withObject:nil afterDelay:self.imgView.animationImages.count*0.1];
}
@end
注意问题:
- 动画的内存占用:
初始化图片类有两种方式,一种根据图片名直接加载到内存中,速度较快,但不推荐。因为动画本质是一组图片,内存中图片会越来越多
使用NSBundle方式。不要使用缓存:直接从资源文件的路径中去获取即可。
注意:用bundle方式,需要把图片资源放到文件夹下,放在Assets中找不到。因为在App中,Assets中的文件不在主目录下。
该Demo中动画API和前面动画API的写法均不同,前面用了首尾式、block式。
· 以下是如果不适用bundle加载图片会导致项目占用内存过大的效果图。
- 最后一组图片内存释放问题
最后一组图片加载后还会保存在内存中,需要清空,但清空需要注意延迟几秒后做。
用if(运行状态)来检测后清空,清空函数不会执行。所以需要延迟等动画执行完再情况图片有专门的延迟函数。
以下是几种清除思路,只有最后一种正确:使用特定API。
// 清除缓存的方式探究::
// 1. 存图片的数组中仍然占内存 释放图片数组内存的方式
// . 直接销毁会因为运行太快动画没显示就把数组清空了
// 不可取
//self.imgView.animationImages = nil;
// 2. 如果做判断才销毁则不会销毁,因为那一时刻监测到正在执行
// 以下不可取
// if(!self.imgView.isAnimating){
// self.imgView.animationImages = nil;
// }
// 3 . 专门的动画延迟释放API:在代码中有
// 停顿3秒后销毁的API:
- tom猫背景没显示,后缀赋值错了
- 动画图片加载报错:
无非就是图片数量没对应上。
- 初始化某控件没显示出来:
仔细查看subView。
- 发现动画没播放:
没有使用播放动画函数。
- 报错: No visible @interface for ‘TomView’ declares the selector ‘startAnimation1:picName:’
在一个自定义函数调用另外一个自定义函数时,因为命名错了。(可以不声明)