一、实现之后的效果 1.左边3个按钮,分别是 1)点击之后,汤姆猫掏出一只白色的小鸟,刚要吃,它飞走了, 2)汤姆猫放屁 3)汤姆猫喝牛奶, 右边3个按钮,分别是 1)汤姆猫用爪子把屏幕抓出3个道子, 2)汤姆猫用披萨饼砸屏幕, 3)汤姆猫拿出两个锣,对着一敲, 2.这些动画怎么实现的, 是不是很多很多图片顺序播放啊, 很多图片快速的切换,然后你眼睛感觉是不是它们就在动吧, 3.所以,这个app实现的方式,就是背景放一个大的图片框, 为什么是图片框,而不是按钮, 需要点吗,背景,不需要, 点一下,疑,有用了,汤姆猫被打倒了… 说明我在这儿放了一个透明的按钮, 其实,整个这儿背景是一个图片框,你点是没有用的, 它需要放一张图片,又不需要点击, 所以说,这儿用一个图片框就可以了, 4.左边3个按钮,右边3个按钮,也是小图片, 但是这些能点,说明这6个是什么啊,按钮吧 当你点击某个按钮的时候,我就通过动画的方式,快速的切换这个图片框中的每1张图片, 这样的话,看起来就是一个动画了, 5.看一下Supporting Files里面的素材, 1)Angry文件夹,是汤姆猫“生气”的动画,是一大堆图片,单看每一张没什么效果,但是快速切换的时候,就能看到汤姆猫生气的动画了 2)要实现这个效果,首先,我先把素材拷进来, 我们这里用到的素材,一个就是这些图片,再一个,就是这个tom.plist文件, 我们先把图片素材拷进来, 找到我们这个素材文件夹,找到里面的汤姆猫文件夹,找到里面的Animations文件夹,这些是不是我们具体使用的一些图片啊, 在以前xcode5的时候,只有这个图片是png图片的时候,才可以直接拷到images.xcassets文件夹下, 但是在xcode6的时候,你这个图片是jpg的图片,也可以直接拷到这个images.xcassets文件夹下, 只要是图片,都可以拷过来, 这是我们动画图片, 这个Buttons,是我们的按钮图片, 然后我把Animations文件夹,和Buttons文件夹,一起拽到我的Xcode的images.xcassets文件夹下, 接下来,我们就尝试把这个汤姆猫做一下,看一看怎么做 二、实现汤姆猫app, 1.打开storyboard, 选中控制器,size调成4.7inch, 2.背景放一个大的图片框,UIImageView控件, 让它充满整个屏幕, 我给它先来一张默认图,例如,来一个angry_00 这是在imageView右边的属性控制器里实现的,找到它的image属性,给个angry_00, 接下来,我们实现动画,就是要让这些图片,快速的切换, 我们已经学了两种动画方式, 一种是“头尾试” 通过调UIView的一个类方法,beginAnimation, 和一个setAnimationDuration, 以及commitAnimation, 通过这3个方法,可以执行一个“头尾式”动画吧, 第二种是调UIView的一个animateWithDuration,那个block方式, 接下来,我们要实现的这个汤姆猫的动画,跟前面那两个没关系, 这个不是说要让它的位置从一个地方移动到另一个地方,大小变化,在动画里面执行,不是这个意思, 现在的动画是,要让这个图片框,快速的切换图片,实现的动画, 3.这个动画的实现思路是这样的, 我们首先这个图片是在一个UIImageView,一个图片框里面吧,我们通过调图片框的一些方法,那么这个时候,图片框会把在图片框里面的图片,它自己会做动画,我们只要调图片框这个对象的一些方法,就ok了, 这个是完全通过图片框自己实现的, 和那两个动画没有关系, 4.那么接下来,我们看一下,这个图片框是怎么实现动画呢, 我们说,这个动画,叫做 UIImageView帧动画, 就是让图片框里面的图片一张一张变的动画, 既然它要让图片框里面的图片一张一张变, 你想想,这个图片框里面能只有1张图片吗, 1张图片,你再怎么变,它也不会动吧, 所以说,第一个,是要告诉这个图片框,它里面,有哪些图片,就是给它的animationImages,给这个属性设置值, @property(nonatomic,copy) NSArray *animationImages; 这个属性,是1个什么类型的属性,数组类型吧, 意味着,我们一开始,要把所有的图片,加载到这个数组里面, 也就是说,这个图片框,它首先有一个数组,这个数组里面,保存了它要执行动画的所有的图片, @property(nonatomic,copy) NSArray *animationImages; 那为什么@property的参数要用copy,是不是不希望把数组赋值给下划线属性之后,如果修改了赋值之前的数组,下划线属性也会跟着修改啊,不希望下划线属性跟着修改啊,所以用copy,深拷贝一份数组,然后赋值给下划线属性, 当然NSArray是不能修改的,但如果有时候是把NSMutableString赋值给这个下划线属性呢,是不是赋值前的变量就可以修改了,如果一修改,下划线属性就跟着改,这肯定是不好的,所以这里要用copy参数,深拷贝一份,然后赋值给下划线属性, setter方法是这样写的: - (void)setAnimationImages:(NSArray *)animationImages{ _animationImages = [animationImages copy]; } 这就好比Person类的一个属性是name,它是一个NSString类型的,但是有时候会把NSMutableString类型的字符串赋值给它,不希望赋值前的变量修改的时候,这个Person的name属性也跟着修改,所以NSString类型的属性,不论是在ARC还是在MRC下,都要用copy参数写property, @property(nonatomic,copy)NSString *name; 这时候,setter方法是这样的, - (void)setName:(NSString *)name{ _name = [name copy]; } 这样写,是把赋值前的变量name,深拷贝一份,赋值给_name属性, 因为NSString,在copy的时候,是浅拷贝,因为NSString修改的时候,是立刻生成一个新的字符串,所以不会影响Person的name属性, 但是NSMutableString,在copy的时候,是深拷贝,所以拷贝出来一个对象,赋值给Person的name属性,赋值前的变量即使修改,也不影响Person的name属性, NSString如果用mutableCopy方法,是深拷贝,生成一个NSMutableString变量, NSMubatleString使用mutableCopy方法,也是深拷贝,生成的还是NSMutableString变量, 3.好了,这是第一步,设置 @property(nonatomic,copy)NSArray *animationImages; 属性,这个属性的作用,是 需要播放的序列帧图片数组(里面都是UIImage对象,会按顺序显示里面的图片), 4.第二步,你告诉这个图片框,你这个动画要执行多长时间呢, 用这个属性, @property(nonatomic)NSTimeInterval animationDuration; 帧动画的持续时间, 比如说,喝牛奶这个动画,要在10秒钟完成,它是不是很慢, 我如果1秒钟执行完,它是不是立刻就喝完了, 也就是说,这个animationDuration,就是告诉图片框,切图片的时候,这个时间,用多长时间来切, 这个时间越短,动画看起来越快, 这个时间越长,动画看起来越快, 所以说,要让图片框里面的图片快速切换,实现动画, 第一,告诉图片框,哪些图片要实现动画, 第二,告诉图片框,动画持续时间, 第三,告诉图片框,这个动画是不是要重复播放, 也就是说给这个属性赋值: @property(nonatomic) NSInteger animationRepeatCount; 帧动画的执行次数(默认是无限循环) 如果这个动画重复播放,你告诉它“是”的话, 它在播放完一遍以后,紧接着第二遍继续播放,永不停止,一直重复播放, 但是我们要的是这个效果吗,不是, 所以这个animationRepeat,Repeat是重复的意思,这个animationRepeatCount,等于是“YES”还是“NO”,“NO”吧,不让它重复, 第四步,紧接着,你这些基本的条件都设置好以后,这个图片框知道它里面要显示的是哪些图片,它知道这个动画要执行多长时间,它也知道是否要重复啦, 接下来,就该怎么办了, startAnimating,是不是启动动画啊,用这个方法: - (void)startAnimating; 开始执行帧动画, 第五步,比如说,动画启动以后,还没有执行完毕,就想让动画停止,怎么办, 用这个stopAnimating方法, - (void)stopAnimating; 停止执行帧动画, 第六步,想知道现在动画是否正在执行,怎么办,用这个方法: - (BOOL)isAnimating; 是否正在执行帧动画, 6.所以说,现在要想实现这个“汤姆猫”, 1)第一步,拽一个大的图片框, 2)第二步,紧接着就是加载那一系列的图片,设置给图片框的animationImages属性,让它知道,哦,这些图片,我让它动, 3)第三步,然后我设置时间, 4)第四步,启动, 明白这一个思路了吧, 就是通过UIImageView,这个图片框的一个动画,和我们之前学的动画,是没有关系的, 三、那么,接下来看一下,要怎么做呢, 1.打开我们的这个程序, 我们现在,图片素材已经拷进来了, 但是我们还没有把这些图片加进来吧, 好,我们先不做那么多动画, 我们先做一个, 咱们先做一个“喝牛奶”, “喝牛奶”是不是图片比较多吧, 2.既然要“喝牛奶”,这个左下角,应该摆一个“喝牛奶”的小按钮吧, 拖一个按钮进来,放到左下角, 设置一下它这个喝牛奶的图片, 1)里面的文字,是“”,不要, 2)喝牛奶这个图片叫什么啊,drink吧, 我们设置一下这个按钮吧, 注意,我设置在背景属性background也行,设置在上面这个image属性也行, 如果你上面有文字,同时还要显示图片,这个时候一般情况下设置背景属性background, 但是我如果只想显示图片的时候,设image是不是也行啊, 我为什么设置image,不设置背景呢, 因为设置了背景以后,这个按钮不会跟随图片大小而变化, 但是我如果设置image以后,这个按钮会自动调整到和这个图片一样, 所以说,为了省事儿,我就设置image了, image属性,给个drink, ok,它是不是自动调整大小,和图片大小一样了, 我为了用这个功能,所以就设了个image, 3)这个按钮单击的时候,我们是不是要执行一段动画啊, 所以说,给这个按钮来一个单击事件, 先把其他5个按钮给你拖进来吧, 我要给这个drink按钮注册一个单击事件, 同时,单击的时候,等会儿我们需要访问这个图片框啊, 是不是我要给这个图片框动态的设置它的一些属性, 所以说,我要通过一个属性,来引用这个图片框, 辅助编辑器,打开,自动跳到这个延展中, 选中图片框,按住control键,拖线到延展里面, 设置4个参数, 第一个参数,Connection,给个Outlet, 第二个参数,Name属性,给个imgView, 是不是图片框,来个Cat,猫, 所以,name属性,给个imgViewCat, 第三个参数,Type,给个UIImageView, 第四个属性,Storage,给个Weak, 点击Connect,自动生成了这个属性: @property(weak,nonatomic)IBOutlet UIImageView *imgViewCat; 4)然后呢,drink按钮,拖线到延展中,设置5个参数, 第一个参数,Connection,给个Action, 第二个参数,Name,给个drink, 第三个参数,Type,给个id, 第四个参数,Event,给个Touch Up Inside, 第五个参数,Argument,给个None, 点击Connect,自动生成了这个方法: - (IBAction)drink; 5)接下来,我们来实现这个代码, //喝牛奶的动画, - (IBAction)drink{ //code; } 那么这个喝牛奶的动画,怎么来实现呢, 这个动画的思路,还记得吗, 1)动态加载图片,到一个NSArray中, 2)设置UIImageView,就是图片框,的animationImages属性,这个属性里面包含的是什么,这个属性中包含的就是所有那些要执行动画的图片, 3)设置动画持续时间, 4)设置动画是否需要重复播放, 5)开启动画, 你要给图片框设置这个属性,刚才我们是不是已经拖线,用这个imgViewCat表示这个大的图片框啊, 所以,这儿就来个self.imgViewCat.animationImages ,是不是设置这个属性啊, 要给它,给一个Array,这个NSArray里面,就包含了所有那些要执行动画的图片, 所以说,第一步,我们首先要把那些要执行动画的图片,是不是加载到一个NSArray里面, 好,注意看,现在我们是喝牛奶,喝牛奶那些图片在哪里, 我们发现,凡是喝牛奶的图片,都是drink下划线,后面跟两个索引来开头的啊, 所以说,接下来,我们就要一张一张去把图片加载起来, 并且把每张图片都放到一个数组的元素里面, 怎么样把这张图片,drink_00 ,加载到内存里面, 加载一张图片,怎么加, UIImage *img = [UIImage imageNamed:@这儿是不是要写那个图片的名称, UIImage *img = [UIImage imageNamed:@“drink_00”]; 因为这个图片的后缀是jpg,所以我们这儿是不是要加一个jpg后缀啊, UIImage *img = [UIImage imageNamed:@“drink_00.jpg”]; 这样的话,我们是不是就把这张图片加载到内存里面了, 但是,我们现在要的是只加载一张吗, 不是,我们现在要的是加载什么啊,是不是加载所有的图片啊, 从drink_00到drink_80, 是不是加载这么多张图片啊, 请问大家,如果是你的话,你会不会把这句话写80遍? UIImage *img = [UIImage imageNamed:@“drink_00.jpg”]; 如果你会的话,我告诉你,不需要,我们是不是可以写个循环啊, 6)写个循环: 那么,我们要把这句话,放到循环里面,注意看: UIImage *img = [UIImage imageNamed:@“drink_00.jpg”]; for(int i for循环,i等于几,等于0,i小于几,我们是不是一共80吧,应该是小于81吧,为什么小于81呢,因为这里的图片,是不是00到80吧,是不是要用80,所以来个81, 这儿一共是几张图,81张图,对吧, 0到81,循环81次, i怎么样,i++ for(int i=0;i<81;i++){ //statements; } 然后这里面,这张图片是不是要有循环,是不是要加载81次, UIImage *img = [UIImage imageNamed:@“drink_00.jpg”]; 所以,把这句话剪切到for循环中, for(int i=0;i<81;i++){ UIImage *img = [UIImage imageNamed:@“drink_00”]; } 但是,加载81次的时候,每次加载这张图,是不是要放到数组里面, 所以说,前面得有个啥, NSArray行不行, 不行,NSMutableArray吧, 因为这个NSArray是个不可变数组,是不是要可变, 是不是每次要向里面加一张图, 所以说,这里面不能用NSArray,要用NSMutableArray, NSMutableArray *arrayM, 一般我们后缀是M的时候,一般表示可变的, NSMutableArray *arrayM = [NSMutableArray array]; 好,这样的话,就创建了一个可变数组, 每次我们加载一张图,是不是要把这张图,放到这个数组里面, [arrayM addObject:img]; 也就是这样, - (IBAction)drink{ //0.动态加载图片到一个NSArray中 NSMutableArray *arrayM = [NSMutableArray array]; for(int i = 0 ; i < 81 ; i++){ UIImage *img = [UIImage imageNamed:@“drink_00.jpg”]; [arrayM addObject:img]; } 这样的话,是不是就循环,把这个图加进来了, 但是,哪儿有问题, 你这是把同样的图片,是不是加了80张进来, UIImage *img = [UIImage imageNamed:@“drink_00.jpg”]; 我们这个地方,怎么才能一张一张的变, 我们是不是首先要通过NSString,给这个图片名称,是不是要动态拼接这个图片名称, 这个图片名称要动态拼接,不能写死, NSString *imgName = [NSString stringWithFormat:@“drink_%d.jpg”,i]; 这儿该怎么写啊, 这儿,我写@“drink_%d.jpg”,这儿写i,请问对还是不对, 不对,为什么不对, 因为我这儿如果是第1张的话,这儿会变成个1,drink_1.jpg,请问有drink_1.jpg这张图片吗, 没有,只有drink_01.jpg, 也就是说,你这个i,如果是小于10的话,是不是也得保留两位啊, 所以,这儿应该写%2d,保留两位, @“drink_%2d.jpg”,i 保留两位,如果是3,是什么3,前面那个补一个几啊, 补一个0,所以是百分号零2d @“drink_%02d.jpg”,i 是不是这么来写啊, 百分号2d,表示保留两位, 百分号零2d,表示如果要是两位,第一位如果是空的话,给它补一个零, 千万别写百分号20d,这样的话,保留20位, %02d,这样的话,就表示i如果是个位的话,你要让它保留两位,必须得显示两位,那么第一位,如果要是空出来的话,用什么表示呢,用1个零来补齐, 这样的话,遇到0,就变成00, 遇到1,就变成01, 遇到3,就变成03, 遇到10,还是10, 因为它不是个位,不需要补,对吧, NSString *imgName = [NSString stringWithFormat:@“drink_%02d.jpg”,i]; 这样的话,就创建好了,加载好了图片, 然后,把这个地方写一个imgName, [arrayM addObject:imgName]; for(int i=0;i<81;i++){ NSString *imgName = [NSString stringWithFormat:@“drink_%02d.jpg”,i]; [arrayM addObject:imgName]; } 好,这是不是我们就创建好这个图片了吧, 这样的话,所有的图片都有了, 并且图片都加载到这个集合里面了,arrayM, 然后,接下来怎么办, 2)我们把这个集合,把这个数组,设置给我们这个图片框的animationImages属性, self.imgViewCat.animationImages = arrayM; 这样的话,这个图片框,就知道,我将来要把这些图片,做动画,要一张一张切换吧, 3)设置持续时间, self.imgViewCat.animationDuration, 是不是动画持续时间, 这个动画持续时间,应该是多长啊, 1秒也行,2秒也行,3秒也行,随便你, 但是你也可以试着来一下,你这个图片框,它里面有多少张图片, self.imgViewCat.animationImages.count, 让每张图片显示0.1秒, self.imgViewCat.animationImages.count * 0.1 这样的话,是不是也行, self.imgViewCat.animationDuration = self.imgViewCat.animationImages.count * 0.1; 这样的话,你有多少张图片,每张图片显示0.1秒,总的时间,是不是就是总的动画持续时间, 这样的话,相对来说是不是,就是比较,更自然一些吧, 4)然后呢,是否要重复播放呢, self.imgViewCat.animationRepeatCount = 等于几, 这个属性的签名如下: NSInteger animationRepeatCount 这个属性,是一个整型吧, 整型的话,重复几次,重复1次就可以了吧, 如果你不设置这个属性,它会一直重复的, 5)开启动画, 怎么开启动画, [self.imgViewCat startAnimating]; 好了,这样的话,就是 1)把图片加载到数组里面, 2)设置这个数组,给这个图片,给这个图片集合, 3)设置持续时间, 4)设置重复次数, 5)启动动画, 来,咱们看一下动画,能不能启动, 运行, 崩溃了,有一个方法未识别, unrecognized selector sent to instance, 为什么,因为这句话, NSString *imgName = [NSString stringWithFormat:@“drink_%02d.jpg”,I]; 这个*imgName,只是一个图片的名称吧, 还没有给它加载到内存里面呢, 没有调用那句话,imageNamed, [UIImage imageNamed:@“”]; 没有调用这个方法, UIImage *imgCat = [UIImage imageNamed:imgName]; [arrayM addObject:imgCat]; for(int i = 0 ; i < 81 ; i ++ ){ //1.拼接图片名称, NSString *imgName = [NSString stringWithFormat:@“drink_%02d.jpg”,i]; //2.根据图片名称加载图片, UIImage *imgCat = [UIImage imageNamed:imgName]; //3.把图片加载到数组中, [arrayM addObject:imgCat]; } 再运行,可以了, 如果不设置重复次数,将会一直重复, repeatCount如果没有设置,会一直重复, repeatCount,给它来个1次, self.imgViewCat.animationRepeatCount = 1; 这样是不是就ok了, 好了,这就是我们的图片框的帧动画是怎么实现的