您的位置:首页 > 移动开发 > IOS开发

ios基础+动画

2017-01-12 18:43 471 查看
obj->method(argument);
Objective-C则写成:
[obj
method: argument];
1111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111111111111111
方法的存储位置在内存中每个类的方法都存储在类对象中每个方法都有一个与之对应的SEL类型的数据根据一个SEL数据就可以找到对应的方法地址,进而调用方法SEL类型的定义:  typedef struct objc_selector *SEL2.SEL对象的创建
SEL s1 = @selector(test1); // 将test1方法包装成SEL对象 
SEL s2 = NSSelectorFromString(@"test1"); // 将一个字符串方法转换成为SEL对象

222222222222222222222222222222222222222222222222222222222222
2222222222222222222222222222222222222222222222222222222222222222222222222
22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
NSURL初始化方法:
33333333333333333333333333333333333333333333333333333333
333333333333333333333333333333333333333333333333333333333333333
3333333333333333333333333333333333333333333333333333333333333333333333333333
.表示的是类的成员变量,[
]表示的是类的成员函数
a
= obj.foo
等价于
a = [obj
foo]

obj.foo
= 1
则等价于
[obj setFoo:1]
4444444444444444444444444444444444444444444444444444444444444
4444444444444444444444444444444444444444444444444444444444444444444444
刚开始接触OC再看别人写的代码的时候,常常困惑于人家在声明属性时的写法,总结出来有三中方式,不知道哪一种比较规范化,现在我把三种方式贴出来,然后再一一探讨每个方式声明属性的区别。方式一:直接在@interface中的大括号中声明。@interface MyTest : NSObject{NSString *mystr;}方式二:在@interface中声明,然后再在@property中声明。@interface MyTest : NSObject{NSString *_mystr;}@property (strong, nonatomic) NSString *mystr;随后在.m文件中加入@synthesize mystr = _myStr;方式三:直接用@property声明@interface MyTest : NSObject{}@property (strong, nonatomic) NSString *mystr;随后在.m文件中加入@synthesize mystr = _myStr;==========================我是分割线============================首先来说一下方式一根方式三的区别,使用方式一声明的成员变量是只能在自己类内部使用的,而不能在类的外部使用,(就是通过 类名. 点的方式是显示不出来的),方式三则相反,它可以在类的外部访问,在类的内部可以通过下划线+变量名或者self.变量名的方式来访问。方式二的写法是一种过时的声明变量的方式,xcode在早期@systhesize没有自动合成属性器之前,需要手写getter与setter方法,下划线从风格上表明这是类的内部变量,要是需要直接使用变量则需要使用get或者set的方式。在XCode目前有了自动合成属性器后,编译器会自动帮我们生成一个以下划线开头的的实例变量,所以我们不必去同时声明属性与变量。 我们可以直接用@property的方式来声明一个成员属性,在.m文件中使不使用@systhesize都无所谓,xcode会自动帮你生成getter与setter.个人比较喜欢使用方式三的方式,这是是苹果开发模板所推荐的,也可以在.m文件中不加@systhesize看个人喜好吧。
5555555555555555555555555555555555555555555555555555555555555555
55555555555555555555555555555555555555555555555555555555555555555555555555555555
5555555555555555555555555555555555555555555555555555555555555555555555555555555555555


OC学习篇之---@property和@synthesize的使用

2014-12-14 16:45 20570人阅读 评论(2) 收藏 举报
分类:IOS(28)
版权声明:本文为博主原创文章,未经博主允许不得转载。在之前一片文章我们介绍了OC中的内存管理:http://blog.csdn.net/jiangwei0910410003/article/details/41924683,今天我们来介绍两个关键字的使用:@property和@synthesize一、@property关键字这个关键字是OC中能够快速的定义一个属性的方式,而且他可以设置一些值,就可以达到一定的效果,比如引用计数的问题下面来看一下他的使用方法:[objc] view
plain copy



//// Person.h// 25_Property//// Created by jiangwei on 14-10-12.// Copyright (c) 2014年 jiangwei. All rights reserved.//#import <Foundation/Foundation.h>@interface User : NSObject{//NSString *_userName;//NSString *_passWord;//...}//第一步生成_userName属性//第二步为_userName属性自动生成set/get方法//property在生成的set方法中,有没有做引用的操作?//set方法的三种方式://第一种方式://普通赋值//一般对象类型的引用操作//NSString对象类型的引用操作//第一个位置//atomic:线程保护的,默认//nonatomic:线程不保护的//第二个位置//assign:直接赋值,默认//retain:保留对象//copy:拷贝对象//第三个位置//readwrite:生成get/set方法,默认//readonly:只生成get方法<pre code_snippet_id="551736" snippet_file_name="blog_20141214_1_9125791" name="code" class="objc">@property NSString *userName;</pre><br>@end还记得我们之前定义属性的时候,在{...}中进行定义,而且定义完之后还有可能需要实现get/set方法,这里我们直接使用@property关键字进行定义:[objc] view
plain copy



@property NSString *userName;这样定义完之后,我们就可以使用这个属性了:这样定义的方式之后,这个属性就会自动有set/get方法了第一步生成_userName属性第二步为_userName属性自动生成set/get方法
这样定义是不是比以前方便多了下面再来看一下他还有三个值可以设置:[objc] view
plain copy



@property(atomic,retain,readwrite) Dog *dog;1、第一个位置的值:atomic:线程保护的,默认nonatomic:线程不保护的2、第二个位置的值:assign:直接赋值,默认retain:保留对象,内部会自动调用retain方法,引用计数+1copy:拷贝对象3、第三个位置的值:readwrite:生成get/set方法,默认readonly:只生成get方法这里来做一个例子:main.m[objc] view
plain copy



//// main.m// 25_Property//// Created by jiangwei on 14-10-12.// Copyright (c) 2014年 jiangwei. All rights reserved.//#import <Foundation/Foundation.h>#import "User.h"#import "Dog.h"//当一个类中有很多个属性的时候,那么我们需要手动的编写他们的set/get方法//这样就比较费时,所以这时候就可以使用@propertyint main(int argc, const charchar * argv[]) {User *user = [[User alloc] init];Dog *dog = [[Dog alloc] init];NSLog(@"count:%ld",[dog retainCount]);[user setDog:dog];NSLog(@"count:%ld",[dog retainCount]);return 0;}运行结果:二、@synthesize关键字[java] view
plain copy



//// Person.m// 25_Property//// Created by jiangwei on 14-10-12.// Copyright (c) 2014年 jiangwei. All rights reserved.//#import <Foundation/Foundation.h>#import "User.h"//有时候我们不想定义属性为_开头的//这时候我们就可以使用@synthesize,来修改我们想要的属性名//这时候属性_userName变成了$userName@implementation User@synthesize userName = $userName;@end因为我们使用@property定义属性之后,如果我们想修改这个属性的名称,就可以使用@synthesize关键字来对属性名称进行修改[objc] view
plain copy



@synthesize userName = $userName;总结这一篇主要介绍了两个关键字的使用,@property和@synthesize,特别是@property关键字,后面定义属性的时候几乎就是用它了,非常方便6666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666666666666666666


@synthesize和@dynamic区别

在声明property属性后,有2种实现选择
@synthesize
编译器期间,让编译器自动生成getter/setter方法。
当有自定义的存或取方法时,自定义会屏蔽自动生成该方法@dynamic
告诉编译器,不自动生成getter/setter方法,避免编译期间产生警告
然后由自己实现存取方法
或存取方法在运行时动态创建绑定:主要使用在CoreData的实现NSManagedObject子类时使用,由Core Data框架在程序运行的时动态生成子类属性
@interface@implementation
1.@interfaceJava等语言编程中,创建类都是用class,但在object-c中,用@interface。例子:@interface circle :NSObject //定义名为circle的类,继承自NSObject{ShapeColorfillColor;//定义ShapeColor类型的实例变量 fillColor;Shapebounds;//定义Shape类型的实例变量 bounds;}- (void)setFillColor:(ShapeColor)fillColor;//定义名为setFillColor的方法2.@implementation在object-c中,@interface定义放在.h文件中,而@implementation放在.m 文件,是.h文件的详细实现。例子:@implementation circle //结尾没有分号。- (void)setFillColor:(ShapeColor)c{fillColor= c;//把参数c赋值给@interface中定义的fillColor;}3.实例化对象为了使用我们已经定义的类,我们需要实例化对象,我们可以在我们创建的文件中的main()函数实例化:如下int main(int argc, const char * argv[]){idshapes[3];//定义id类型数组shapes[0]= [circle new];//实例化circle并赋值给shapes[shapes[0]setFillColor:kRedColor];//调用方法进行赋值}今天就到这里,下次讲一下xcode4.x如何使用。hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh1、在头文件中:C代码



@property int count;[c] view
plain copy@property int count;等效于在头文件中声明2个方法:C代码



- (int)count;-(void)setCount:(int)newCount;[c] view
plain copy- (int)count;-(void)setCount:(int)newCount;777777777777777777777777777777777777777777777777
777777777777777777777777777777777777777777777777777777777777
77777777777777777777777777777777777777777777777777777777777777777777777


property
synthesize dynamic 的不同和区别

property:只是对getter和setter方法进行了声明,其他的什么也没有干。
synthesize:主要是对getter和 setter方法进行了实现,并且自动把你添加实例变量如果没有实例变量的话。实现主要是根据property特性的设置,例如property设置特性为 copy(retain)、readonly、assgin等。
dynamic:主要是告诉编译器说 不要给我生成 实例变量、getter、setter方法。我要自己生成。如果你没有自己生成当你在调用getter或者setter方法的时候,程序就会发生crash。
上代码
头文件中:
(1)实现文件中:
实现文件中这样写。确实很完美。这样我们就很自然的生成 name、age 实例变量的getter 和setter方法。
(2)改变实文件中的写法
在这里需要注意了,@synthesize name = _name; 这句话我们分为三部分来看。 第一部分:@synthesize:oc的关键字。第二部分:name:这里的name是指的property的后面那个变量的标识,如果我把头文件中的@property(nonatomic, copy)NSString *name;这句话中的“name” 改变为“nickName”,那么@synthesize 中的name 也应该相应的改变为nickName,否则就会出错。第三部分:_name,可能你会在这里迷惑,为什么会出来一个_name的标识呢?而且还没有出错。首先我们要明确的是这第三部分是是指实例变量。也就说那个实例变量要与property相对应。可能你会发现我的头文件中并没有_name的实例变量啊。这样难道不会报错吗?答案是不会的因为@synthsize
有自动创建实例变量的能力。如果编译器在实例变量列表中没有看找见_name 那么编译器就会自动的创建一个_name实例变量。这样我们在程序别的地方可以调用实力变量: _name = @"hello";这样是正确的。
(3)更改实现文件
如果这样写。就是说name的getter方法和setter方法没有实现。如果你在程序中调用 self setName: 这个方法会报错。
总结:
其实@Property 只是说明了 我们声明一个方法。而@synthesize 告诉我们 方法中的主角是谁,也就说要给那个实例变量赋值。
注意:
现在我们写程序可能只需要写@property (nonatomic, retain)NSString *nickName; 并没有发现什么@synthesize,那么是因为现在一句@property 相当做了两件事,一件事 他原本的含义声明getter和setter方法。一件是:@synthesize nickname= _nickname;所以我们现在实例变量都是加下划线的啊“_”.这是一个很好的编程规范。标签: prope1)Book.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>@interface Book :NSObject
{
@private
__strong NSString *_name;
__strong NSString *_author;
}@property(nonatomic, copy) NSString *name;
@property(nonatomic, copy) NSString *author;
@property(nonatomic, copy) NSString*version;@end2)Book.m
#import "Book.h"@implementation Book@dynamic name;
@dynamicauthor;
@synthesizeversion = _version;- (id)init
{
self = [super init];
if(self)
{
}
return self;
}- (NSString *)name
{
if(nil == _name)
{
_name = @"you forgot inputbook name";
}
return _name;
}- (void)setName:(NSString *)name
{
_name = name;
NSLog(@"_name address:%p", _name);
}- (NSString *)author
{
if(nil == _author)
{
_author = @"you forgot inputbook author";
}
return _author;
}- (void)setAuthor:(NSString *)author
{
_author = author;
}@end
88888888888888888888888888888888888888888888888888888888888
888888888888888888888888888888888888888888888888888888888888888888
888888888888888888888888888888888888888888888888888888888888888888888888888888888

ASIHTTPRequest类库简介和使用说明

官方网站: http://allseeing-i.com/ASIHTTPRequest/ 。可以从上面下载到最新源码,以及获取到相关的资料。
使用iOS SDK中的HTTP网络请求API,相当的复杂,调用很繁琐,ASIHTTPRequest就是一个对CFNetwork API进行了封装,并且使用起来非常简单的一套API,用Objective-C编写,可以很好的应用在Mac OS X系统和iOS平台的应用程序中。ASIHTTPRequest适用于基本的HTTP请求,和基于REST的服务之间的交互。
ASIHTTPRequest功能很强大,主要特色如下:l 通过简单的接口,即可完成向服务端提交数据和从服务端获取数据的工作
l 下载的数据,可存储到内存中或直接存储到磁盘中
l 能上传本地文件到服务端
l 可以方便的访问和操作请求和返回的Http头信息
l 可以获取到上传或下载的进度信息,为应用程序提供更好的体验
l 支持上传或下载队列,并且可获取队列的进度信息
l 支持基本、摘要和NTLM身份认证,在同一会话中授权凭证会自动维持,并且可以存储在Keychain(Mac和iOS操作系统的密码管理系统)中
l 支持Cookie
l 当应用(iOS 4+)在后台运行时,请求可以继续运行
l 支持GZIP压缩数据
l 内置的ASIDownloadCache类,可以缓存请求返回的数据,这样即使没有网络也可以返回已经缓存的数据结果
l ASIWebPageRequest –可以下载完整的网页,包括包含的网页、样式表、脚本等资源文件,并显示在UIWebView /WebView中。任意大小的页面都可以无限期缓存,这样即使没有网络也可以离线浏览
l 支持客户端证书
l 支持通过代理发起Http请求
l 支持带宽限制。在iOS平台,可以根据当前网络情况来自动决定是否限制带宽,例如当使用WWAN(GPRS/Edge/3G)网络时限制,而当使用WIFI时不做任何限制
l 支持断点续传
l 支持同步和异步请求
999999999999999999999999999999999999999999999999999999999999999999
9999999999999999999999999999999999999999999999999999999999999999999999999999
999999999999999999999999999999999999999999999999999999999999999999999999999999999999999


NSNotificationCenter 的使用详解

通常我们在 iOS 中发生什么事件时该做什么是由 Delegate 实现的,例如 View 加载完后会触发 viewDidLoad。Apple 还为我们提供了另一种通知响应方式,那就是 NSNotification,系统中(UIKeyboardDidShowNotification 等) 以及某些第三方组件(例如 ASIHTTPRequest 的 kReachabilityChangedNotification 等)。
NSNotificationCenter 较之于 Delegate 可以实现更大的跨度的通信机制,可以为两个无引用关系的两个对象进行通信。NSNotificationCenter 的通信原理使用了观察者模式:1. NSNotificationCenter 注册观察者对某个事件(以字符串命名)感兴趣,及该事件触发时该执行的 Selector 或 Block2. NSNotificationCenter 在某个时机激发事件(以字符串命名)3. 观察者在收到感兴趣的事件时,执行相应的 Selector 或 Block使用 NSNotificationCenter 的步骤示例代码:1. 定义一个事件到来时该执行的方法:

1 - (void)execute:(NSNotification *)notification {
2     //do something when received notification
3     //notification.name is @"NOTIFICATION_NAME"
4     if(notification.object && [notification.object isKindOfClass:[Test class]]){
5         //do something
6     }
7 }

2. 注册观察者:
1 [[NSNotificationCenter defaultCenter] addObserver:self
2                                          selector:@selector(execute:)
3                                              name:@"NOTIFICATION_NAME"
4                                            object:nil];
使用默认的通知中心,上面代码的意义的,观察者 self 在收到名为 @"NOTIFICATION_NAME" 的事件是执行 @selector(execute:),最后一个参数是表示会对哪个发送者对象发出的事件作出响应,nil 时表示接受所有发送者的事件。还有一种注册观察者的方式是用方法:- (id)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *))block3. 激发事件,即通知相应的观察者

[[NSNotificationCenter defaultCenter] postNotificationName:@"NOTIFICATION_NAME"
object:nil];
//或者用下面几行代码,明确的 notification 示例
Test *test = [[Test alloc] init];
NSNotification *notification = [NSNotification notificationWithName:@"NOTIFICATION_NAME"
object:test];
[[NSNotificationCenter defaultCenter] postNotification:notification];

这里的 object 参数对应到方法 - (void)execute:(NSNotification *)notification 里的 notification.object, name 就是 notification.name。代码到这里,方法 - (void)execute:(NSNotification *)notification 就会得到执行了。说明:我们上面用的 [NSNotificationCenter defaultCenter] 同一个实例,你也可以使用自己的 NSNotificationCenter,如:NSNotificationCenter *notificationCenter = [[NSNotificationCenter alloc]init];事件只会在当前的 NotificationCenter 中广播,不同的 NotificationCenter 之间的事件通知互不相干。NSNotificationCenter 比之 Delegate 的好处就是事件发出者与响应者可以完全不认识,例如你在某个类中注册了 A 观察者某 E 事件的响应,你可以在程序的任何代码 X 中激发 E,A 的相应选择器即被触发,对象 A 与 X 完全是松散的。最后,你的观察者如果对一些事件没兴趣了,应该从 NotificationCenter 中移除掉:
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"NOTIFICATION_NAME"
object:test];//object 与注册时相同
//或[[NSNotificationCenter defaultCenter] removeObserver:self];
系统里定义了许多的 XxxNotification 名称,其实只要 Cmd+Shift+O 打开 Open Quickly,输入 nsnotification 或者 uinotification 可以看到许多以 Notification 结尾的变量定义,由变量名称也能理解在什么时候会激发什么事件,一般都是向 [NSNotificationCenter defaultCenter] 通知的。比如你想对系统的某些事件时作出响应只要注册一个观察者即可,想要在每次键盘显示后得到通知就得关心 UIKeyboardDidShowNotification 事件:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification
object:nil];
键盘显示后就会执行 self 的 keyboardDidShow 方法。我们可以多看看第三方 Objective-C 库是怎么运用的 NSNotificationCenter。Cocoa 给我们另一种事件通知模型就是 KVO(Key-Value Obsering),基于 NSKeyValueObserving 非正式协议。99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999


iOS开发系列--让你的应用“动”起来

2014-09-15 06:29 by KenshinCui, 94332 阅读, 108 评论, 收藏, 编辑--iOS核心动画


概览

在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看到iOS中如何使用图层精简非交互式绘图,如何通过核心动画创建基础动画、关键帧动画、动画组、转场动画,如何通过UIView的装饰方法对这些动画操作进行简化等。在今天的文章里您可以看到动画操作在iOS中是如何简单和高效,很多原来想做但是苦于没有思路的动画在iOS中将变得越发简单:CALayerCALayer简介
CALayer常用属性
CALayer绘图Core Animation基础动画
关键帧动画
动画组
转场动画
逐帧动画UIView动画封装基础动画
关键帧动画
转场动画目 录


CALayer


CALayer简介

在介绍动画操作之前我们必须先来了解一个动画中常用的对象CALayer。CALayer包含在QuartzCore框架中,这是一个跨平台的框架,既可以用在iOS中又可以用在Mac OS X中。在使用Core Animation开发动画的本质就是将CALayer中的内容转化为位图从而供硬件操作,所以要熟练掌握动画操作必须先来熟悉CALayer。
在上一篇文章中使用Quartz 2D绘图时大家其实已经用到了CALayer,当利用drawRect:方法绘图的本质就是绘制到了UIView的layer(属性)中,可是这个过程大家在上一节中根本体会不到。但是在Core Animation中我们操作更多的则不再是UIView而是直接面对CALayer。下图描绘了CALayer和UIView的关系,在UIView中有一个layer属性作为根图层,根图层上可以放其他子图层,在UIView中所有能够看到的内容都包含在layer中:




CALayer常用属性

在iOS中CALayer的设计主要是了为了内容展示和动画操作,CALayer本身并不包含在UIKit中,它不能响应事件。由于CALayer在设计之初就考虑它的动画操作功能,CALayer很多属性在修改时都能形成动画效果,这种属性称为“隐式动画属性”。但是对于UIView的根图层而言属性的修改并不形成动画效果,因为很多情况下根图层更多的充当容器的做用,如果它的属性变动形成动画效果会直接影响子图层。另外,UIView的根图层创建工作完全由iOS负责完成,无法重新创建,但是可以往根图层中添加子图层或移除子图层。
下表列出了CALayer常用的属性:
属性说明是否支持隐式动画
anchorPoint和中心点position重合的一个点,称为“锚点”,锚点的描述是相对于x、y位置比例而言的默认在图像中心点(0.5,0.5)的位置
backgroundColor图层背景颜色
borderColor边框颜色
borderWidth边框宽度
bounds图层大小
contents图层显示内容,例如可以将图片作为图层内容显示
contentsRect图层显示内容的大小和位置
cornerRadius圆角半径
doubleSided图层背面是否显示,默认为YES
frame图层大小和位置,不支持隐式动画,所以CALayer中很少使用frame,通常使用bounds和position代替
hidden是否隐藏
mask图层蒙版
maskToBounds子图层是否剪切图层边界,默认为NO
opacity透明度 ,类似于UIView的alpha
position图层中心点位置,类似于UIView的center
shadowColor阴影颜色
shadowOffset阴影偏移量
shadowOpacity阴影透明度,注意默认为0,如果设置阴影必须设置此属性
shadowPath阴影的形状
shadowRadius阴影模糊半径
sublayers子图层
sublayerTransform子图层形变
transform图层形变
隐式属性动画的本质是这些属性的变动默认隐含了CABasicAnimation动画实现,详情大家可以参照Xcode帮助文档中“Animatable Properties”一节。
在CALayer中很少使用frame属性,因为frame本身不支持动画效果,通常使用bounds和position代替。
CALayer中透明度使用opacity表示而不是alpha;中心点使用position表示而不是center。
anchorPoint属性是图层的锚点,范围在(0~1,0~1)表示在x、y轴的比例,这个点永远可以同position(中心点)重合,当图层中心点固定后,调整anchorPoint即可达到调整图层显示位置的作用(因为它永远和position重合)为了进一步说明anchorPoint的作用,假设有一个层大小100*100,现在中心点位置(50,50),由此可以得出frame(0,0,100,100)。上面说过anchorPoint默认为(0.5,0.5),同中心点position重合,此时使用图形描述如图1;当修改anchorPoint为(0,0),此时锚点处于图层左上角,但是中心点poition并不会改变,因此图层会向右下角移动,如图2;然后修改anchorPoint为(1,1,),position还是保持位置不变,锚点处于图层右下角,此时图层如图3。



下面通过一个简单的例子演示一下上面几个属性,程序初始化阶段我们定义一个正方形,但是圆角路径调整为正方形边长的一般,使其看起来是一个圆形,在点击屏幕的时候修改图层的属性形成动画效果(注意在程序中没有直接修改UIView的layer属性,因为根图层无法形成动画效果):
//
// KCMainViewController.m
// CALayer
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"
#define WIDTH 50@interface KCMainViewController ()@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self drawMyLayer];
}#pragma mark 绘制图层
-(void)drawMyLayer{
CGSize size=[UIScreen mainScreen].bounds.size;//获得根图层
CALayer *layer=[[CALayer alloc]init];
//设置背景颜色,由于QuartzCore是跨平台框架,无法直接使用UIColor
layer.backgroundColor=[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0].CGColor;
//设置中心点
layer.position=CGPointMake(size.width/2, size.height/2);
//设置大小
layer.bounds=CGRectMake(0, 0, WIDTH,WIDTH);
//设置圆角,当圆角半径等于矩形的一半时看起来就是一个圆形
layer.cornerRadius=WIDTH/2;
//设置阴影
layer.shadowColor=[UIColor grayColor].CGColor;
layer.shadowOffset=CGSizeMake(2, 2);
layer.shadowOpacity=.9;
//设置边框
// layer.borderColor=[UIColor whiteColor].CGColor;
// layer.borderWidth=1;//设置锚点
// layer.anchorPoint=CGPointZero;[self.view.layer addSublayer:layer];}#pragma mark 点击放大
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=[touches anyObject];
CALayer *layer=self.view.layer.sublayers[0];
CGFloat width=layer.bounds.size.width;
if (width==WIDTH) {
width=WIDTH*4;
}else{
width=WIDTH;
}
layer.bounds=CGRectMake(0, 0, width, width);
layer.position=[touch locationInView:self.view];
layer.cornerRadius=width/2;
}
@end


运行效果:




CALayer绘图

上一篇文章中重点讨论了使用Quartz 2D绘图,当时调用了UIView的drawRect:方法绘制图形、图像,这种方式本质还是在图层中绘制,但是这里会着重介绍一下如何直接在图层中绘图。在图层中绘图的方式跟原来基本没有区别,只是drawRect:方法是由UIKit组件进行调用,因此里面可以使用一些UIKit封装的方法进行绘图,而直接绘制到图层的方法由于并非UIKit直接调用因此只能用原生的Core Graphics方法绘制。
图层绘图有两种方法,不管使用哪种方法绘制完必须调用图层的setNeedDisplay方法(注意是图层的方法,不是UIView的方法,前面我们介绍过UIView也有此方法)通过图层代理drawLayer: inContext:方法绘制
通过自定义图层drawInContext:方法绘制

使用代理方法绘图

通过代理方法进行图层绘图只要指定图层的代理,然后在代理对象中重写-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx方法即可。需要注意这个方法虽然是代理方法但是不用手动实现CALayerDelegate,因为CALayer定义中给NSObject做了分类扩展,所有的NSObject都包含这个方法。另外设置完代理后必须要调用图层的setNeedDisplay方法,否则绘制的内容无法显示。
下面的代码演示了在一个自定义图层绘制一张图像并将图像设置成圆形,这种效果在很多应用中很常见,如最新版的手机QQ头像就是这种效果:
//
// KCMainViewController.m
// CALayer
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"
#define PHOTO_HEIGHT 150@interface KCMainViewController ()@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];//自定义图层
CALayer *layer=[[CALayer alloc]init];
layer.bounds=CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT);
layer.position=CGPointMake(160, 200);
layer.backgroundColor=[UIColor redColor].CGColor;
layer.cornerRadius=PHOTO_HEIGHT/2;
//注意仅仅设置圆角,对于图形而言可以正常显示,但是对于图层中绘制的图片无法正确显示
//如果想要正确显示则必须设置masksToBounds=YES,剪切子图层
layer.masksToBounds=YES;
//阴影效果无法和masksToBounds同时使用,因为masksToBounds的目的就是剪切外边框,
//而阴影效果刚好在外边框
// layer.shadowColor=[UIColor grayColor].CGColor;
// layer.shadowOffset=CGSizeMake(2, 2);
// layer.shadowOpacity=1;
//设置边框
layer.borderColor=[UIColor whiteColor].CGColor;
layer.borderWidth=2;//设置图层代理
layer.delegate=self;//添加图层到根图层
[self.view.layer addSublayer:layer];//调用图层setNeedDisplay,否则代理方法不会被调用
[layer setNeedsDisplay];
}#pragma mark 绘制图形、图像到图层,注意参数中的ctx是图层的图形上下文,其中绘图位置也是相对图层而言的
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
// NSLog(@"%@",layer);//这个图层正是上面定义的图层
CGContextSaveGState(ctx);//图形上下文形变,解决图片倒立的问题
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, 0, -PHOTO_HEIGHT);UIImage *image=[UIImage imageNamed:@"photo.png"];
//注意这个位置是相对于图层而言的不是屏幕
CGContextDrawImage(ctx, CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT), image.CGImage);// CGContextFillRect(ctx, CGRectMake(0, 0, 100, 100));
// CGContextDrawPath(ctx, kCGPathFillStroke);CGContextRestoreGState(ctx);
}@end


运行效果:



使用代理方法绘制图形、图像时在drawLayer:inContext:方法中可以通过事件参数获得绘制的图层和图形上下文。在这个方法中绘图时所有的位置都是相对于图层而言的,图形上下文指的也是当前图层的图形上下文。
需要注意的是上面代码中绘制图片圆形裁切效果时如果不设置masksToBounds是无法显示圆形,但是对于其他图形却没有这个限制。原因就是当绘制一张图片到图层上的时候会重新创建一个图层添加到当前图层,这样一来如果设置了圆角之后虽然底图层有圆角效果,但是子图层还是矩形,只有设置了masksToBounds为YES让子图层按底图层剪切才能显示圆角效果。同样的,有些朋友经常在网上提问说为什么使用UIImageView的layer设置圆角后图片无法显示圆角,只有设置masksToBounds才能出现效果,也是类似的问题。

扩展1--带阴影效果的圆形图片裁切

如果设置了masksToBounds=YES之后确实可以显示图片圆角效果,但遗憾的是设置了这个属性之后就无法设置阴影效果。因为masksToBounds=YES就意味着外边框不能显示,而阴影恰恰作为外边框绘制的,这样两个设置就产生了矛盾。要解决这个问题不妨换个思路:使用两个大小一样的图层,下面的图层负责绘制阴影,上面的图层用来显示图片。
//
// KCMainViewController.m
// CALayer
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"
#define PHOTO_HEIGHT 150@interface KCMainViewController ()@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];CGPoint position= CGPointMake(160, 200);
CGRect bounds=CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT);
CGFloat cornerRadius=PHOTO_HEIGHT/2;
CGFloat borderWidth=2;//阴影图层
CALayer *layerShadow=[[CALayer alloc]init];
layerShadow.bounds=bounds;
layerShadow.position=position;
layerShadow.cornerRadius=cornerRadius;
layerShadow.shadowColor=[UIColor grayColor].CGColor;
layerShadow.shadowOffset=CGSizeMake(2, 1);
layerShadow.shadowOpacity=1;
layerShadow.borderColor=[UIColor whiteColor].CGColor;
layerShadow.borderWidth=borderWidth;
[self.view.layer addSublayer:layerShadow];//容器图层
CALayer *layer=[[CALayer alloc]init];
layer.bounds=bounds;
layer.position=position;
layer.backgroundColor=[UIColor redColor].CGColor;
layer.cornerRadius=cornerRadius;
layer.masksToBounds=YES;
layer.borderColor=[UIColor whiteColor].CGColor;
layer.borderWidth=borderWidth;//设置图层代理
layer.delegate=self;//添加图层到根图层
[self.view.layer addSublayer:layer];//调用图层setNeedDisplay,否则代理方法不会被调用
[layer setNeedsDisplay];
}#pragma mark 绘制图形、图像到图层,注意参数中的ctx是图层的图形上下文,其中绘图位置也是相对图层而言的
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
// NSLog(@"%@",layer);//这个图层正是上面定义的图层
CGContextSaveGState(ctx);//图形上下文形变,解决图片倒立的问题
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, 0, -PHOTO_HEIGHT);UIImage *image=[UIImage imageNamed:@"photo.jpg"];
//注意这个位置是相对于图层而言的不是屏幕
CGContextDrawImage(ctx, CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT), image.CGImage);// CGContextFillRect(ctx, CGRectMake(0, 0, 100, 100));
// CGContextDrawPath(ctx, kCGPathFillStroke);CGContextRestoreGState(ctx);
}
@end


运行效果:



扩展2--图层的形变

从上面代码中大家不难发现使用Core Graphics绘制图片时会倒立显示,对图层的图形上下文进行了反转。在前一篇文章中也采用了类似的方法去解决这个问题,但是在那篇文章中也提到过如果直接让图像沿着x轴旋转180度同样可以达到正确显示的目的,只是当时的旋转靠图形上下文还无法绕x轴旋转。今天学习了图层之后,其实可以控制图层直接旋转而不用借助于图形上下文的形变操作,而且这么操作起来会更加简单和直观。对于上面的程序,只需要设置图层的transform属性即可。需要注意的是transform是CATransform3D类型,形变可以在三个维度上进行,使用方法和前面介绍的二维形变是类似的,而且都有对应的形变设置方法(如:CATransform3DMakeTranslation()、CATransform3DMakeScale()、CATransform3DMakeRotation())。下面的代码通过CATransform3DMakeRotation()方法在x轴旋转180度解决倒立问题:
//
// 形变演示
// CALayer
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"
#define PHOTO_HEIGHT 150@interface KCMainViewController ()@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];CGPoint position= CGPointMake(160, 200);
CGRect bounds=CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT);
CGFloat cornerRadius=PHOTO_HEIGHT/2;
CGFloat borderWidth=2;//阴影图层
CALayer *layerShadow=[[CALayer alloc]init];
layerShadow.bounds=bounds;
layerShadow.position=position;
layerShadow.cornerRadius=cornerRadius;
layerShadow.shadowColor=[UIColor grayColor].CGColor;
layerShadow.shadowOffset=CGSizeMake(2, 1);
layerShadow.shadowOpacity=1;
layerShadow.borderColor=[UIColor whiteColor].CGColor;
layerShadow.borderWidth=borderWidth;
[self.view.layer addSublayer:layerShadow];//容器图层
CALayer *layer=[[CALayer alloc]init];
layer.bounds=bounds;
layer.position=position;
layer.backgroundColor=[UIColor redColor].CGColor;
layer.cornerRadius=cornerRadius;
layer.masksToBounds=YES;
layer.borderColor=[UIColor whiteColor].CGColor;
layer.borderWidth=borderWidth;//利用图层形变解决图像倒立问题
layer.transform=CATransform3DMakeRotation(M_PI, 1, 0, 0);//设置图层代理
layer.delegate=self;//添加图层到根图层
[self.view.layer addSublayer:layer];//调用图层setNeedDisplay,否则代理方法不会被调用
[layer setNeedsDisplay];
}#pragma mark 绘制图形、图像到图层,注意参数中的ctx时图层的图形上下文,其中绘图位置也是相对图层而言的
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
// NSLog(@"%@",layer);//这个图层正是上面定义的图层
UIImage *image=[UIImage imageNamed:@"photo.jpg"];
//注意这个位置是相对于图层而言的不是屏幕
CGContextDrawImage(ctx, CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT), image.CGImage);
}@end


事实上如果仅仅就显示一张图片在图层中当然没有必要那么麻烦,直接设置图层contents就可以了,不牵涉到绘图也就没有倒立的问题了。
//
// 图层内容设置
// CALayer
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"
#define PHOTO_HEIGHT 150@interface KCMainViewController ()@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];CGPoint position= CGPointMake(160, 200);
CGRect bounds=CGRectMake(0, 0, PHOTO_HEIGHT, PHOTO_HEIGHT);
CGFloat cornerRadius=PHOTO_HEIGHT/2;
CGFloat borderWidth=2;//阴影图层
CALayer *layerShadow=[[CALayer alloc]init];
layerShadow.bounds=bounds;
layerShadow.position=position;
layerShadow.cornerRadius=cornerRadius;
layerShadow.shadowColor=[UIColor grayColor].CGColor;
layerShadow.shadowOffset=CGSizeMake(2, 1);
layerShadow.shadowOpacity=1;
layerShadow.borderColor=[UIColor whiteColor].CGColor;
layerShadow.borderWidth=borderWidth;
[self.view.layer addSublayer:layerShadow];//容器图层
CALayer *layer=[[CALayer alloc]init];
layer.bounds=bounds;
layer.position=position;
layer.backgroundColor=[UIColor redColor].CGColor;
layer.cornerRadius=cornerRadius;
layer.masksToBounds=YES;
layer.borderColor=[UIColor whiteColor].CGColor;
layer.borderWidth=borderWidth;
//设置内容(注意这里一定要转换为CGImage)
UIImage *image=[UIImage imageNamed:@"photo.jpg"];
// layer.contents=(id)image.CGImage;
[layer setContents:(id)image.CGImage];//添加图层到根图层
[self.view.layer addSublayer:layer];
}@end

既然如此为什么还大费周章的说形变呢,因为形变对于动画有特殊的意义。在动画开发中形变往往不是直接设置transform,而是通过keyPath进行设置。这种方法设置形变的本质和前面没有区别,只是利用了KVC可以动态修改其属性值而已,但是这种方式在动画中确实很常用的,因为它可以很方便的将几种形变组合到一起使用。同样是解决动画旋转问题,只要将前面的旋转代码改为下面的代码即可:
[layer setValue:@M_PI forKeyPath:@"transform.rotation.x"];


当然,通过key path设置形变参数就需要了解有哪些key path可以设置,这里就不再一一列举,大家可以参照Xcode帮助文档中“CATransform3D Key Paths”一节,里面描述的很详细。

使用自定义图层绘图

在自定义图层中绘图时只要自己编写一个类继承于CALayer然后在drawInContext:中绘图即可。同前面在代理方法绘图一样,要显示图层中绘制的内容也要调用图层的setNeedDisplay方法,否则drawInContext方法将不会调用。
前面的文章中曾经说过,在使用Quartz 2D在UIView中绘制图形的本质也是绘制到图层中,为了说明这个问题下面演示自定义图层绘图时没有直接在视图控制器中调用自定义图层,而是在一个UIView将自定义图层添加到UIView的根图层中(例子中的UIView跟自定义图层绘图没有直接关系)。从下面的代码中可以看到:UIView在显示时其根图层会自动创建一个CGContextRef(CALayer本质使用的是位图上下文),同时调用图层代理(UIView创建图层会自动设置图层代理为其自身)的draw:
inContext:
方法并将图形上下文作为参数传递给这个方法。而在UIView的draw:inContext:方法中会调用其drawRect:方法,在drawRect:方法中使用UIGraphicsGetCurrentContext()方法得到的上下文正是前面创建的上下文。
KCLayer.m
//
// KCLayer.m
// CALayer
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCLayer.h"@implementation KCLayer-(void)drawInContext:(CGContextRef)ctx{
NSLog(@"3-drawInContext:");
NSLog(@"CGContext:%@",ctx);
// CGContextRotateCTM(ctx, M_PI_4);
CGContextSetRGBFillColor(ctx, 135.0/255.0, 232.0/255.0, 84.0/255.0, 1);
CGContextSetRGBStrokeColor(ctx, 135.0/255.0, 232.0/255.0, 84.0/255.0, 1);
// CGContextFillRect(ctx, CGRectMake(0, 0, 100, 100));
// CGContextFillEllipseInRect(ctx, CGRectMake(50, 50, 100, 100));
CGContextMoveToPoint(ctx, 94.5, 33.5);//// Star Drawing
CGContextAddLineToPoint(ctx,104.02, 47.39);
CGContextAddLineToPoint(ctx,120.18, 52.16);
CGContextAddLineToPoint(ctx,109.91, 65.51);
CGContextAddLineToPoint(ctx,110.37, 82.34);
CGContextAddLineToPoint(ctx,94.5, 76.7);
CGContextAddLineToPoint(ctx,78.63, 82.34);
CGContextAddLineToPoint(ctx,79.09, 65.51);
CGContextAddLineToPoint(ctx,68.82, 52.16);
CGContextAddLineToPoint(ctx,84.98, 47.39);
CGContextClosePath(ctx);CGContextDrawPath(ctx, kCGPathFillStroke);
}@end


KCView.m
//
// KCView.m
// CALayer
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCView.h"
#import "KCLayer.h"@implementation KCView-(instancetype)initWithFrame:(CGRect)frame{
NSLog(@"initWithFrame:");
if (self=[super initWithFrame:frame]) {
KCLayer *layer=[[KCLayer alloc]init];
layer.bounds=CGRectMake(0, 0, 185, 185);
layer.position=CGPointMake(160,284);
layer.backgroundColor=[UIColor colorWithRed:0 green:146/255.0 blue:1.0 alpha:1.0].CGColor;//显示图层
[layer setNeedsDisplay];[self.layer addSublayer:layer];
}
return self;
}-(void)drawRect:(CGRect)rect{
NSLog(@"2-drawRect:");
NSLog(@"CGContext:%@",UIGraphicsGetCurrentContext());//得到的当前图形上下文正是drawLayer中传递的
[super drawRect:rect];}-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
NSLog(@"1-drawLayer:inContext:");
NSLog(@"CGContext:%@",ctx);
[super drawLayer:layer inContext:ctx];}@end


KCMainViewController.m
//
// KCMainViewController.m
// CALayer
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"
#import "KCView.h"@interface KCMainViewController ()@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];KCView *view=[[KCView alloc]initWithFrame:[UIScreen mainScreen].bounds];
view.backgroundColor=[UIColor colorWithRed:249.0/255.0 green:249.0/255.0 blue:249.0/255.0 alpha:1];[self.view addSubview:view];
}@end


运行效果:




Core Animation

大家都知道在iOS中实现一个动画相当简单,只要调用UIView的块代码即可实现一个动画效果,这在其他系统开发中基本不可能实现。下面通过一个简单的UIView进行一个图片放大动画效果演示:
//
// KCMainViewController.m
// Animation
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"@interface KCMainViewController ()@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];UIImage *image=[UIImage imageNamed:@"open2.png"];
UIImageView *imageView=[[UIImageView alloc]init];
imageView.image=image;
imageView.frame=CGRectMake(120, 140, 80, 80);
[self.view addSubview:imageView];//两秒后开始一个持续一分钟的动画
[UIView animateWithDuration:1 delay:2 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
imageView.frame=CGRectMake(80, 100, 160, 160);
} completion:nil];
}
@end

使用上面UIView封装的方法进行动画设置固然十分方便,但是具体动画如何实现我们是不清楚的,而且上面的代码还有一些问题是无法解决的,例如:如何控制动画的暂停?如何进行动画的组合?。。。
这里就需要了解iOS的核心动画Core Animation(包含在Quartz Core框架中)。在iOS中核心动画分为几类:基础动画、关键帧动画、动画组、转场动画。各个类的关系大致如下:


CAAnimation:核心动画的基础类,不能直接使用,负责动画运行时间、速度的控制,本身实现了CAMediaTiming协议。
CAPropertyAnimation:属性动画的基类(通过属性进行动画设置,注意是可动画属性),不能直接使用。
CAAnimationGroup:动画组,动画组是一种组合模式设计,可以通过动画组来进行所有动画行为的统一控制,组中所有动画效果可以并发执行。
CATransition:转场动画,主要通过滤镜进行动画效果设置。
CABasicAnimation:基础动画,通过属性修改进行动画参数控制,只有初始状态和结束状态。
CAKeyframeAnimation:关键帧动画,同样是通过属性进行动画参数控制,但是同基础动画不同的是它可以有多个状态控制。
基础动画、关键帧动画都属于属性动画,就是通过修改属性值产生动画效果,开发人员只需要设置初始值和结束值,中间的过程动画(又叫“补间动画”)由系统自动计算产生。和基础动画不同的是关键帧动画可以设置多个属性值,每两个属性中间的补间动画由系统自动完成,因此从这个角度而言基础动画又可以看成是有两个关键帧的关键帧动画。


基础动画

在开发过程中很多情况下通过基础动画就可以满足开发需求,前面例子中使用的UIView代码块进行图像放大缩小的演示动画也是基础动画(在iOS7中UIView也对关键帧动画进行了封装),只是UIView装饰方法隐藏了更多的细节。如果不使用UIView封装的方法,动画创建一般分为以下几步:
1.初始化动画并设置动画属性
2.设置动画属性初始值(可以省略)、结束值以及其他动画属性
3.给图层添加动画
下面以一个移动动画为例进行演示,在这个例子中点击屏幕哪个位置落花将飞向哪里。
//
// KCMainViewController.m
// Animation
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"@interface KCMainViewController (){
CALayer *_layer;
}@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];//设置背景(注意这个图片其实在根图层)
UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];//自定义一个图层
_layer=[[CALayer alloc]init];
_layer.bounds=CGRectMake(0, 0, 10, 20);
_layer.position=CGPointMake(50, 150);
_layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGImage;
[self.view.layer addSublayer:_layer];
}#pragma mark 移动动画
-(void)translatonAnimation:(CGPoint)location{
//1.创建动画并指定动画属性
CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"position"];//2.设置动画属性初始值和结束值
// basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不设置,默认为图层初始状态
basicAnimation.toValue=[NSValue valueWithCGPoint:location];//设置其他动画属性
basicAnimation.duration=5.0;//动画时间5秒
//basicAnimation.repeatCount=HUGE_VALF;//设置重复次数,HUGE_VALF可看做无穷大,起到循环动画的效果
// basicAnimation.removedOnCompletion=NO;//运行一次是否移除动画//3.添加动画到图层,注意key相当于给动画进行命名,以后获得该动画时可以使用此名称获取
[_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
}#pragma mark 点击事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=touches.anyObject;
CGPoint location= [touch locationInView:self.view];
//创建并开始动画
[self translatonAnimation:location];
}@end


运行效果:



上面实现了一个基本动画效果,但是这个动画存在一个问题:动画结束后动画图层回到了原来的位置,当然是用UIView封装的方法是没有这个问题的。如何解决这个问题呢?
前面说过图层动画的本质就是将图层内部的内容转化为位图经硬件操作形成一种动画效果,其实图层本身并没有任何的变化。上面的动画中图层并没有因为动画效果而改变它的位置(对于缩放动画其大小也是不会改变的),所以动画完成之后图层还是在原来的显示位置没有任何变化,如果这个图层在一个UIView中你会发现在UIView移动过程中你要触发UIView的点击事件也只能点击原来的位置(即使它已经运动到了别的位置),因为它的位置从来没有变过。当然解决这个问题方法比较多,这里不妨在动画完成之后重新设置它的位置。
//
// KCMainViewController.m
// Animation
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"@interface KCMainViewController (){
CALayer *_layer;
}@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];//设置背景(注意这个图片其实在根图层)
UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];//自定义一个图层
_layer=[[CALayer alloc]init];
_layer.bounds=CGRectMake(0, 0, 10, 20);
_layer.position=CGPointMake(50, 150);
_layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGImage;
[self.view.layer addSublayer:_layer];
}#pragma mark 移动动画
-(void)translatonAnimation:(CGPoint)location{
//1.创建动画并指定动画属性
CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"position"];//2.设置动画属性初始值和结束值
// basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不设置,默认为图层初始状态
basicAnimation.toValue=[NSValue valueWithCGPoint:location];//设置其他动画属性
basicAnimation.duration=5.0;//动画时间5秒
//basicAnimation.repeatCount=HUGE_VALF;//设置重复次数,HUGE_VALF可看做无穷大,起到循环动画的效果
// basicAnimation.removedOnCompletion=NO;//运行一次是否移除动画
basicAnimation.delegate=self;
//存储当前位置在动画结束后使用
[basicAnimation setValue:[NSValue valueWithCGPoint:location] forKey:@"KCBasicAnimationLocation"];//3.添加动画到图层,注意key相当于给动画进行命名,以后获得该动画时可以使用此名称获取
[_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
}#pragma mark 点击事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=touches.anyObject;
CGPoint location= [touch locationInView:self.view];
//创建并开始动画
[self translatonAnimation:location];
}#pragma mark - 动画代理方法
#pragma mark 动画开始
-(void)animationDidStart:(CAAnimation *)anim{
NSLog(@"animation(%@) start.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
NSLog(@"%@",[_layer animationForKey:@"KCBasicAnimation_Translation"]);//通过前面的设置的key获得动画
}#pragma mark 动画结束
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
NSLog(@"animation(%@) stop.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
_layer.position=[[anim valueForKey:@"KCBasicAnimationLocation"] CGPointValue];
}
@end


上面通过给动画设置一个代理去监听动画的开始和结束事件,在动画开始前给动画添加一个自定义属性“KCBasicAnimationLocation”存储动画终点位置,然后在动画结束后设置动画的位置为终点位置。
如果运行上面的代码大家可能会发现另外一个问题,那就是动画运行完成后会重新从起始点运动到终点。这个问题产生的原因就是前面提到的,对于非根图层,设置图层的可动画属性(在动画结束后重新设置了position,而position是可动画属性)会产生动画效果。解决这个问题有两种办法:关闭图层隐式动画、设置动画图层为根图层。显然这里不能采取后者,因为根图层当前已经作为动画的背景。
要关闭隐式动画需要用到动画事务CATransaction,在事务内将隐式动画关闭,例如上面的代码可以改为:
#pragma mark 动画结束
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
NSLog(@"animation(%@) stop.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
//开启事务
[CATransaction begin];
//禁用隐式动画
[CATransaction setDisableActions:YES];_layer.position=[[anim valueForKey:@"KCBasicAnimationLocation"] CGPointValue];//提交事务
[CATransaction commit];
}
补充
上面通过在animationDidStop中重新设置动画的位置主要为了说明隐式动画关闭和动画事件之间传参的内容,有朋友发现这种方式有可能在动画运行完之后出现从原点瞬间回到终点的过程,最早在调试的时候没有发现这个问题,这里感谢这位朋友。其实解决这个问题并不难,首先必须设置fromValue,其次在动画开始前设置动画position为终点位置(当然也必须关闭隐式动画)。但是这里主要还是出于学习的目的,真正开发的时候做平移动画直接使用隐式动画即可,没有必要那么麻烦。当然上面的动画还显得有些生硬,因为落花飘散的时候可能不仅仅是自由落体运动,本身由于空气阻力、外界风力还会造成落花在空中的旋转、摇摆等,这里不妨给图层添加一个旋转的动画。对于图层的旋转前面已经演示过怎么通过key path设置图层旋转的内容了,在这里需要强调一下,图层的形变都是基于锚点进行的。例如旋转,旋转的中心点就是图层的锚点。
//
// KCMainViewController.m
// Animation
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"@interface KCMainViewController (){
CALayer *_layer;
}@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];//设置背景(注意这个图片其实在根图层)
UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];//自定义一个图层
_layer=[[CALayer alloc]init];
_layer.bounds=CGRectMake(0, 0, 10, 20);
_layer.position=CGPointMake(50, 150);
_layer.anchorPoint=CGPointMake(0.5, 0.6);//设置锚点
_layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGImage;
[self.view.layer addSublayer:_layer];
}#pragma mark 移动动画
-(void)translatonAnimation:(CGPoint)location{
//1.创建动画并指定动画属性
CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"position"];//2.设置动画属性初始值、结束值
// basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不设置,默认为图层初始状态
basicAnimation.toValue=[NSValue valueWithCGPoint:location];//设置其他动画属性
basicAnimation.duration=5.0;//动画时间5秒
//basicAnimation.repeatCount=HUGE_VALF;//设置重复次数,HUGE_VALF可看做无穷大,起到循环动画的效果
// basicAnimation.removedOnCompletion=NO;//运行一次是否移除动画
basicAnimation.delegate=self;
//存储当前位置在动画结束后使用
[basicAnimation setValue:[NSValue valueWithCGPoint:location] forKey:@"KCBasicAnimationLocation"];//3.添加动画到图层,注意key相当于给动画进行命名,以后获得该图层时可以使用此名称获取
[_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
}#pragma mark 旋转动画
-(void)rotationAnimation{
//1.创建动画并指定动画属性
CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];//2.设置动画属性初始值、结束值
// basicAnimation.fromValue=[NSNumber numberWithInt:M_PI_2];
basicAnimation.toValue=[NSNumber numberWithFloat:M_PI_2*3];//设置其他动画属性
basicAnimation.duration=6.0;
basicAnimation.autoreverses=true;//旋转后再旋转到原来的位置//4.添加动画到图层,注意key相当于给动画进行命名,以后获得该动画时可以使用此名称获取
[_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Rotation"];
}#pragma mark 点击事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=touches.anyObject;
CGPoint location= [touch locationInView:self.view];
//创建并开始动画
[self translatonAnimation:location];[self rotationAnimation];
}#pragma mark - 动画代理方法
#pragma mark 动画开始
-(void)animationDidStart:(CAAnimation *)anim{
NSLog(@"animation(%@) start.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
NSLog(@"%@",[_layer animationForKey:@"KCBasicAnimation_Translation"]);//通过前面的设置的key获得动画
}#pragma mark 动画结束
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
NSLog(@"animation(%@) stop.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
//开启事务
[CATransaction begin];
//禁用隐式动画
[CATransaction setDisableActions:YES];_layer.position=[[anim valueForKey:@"KCBasicAnimationLocation"] CGPointValue];//提交事务
[CATransaction commit];
}@end


上面代码中结合两种动画操作,需要注意的是只给移动动画设置了代理,在旋转动画中并没有设置代理,否则代理方法会执行两遍。由于旋转动画会无限循环执行(上面设置了重复次数无穷大),并且两个动画的执行时间没有必然的关系,这样一来移动停止后可能还在旋转,为了让移动动画停止后旋转动画停止就需要使用到动画的暂停和恢复方法。
核心动画的运行有一个媒体时间的概念,假设将一个旋转动画设置旋转一周用时60秒的话,那么当动画旋转90度后媒体时间就是15秒。如果此时要将动画暂停只需要让媒体时间偏移量设置为15秒即可,并把动画运行速度设置为0使其停止运动。类似的,如果又过了60秒后需要恢复动画(此时媒体时间为75秒),这时只要将动画开始开始时间设置为当前媒体时间75秒减去暂停时的时间(也就是之前定格动画时的偏移量)15秒(开始时间=75-15=60秒),那么动画就会重新计算60秒后的状态再开始运行,与此同时将偏移量重新设置为0并且把运行速度设置1。这个过程中真正起到暂停动画和恢复动画的其实是动画速度的调整,媒体时间偏移量以及恢复时的开始时间设置主要为了让动画更加连贯。
下面的代码演示了移动动画结束后旋转动画暂停,并且当再次点击动画时旋转恢复的过程(注意在移动过程中如果再次点击屏幕可以暂停移动和旋转动画,再次点击可以恢复两种动画。但是当移动结束后触发了移动动画的完成事件如果再次点击屏幕则只能恢复旋转动画,因为此时移动动画已经结束而不是暂停,无法再恢复)。
//
// KCMainViewController.m
// Animation
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"@interface KCMainViewController (){
CALayer *_layer;
}@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];//设置背景(注意这个图片其实在根图层)
UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];//自定义一个图层
_layer=[[CALayer alloc]init];
_layer.bounds=CGRectMake(0, 0, 10, 20);
_layer.position=CGPointMake(50, 150);
_layer.anchorPoint=CGPointMake(0.5, 0.6);//设置锚点
_layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGImage;
[self.view.layer addSublayer:_layer];
}#pragma mark 移动动画
-(void)translatonAnimation:(CGPoint)location{
//1.创建动画并指定动画属性
CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"position"];//2.设置动画属性初始值、结束值
// basicAnimation.fromValue=[NSNumber numberWithInteger:50];//可以不设置,默认为图层初始状态
basicAnimation.toValue=[NSValue valueWithCGPoint:location];//设置其他动画属性
basicAnimation.duration=5.0;//动画时间5秒
// basicAnimation.repeatCount=HUGE_VALF;//设置重复次数,HUGE_VALF可看做无穷大,起到循环动画的效果
basicAnimation.removedOnCompletion=NO;//运行一次是否移除动画
basicAnimation.delegate=self;
//存储当前位置在动画结束后使用
[basicAnimation setValue:[NSValue valueWithCGPoint:location] forKey:@"KCBasicAnimationLocation"];//3.添加动画到图层,注意key相当于给动画进行命名,以后获得该图层时可以使用此名称获取
[_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Translation"];
}#pragma mark 旋转动画
-(void)rotationAnimation{
//1.创建动画并指定动画属性
CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];//2.设置动画属性初始值、结束值
// basicAnimation.fromValue=[NSNumber numberWithInt:M_PI_2];
basicAnimation.toValue=[NSNumber numberWithFloat:M_PI_2*3];//设置其他动画属性
basicAnimation.duration=6.0;
basicAnimation.autoreverses=true;//旋转后在旋转到原来的位置
basicAnimation.repeatCount=HUGE_VALF;//设置无限循环
basicAnimation.removedOnCompletion=NO;
// basicAnimation.delegate=self;//4.添加动画到图层,注意key相当于给动画进行命名,以后获得该动画时可以使用此名称获取
[_layer addAnimation:basicAnimation forKey:@"KCBasicAnimation_Rotation"];
}#pragma mark 点击事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=touches.anyObject;
CGPoint location= [touch locationInView:self.view];
//判断是否已经常见过动画,如果已经创建则不再创建动画
CAAnimation *animation= [_layer animationForKey:@"KCBasicAnimation_Translation"];
if(animation){
if (_layer.speed==0) {
[self animationResume];
}else{
[self animationPause];
}
}else{
//创建并开始动画
[self translatonAnimation:location];[self rotationAnimation];
}
}#pragma mark 动画暂停
-(void)animationPause{
//取得指定图层动画的媒体时间,后面参数用于指定子图层,这里不需要
CFTimeInterval interval=[_layer convertTime:CACurrentMediaTime() fromLayer:nil];
//设置时间偏移量,保证暂停时停留在旋转的位置
[_layer setTimeOffset:interval];
//速度设置为0,暂停动画
_layer.speed=0;
}#pragma mark 动画恢复
-(void)animationResume{
//获得暂停的时间
CFTimeInterval beginTime= CACurrentMediaTime()- _layer.timeOffset;
//设置偏移量
_layer.timeOffset=0;
//设置开始时间
_layer.beginTime=beginTime;
//设置动画速度,开始运动
_layer.speed=1.0;
}#pragma mark - 动画代理方法
#pragma mark 动画开始
-(void)animationDidStart:(CAAnimation *)anim{
NSLog(@"animation(%@) start.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));
NSLog(@"%@",[_layer animationForKey:@"KCBasicAnimation_Translation"]);//通过前面的设置的key获得动画
}#pragma mark 动画结束
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
NSLog(@"animation(%@) stop.\r_layer.frame=%@",anim,NSStringFromCGRect(_layer.frame));//开启事务
[CATransaction begin];
//禁用隐式动画
[CATransaction setDisableActions:YES];_layer.position=[[anim valueForKey:@"KCBasicAnimationLocation"] CGPointValue];//提交事务
[CATransaction commit];//暂停动画
[self animationPause];}@end

运行效果:



注意:动画暂停针对的是图层而不是图层中的某个动画。
要做无限循环的动画,动画的removedOnCompletion属性必须设置为NO,否则运行一次动画就会销毁。


关键帧动画

熟悉flash开发的朋友对于关键帧动画应该不陌生,这种动画方式在flash开发中经常用到。关键帧动画就是在动画控制过程中开发者指定主要的动画状态,至于各个状态间动画如何进行则由系统自动运算补充(每两个关键帧之间系统形成的动画称为“补间动画”),这种动画的好处就是开发者不用逐个控制每个动画帧,而只要关心几个关键帧的状态即可。
关键帧动画开发分为两种形式:一种是通过设置不同的属性值进行关键帧控制,另一种是通过绘制路径进行关键帧控制。后者优先级高于前者,如果设置了路径则属性值就不再起作用。
对于前面的落花动画效果而言其实落花的过程并不自然,很显然实际生活中它不可能沿着直线下落,这里我们不妨通过关键帧动画的values属性控制它在下落过程中的属性。假设下落过程如图:



在这里需要设置四个关键帧(如图中四个关键点),具体代码如下(动画创建过程同基本动画基本完全一致):
//
// 通过values设置关键帧动画
// Animation
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"@interface KCMainViewController (){
CALayer *_layer;
}@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];//设置背景(注意这个图片其实在根图层)
UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];//自定义一个图层
_layer=[[CALayer alloc]init];
_layer.bounds=CGRectMake(0, 0, 10, 20);
_layer.position=CGPointMake(50, 150);
_layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGImage;
[self.view.layer addSublayer:_layer];//创建动画
[self translationAnimation];
}#pragma mark 关键帧动画
-(void)translationAnimation{
//1.创建关键帧动画并设置动画属性
CAKeyframeAnimation *keyframeAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];//2.设置关键帧,这里有四个关键帧
NSValue *key1=[NSValue valueWithCGPoint:_layer.position];//对于关键帧动画初始值不能省略
NSValue *key2=[NSValue valueWithCGPoint:CGPointMake(80, 220)];
NSValue *key3=[NSValue valueWithCGPoint:CGPointMake(45, 300)];
NSValue *key4=[NSValue valueWithCGPoint:CGPointMake(55, 400)];
NSArray *values=@[key1,key2,key3,key4];
keyframeAnimation.values=values;
//设置其他属性
keyframeAnimation.duration=8.0;
keyframeAnimation.beginTime=CACurrentMediaTime()+2;//设置延迟2秒执行//3.添加动画到图层,添加动画后就会执行动画
[_layer addAnimation:keyframeAnimation forKey:@"KCKeyframeAnimation_Position"];
}@end

运行效果(注意运行结束没有设置图层位置为动画运动结束位置):



上面的方式固然比前面使用基础动画效果要好一些,但其实还是存在问题,那就是落花飞落的路径是直线的,当然这个直线是根据程序中设置的四个关键帧自动形成的,那么如何让它沿着曲线飘落呢?这就是第二种类型的关键帧动画,通过描绘路径进行关键帧动画控制。假设让落花沿着下面的曲线路径飘落:



当然,这是一条贝塞尔曲线,学习了前篇文章之后相信对于这类曲线应该并不陌生,下面是具体实现代码:
//
// 通过path设置关键帧动画
// Animation
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"@interface KCMainViewController (){
CALayer *_layer;
}@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];//设置背景(注意这个图片其实在根图层)
UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];//自定义一个图层
_layer=[[CALayer alloc]init];
_layer.bounds=CGRectMake(0, 0, 10, 20);
_layer.position=CGPointMake(50, 150);
_layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGImage;
[self.view.layer addSublayer:_layer];//创建动画
[self translationAnimation];
}#pragma mark 关键帧动画
-(void)translationAnimation{
//1.创建关键帧动画并设置动画属性
CAKeyframeAnimation *keyframeAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];//2.设置路径
//绘制贝塞尔曲线
CGPathRef path=CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, _layer.position.x, _layer.position.y);//移动到起始点
CGPathAddCurveToPoint(path, NULL, 160, 280, -30, 300, 55, 400);//绘制二次贝塞尔曲线keyframeAnimation.path=path;//设置path属性
CGPathRelease(path);//释放路径对象
//设置其他属性
keyframeAnimation.duration=8.0;
keyframeAnimation.beginTime=CACurrentMediaTime()+5;//设置延迟2秒执行//3.添加动画到图层,添加动画后就会执行动画
[_layer addAnimation:keyframeAnimation forKey:@"KCKeyframeAnimation_Position"];
}@end

运行效果(注意运行结束没有设置图层位置为动画运动结束位置):



看起来动画不会那么生硬了,但是这里需要注意,对于路径类型的关键帧动画系统是从描绘路径的位置开始路径,直到路径结束。如果上面的路径不是贝塞尔曲线而是矩形路径那么它会从矩形的左上角开始运行,顺时针一周回到左上角;如果指定的路径是一个椭圆,那么动画运行的路径是从椭圆右侧开始(0度)顺时针一周回到右侧。

补充--其他属性

在关键帧动画中还有一些动画属性初学者往往比较容易混淆,这里专门针对这些属性做一下介绍。
keyTimes:各个关键帧的时间控制。前面使用values设置了四个关键帧,默认情况下每两帧之间的间隔为:8/(4-1)秒。如果想要控制动画从第一帧到第二针占用时间4秒,从第二帧到第三帧时间为2秒,而从第三帧到第四帧时间2秒的话,就可以通过keyTimes进行设置。keyTimes中存储的是时间占用比例点,此时可以设置keyTimes的值为0.0,0.5,0.75,1.0(当然必须转换为NSNumber),也就是说1到2帧运行到总时间的50%,2到3帧运行到总时间的75%,3到4帧运行到8秒结束。
caculationMode:动画计算模式。还拿上面keyValues动画举例,之所以1到2帧能形成连贯性动画而不是直接从第1帧经过8/3秒到第2帧是因为动画模式是连续的(值为kCAAnimationLinear,这是计算模式的默认值);而如果指定了动画模式为kCAAnimationDiscrete离散的那么你会看到动画从第1帧经过8/3秒直接到第2帧,中间没有任何过渡。其他动画模式还有:kCAAnimationPaced(均匀执行,会忽略keyTimes)、kCAAnimationCubic(平滑执行,对于位置变动关键帧动画运行轨迹更平滑)、kCAAnimationCubicPaced(平滑均匀执行)。
下图描绘出了几种动画模式的关系(横坐标是运行时间,纵坐标是动画属性[例如位置、透明度等]):



动画组

实际开发中一个物体的运动往往是复合运动,单一属性的运动情况比较少,但恰恰属性动画每次进行动画设置时一次只能设置一个属性进行动画控制(不管是基础动画还是关键帧动画都是如此),这样一来要做一个复合运动的动画就必须创建多个属性动画进行组合。对于一两种动画的组合或许处理起来还比较容易,但是对于更多动画的组合控制往往会变得很麻烦,动画组的产生就是基于这样一种情况而产生的。动画组是一系列动画的组合,凡是添加到动画组中的动画都受控于动画组,这样一来各类动画公共的行为就可以统一进行控制而不必单独设置,而且放到动画组中的各个动画可以并发执行,共同构建出复杂的动画效果。
动画组使用起来并不复杂,首先单独创建单个动画(可以是基础动画也可以是关键帧动画),然后将基础动画添加到动画组,最后将动画组添加到图层即可。
前面关键帧动画部分,路径动画看起来效果虽然很流畅,但是落花本身的旋转运动没有了,这里不妨将基础动画部分的旋转动画和路径关键帧动画进行组合使得整个动画看起来更加的和谐、顺畅。
//
// 动画组
// Animation
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"@interface KCMainViewController (){
CALayer *_layer;
}@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];//设置背景(注意这个图片其实在根图层)
UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];//自定义一个图层
_layer=[[CALayer alloc]init];
_layer.bounds=CGRectMake(0, 0, 10, 20);
_layer.position=CGPointMake(50, 150);
_layer.contents=(id)[UIImage imageNamed:@"petal.png"].CGImage;
[self.view.layer addSublayer:_layer];//创建动画
[self groupAnimation];
}#pragma mark 基础旋转动画
-(CABasicAnimation *)rotationAnimation{CABasicAnimation *basicAnimation=[CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];CGFloat toValue=M_PI_2*3;
basicAnimation.toValue=[NSNumber numberWithFloat:M_PI_2*3];// basicAnimation.duration=6.0;
basicAnimation.autoreverses=true;
basicAnimation.repeatCount=HUGE_VALF;
basicAnimation.removedOnCompletion=NO;[basicAnimation setValue:[NSNumber numberWithFloat:toValue] forKey:@"KCBasicAnimationProperty_ToValue"];return basicAnimation;
}#pragma mark 关键帧移动动画
-(CAKeyframeAnimation *)translationAnimation{
CAKeyframeAnimation *keyframeAnimation=[CAKeyframeAnimation animationWithKeyPath:@"position"];CGPoint endPoint= CGPointMake(55, 400);
CGPathRef path=CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, _layer.position.x, _layer.position.y);
CGPathAddCurveToPoint(path, NULL, 160, 280, -30, 300, endPoint.x, endPoint.y);keyframeAnimation.path=path;
CGPathRelease(path);[keyframeAnimation setValue:[NSValue valueWithCGPoint:endPoint] forKey:@"KCKeyframeAnimationProperty_EndPosition"];return keyframeAnimation;
}#pragma mark 创建动画组
-(void)groupAnimation{
//1.创建动画组
CAAnimationGroup *animationGroup=[CAAnimationGroup animation];//2.设置组中的动画和其他属性
CABasicAnimation *basicAnimation=[self rotationAnimation];
CAKeyframeAnimation *keyframeAnimation=[self translationAnimation];
animationGroup.animations=@[basicAnimation,keyframeAnimation];animationGroup.delegate=self;
animationGroup.duration=10.0;//设置动画时间,如果动画组中动画已经设置过动画属性则不再生效
animationGroup.beginTime=CACurrentMediaTime()+5;//延迟五秒执行//3.给图层添加动画
[_layer addAnimation:animationGroup forKey:nil];
}#pragma mark - 代理方法
#pragma mark 动画完成
-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
CAAnimationGroup *animationGroup=(CAAnimationGroup *)anim;
CABasicAnimation *basicAnimation=animationGroup.animations[0];
CAKeyframeAnimation *keyframeAnimation=animationGroup.animations[1];
CGFloat toValue=[[basicAnimation valueForKey:@"KCBasicAnimationProperty_ToValue"] floatValue];
CGPoint endPoint=[[keyframeAnimation valueForKey:@"KCKeyframeAnimationProperty_EndPosition"] CGPointValue];[CATransaction begin];
[CATransaction setDisableActions:YES];//设置动画最终状态
_layer.position=endPoint;
_layer.transform=CATransform3DMakeRotation(toValue, 0, 0, 1);[CATransaction commit];
}@end

运行效果:




转场动画

转场动画就是从一个场景以动画的形式过渡到另一个场景。转场动画的使用一般分为以下几个步骤:
1.创建转场动画
2.设置转场类型、子类型(可选)及其他属性
3.设置转场后的新视图并添加动画到图层
下表列出了常用的转场类型(注意私有API是苹果官方没有公开的动画类型,但是目前通过仍然可以使用):
动画类型说明对应常量是否支持方向设置
公开API
fade淡出效果kCATransitionFade
movein新视图移动到旧视图上kCATransitionMoveIn
push新视图推出旧视图kCATransitionPush
reveal移开旧视图显示新视图kCATransitionReveal
私有API私有API只能通过字符串访问
cube立方体翻转效果
oglFlip翻转效果
suckEffect收缩效果
rippleEffect水滴波纹效果
pageCurl向上翻页效果
pageUnCurl向下翻页效果
cameralIrisHollowOpen摄像头打开效果
cameraIrisHollowClose摄像头关闭效果
另外对于支持方向设置的动画类型还包含子类型:
动画子类型说明
kCATransitionFromRight从右侧转场
kCATransitionFromLeft从左侧转场
kCATransitionFromTop从顶部转场
kCATransitionFromBottom从底部转场
在前面的文章“IOS开发系列--无限循环的图片浏览器”中为了使用UIScrollView做无限循环图片浏览器花费了不少时间在性能优化上面,这里使用转场动画利用一个UIImageView实现一个漂亮的无限循环图片浏览器。
//
// KCMainViewController.m
// TransitionAnimation
//
// Created by Kenshin Cui on 14-3-12.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"
#define IMAGE_COUNT 5@interface KCMainViewController (){
UIImageView *_imageView;
int _currentIndex;
}@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];//定义图片控件
_imageView=[[UIImageView alloc]init];
_imageView.frame=[UIScreen mainScreen].applicationFrame;
_imageView.contentMode=UIViewContentModeScaleAspectFit;
_imageView.image=[UIImage imageNamed:@"0.jpg"];//默认图片
[self.view addSubview:_imageView];
//添加手势
UISwipeGestureRecognizer *leftSwipeGesture=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(leftSwipe:)];
leftSwipeGesture.direction=UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:leftSwipeGesture];UISwipeGestureRecognizer *rightSwipeGesture=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(rightSwipe:)];
rightSwipeGesture.direction=UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:rightSwipeGesture];
}#pragma mark 向左滑动浏览下一张图片
-(void)leftSwipe:(UISwipeGestureRecognizer *)gesture{
[self transitionAnimation:YES];
}#pragma mark 向右滑动浏览上一张图片
-(void)rightSwipe:(UISwipeGestureRecognizer *)gesture{
[self transitionAnimation:NO];
}#pragma mark 转场动画
-(void)transitionAnimation:(BOOL)isNext{
//1.创建转场动画对象
CATransition *transition=[[CATransition alloc]init];//2.设置动画类型,注意对于苹果官方没公开的动画类型只能使用字符串,并没有对应的常量定义
transition.type=@"cube";//设置子类型
if (isNext) {
transition.subtype=kCATransitionFromRight;
}else{
transition.subtype=kCATransitionFromLeft;
}
//设置动画时常
transition.duration=1.0f;//3.设置转场后的新视图添加转场动画
_imageView.image=[self getImage:isNext];
[_imageView.layer addAnimation:transition forKey:@"KCTransitionAnimation"];
}#pragma mark 取得当前图片
-(UIImage *)getImage:(BOOL)isNext{
if (isNext) {
_currentIndex=(_currentIndex+1)%IMAGE_COUNT;
}else{
_currentIndex=(_currentIndex-1+IMAGE_COUNT)%IMAGE_COUNT;
}
NSString *imageName=[NSString stringWithFormat:@"%i.jpg",_currentIndex];
return [UIImage imageNamed:imageName];
}
@end


运行效果:



代码十分简单,但是效果和性能却很惊人。当然演示代码有限,其他动画类型大家可以自己实现,效果都很绚丽。


逐帧动画

前面介绍了核心动画中大部分动画类型,但是做过动画处理的朋友都知道,在动画制作中还有一种动画类型“逐帧动画”。说到逐帧动画相信很多朋友第一个想到的就是UIImageView,通过设置UIImageView的animationImages属性,然后调用它的startAnimating方法去播放这组图片。当然这种方法在某些场景下是可以达到逐帧的动画效果,但是它也存在着很大的性能问题,并且这种方法一旦设置完图片中间的过程就无法控制了。当然,也许有朋友会想到利用iOS的定时器NSTimer定时更新图片来达到逐帧动画的效果。这种方式确实可以解决UIImageView一次性加载大量图片的问题,而且让播放过程可控,唯一的缺点就是定时器方法调用有时可能会因为当前系统执行某种比较占用时间的任务造成动画连续性出现问题。
虽然在核心动画没有直接提供逐帧动画类型,但是却提供了用于完成逐帧动画的相关对象CADisplayLink。CADisplayLink是一个计时器,但是同NSTimer不同的是,CADisplayLink的刷新周期同屏幕完全一致。例如在iOS中屏幕刷新周期是60次/秒,CADisplayLink刷新周期同屏幕刷新一致也是60次/秒,这样一来使用它完成的逐帧动画(又称为“时钟动画”)完全感觉不到动画的停滞情况。
在iOS开篇“IOS开发系列--IOS程序开发概览”中就曾说过:iOS程序在运行后就进入一个消息循环中(这个消息循环称为“主运行循环”),整个程序相当于进入一个死循环中,始终等待用户输入。将CADisplayLink加入到主运行循环队列后,它的时钟周期就和主运行循环保持一致,而主运行循环周期就是屏幕刷新周期。在CADisplayLink加入到主运行循环队列后就会循环调用目标方法,在这个方法中更新视图内容就可以完成逐帧动画。
当然这里不得不强调的是逐帧动画性能势必较低,但是对于一些事物的运动又不得不选择使用逐帧动画,例如人的运动,这是一个高度复杂的运动,基本动画、关键帧动画是不可能解决的。所大家一定要注意在循环方法中尽可能的降低算法复杂度,同时保证循环过程中内存峰值尽可能低。下面以一个鱼的运动为例为大家演示一下逐帧动画。
//
// KCMainViewController.m
// DisplayLink
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"
#define IMAGE_COUNT 10@interface KCMainViewController (){
CALayer *_layer;
int _index;
NSMutableArray *_images;
}@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];//设置背景
self.view.layer.contents=(id)[UIImage imageNamed:@"bg.png"].CGImage;//创建图像显示图层
_layer=[[CALayer alloc]init];
_layer.bounds=CGRectMake(0, 0, 87, 32);
_layer.position=CGPointMake(160, 284);
[self.view.layer addSublayer:_layer];//由于鱼的图片在循环中会不断创建,而10张鱼的照片相对都很小
//与其在循环中不断创建UIImage不如直接将10张图片缓存起来
_images=[NSMutableArray array];
for (int i=0; i<10; ++i) {
NSString *imageName=[NSString stringWithFormat:@"fish%i.png",i];
UIImage *image=[UIImage imageNamed:imageName];
[_images addObject:image];
}//定义时钟对象
CADisplayLink *displayLink=[CADisplayLink displayLinkWithTarget:self selector:@selector(step)];
//添加时钟对象到主运行循环
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}#pragma mark 每次屏幕刷新就会执行一次此方法(每秒接近60次)
-(void)step{
//定义一个变量记录执行次数
static int s=0;
//每秒执行6次
if (++s%10==0) {
UIImage *image=_images[_index];
_layer.contents=(id)image.CGImage;//更新图片
_index=(_index+1)%IMAGE_COUNT;
}
}
@end

运行效果:



注意:上面仅仅演示了逐帧动画的过程,事实上结合其他动画类型可以让整条鱼游动起来,这里不再赘述。


UIView动画封装

有了前面核心动画的知识,相信大家开发出一般的动画效果应该不在话下。在核心动画开篇也给大家说过,其实UIView本身对于基本动画和关键帧动画、转场动画都有相应的封装,在对动画细节没有特殊要求的情况下使用起来也要简单的多。可以说在日常开发中90%以上的情况使用UIView的动画封装方法都可以搞定,因此在熟悉了核心动画的原理之后还是有必要给大家简单介绍一下UIView中各类动画使用方法的。由于前面核心动画内容已经进行过详细介绍,学习UIView的封装方法根本是小菜一碟,这里对于一些细节就不再赘述了。

基础动画

基础动画部分和前面的基础动画演示相对应,演示点击屏幕落叶飘落到鼠标点击位置的过程。注意根据前面介绍的隐式动画知识其实非根图层直接设置终点位置不需要使用UIView的动画方法也可以实现动画效果,因此这里落花不再放到图层中而是放到了一个UIImageView中。
下面的代码演示了通过block和静态方法实现动画控制的过程:
//
// UIView实现基础动画
// Animation
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"@interface KCMainViewController (){
UIImageView *_imageView;
}@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];//设置背景
UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];//创建图像显示控件
_imageView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"petal.png"]];
_imageView.center=CGPointMake(50, 150);
[self.view addSubview:_imageView];
}#pragma mark 点击事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=touches.anyObject;
CGPoint location= [touch locationInView:self.view];
//方法1:block方式
/*开始动画,UIView的动画方法执行完后动画会停留在重点位置,而不需要进行任何特殊处理
duration:执行时间
delay:延迟时间
options:动画设置,例如自动恢复、匀速运动等
completion:动画完成回调方法
*/
// [UIView animateWithDuration:5.0 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
// _imageView.center=location;
// } completion:^(BOOL finished) {
// NSLog(@"Animation end.");
// }];//方法2:静态方法
//开始动画
[UIView beginAnimations:@"KCBasicAnimation" context:nil];
[UIView setAnimationDuration:5.0];
//[UIView setAnimationDelay:1.0];//设置延迟
//[UIView setAnimationRepeatAutoreverses:NO];//是否回复
//[UIView setAnimationRepeatCount:10];//重复次数
//[UIView setAnimationStartDate:(NSDate *)];//设置动画开始运行的时间
//[UIView setAnimationDelegate:self];//设置代理
//[UIView setAnimationWillStartSelector:(SEL)];//设置动画开始运动的执行方法
//[UIView setAnimationDidStopSelector:(SEL)];//设置动画运行结束后的执行方法_imageView.center=location;//开始动画
[UIView commitAnimations];
}
@end


补充--弹簧动画效果
由于在iOS开发中弹性动画使用很普遍,所以在iOS7苹果官方直接提供了一个方法用于弹性动画开发,下面简单的演示一下:
//
// UIView实现基础动画--弹性动画
// Animation
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"@interface KCMainViewController (){
UIImageView *_imageView;
}@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];//创建图像显示控件
_imageView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"ball.png"]];
_imageView.center=CGPointMake(160, 50);
[self.view addSubview:_imageView];
}#pragma mark 点击事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch=touches.anyObject;
CGPoint location= [touch locationInView:self.view];
/*创建弹性动画
damping:阻尼,范围0-1,阻尼越接近于0,弹性效果越明显
velocity:弹性复位的速度
*/
[UIView animateWithDuration:5.0 delay:0 usingSpringWithDamping:0.1 initialSpringVelocity:1.0 options:UIViewAnimationOptionCurveLinear animations:^{
_imageView.center=location; //CGPointMake(160, 284);
} completion:nil];
}
@end

运行效果:



补充--动画设置参数
在动画方法中有一个option参数,UIViewAnimationOptions类型,它是一个枚举类型,动画参数分为三类,可以组合使用:
1.常规动画属性设置(可以同时选择多个进行设置)
UIViewAnimationOptionLayoutSubviews:动画过程中保证子视图跟随运动。
UIViewAnimationOptionAllowUserInteraction:动画过程中允许用户交互。
UIViewAnimationOptionBeginFromCurrentState:所有视图从当前状态开始运行。
UIViewAnimationOptionRepeat:重复运行动画。
UIViewAnimationOptionAutoreverse :动画运行到结束点后仍然以动画方式回到初始点。
UIViewAnimationOptionOverrideInheritedDuration:忽略嵌套动画时间设置。
UIViewAnimationOptionOverrideInheritedCurve:忽略嵌套动画速度设置。
UIViewAnimationOptionAllowAnimatedContent:动画过程中重绘视图(注意仅仅适用于转场动画)。
UIViewAnimationOptionShowHideTransitionViews:视图切换时直接隐藏旧视图、显示新视图,而不是将旧视图从父视图移除(仅仅适用于转场动画)UIViewAnimationOptionOverrideInheritedOptions :不继承父动画设置或动画类型。
2.动画速度控制(可从其中选择一个设置)
UIViewAnimationOptionCurveEaseInOut:动画先缓慢,然后逐渐加速。
UIViewAnimationOptionCurveEaseIn :动画逐渐变慢。
UIViewAnimationOptionCurveEaseOut:动画逐渐加速。
UIViewAnimationOptionCurveLinear :动画匀速执行,默认值。
3.转场类型(仅适用于转场动画设置,可以从中选择一个进行设置,基本动画、关键帧动画不需要设置)
UIViewAnimationOptionTransitionNone:没有转场动画效果。
UIViewAnimationOptionTransitionFlipFromLeft :从左侧翻转效果。
UIViewAnimationOptionTransitionFlipFromRight:从右侧翻转效果。
UIViewAnimationOptionTransitionCurlUp:向后翻页的动画过渡效果。
UIViewAnimationOptionTransitionCurlDown :向前翻页的动画过渡效果。
UIViewAnimationOptionTransitionCrossDissolve:旧视图溶解消失显示下一个新视图的效果。
UIViewAnimationOptionTransitionFlipFromTop :从上方翻转效果。
UIViewAnimationOptionTransitionFlipFromBottom:从底部翻转效果。

关键帧动画

从iOS7开始UIView动画中封装了关键帧动画,下面就来看一下如何使用UIView封装方法进行关键帧动画控制,这里实现前面关键帧动画部分对于落花的控制。
//
// UIView关键帧动画
// UIViewAnimation
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"@interface KCMainViewController (){
UIImageView *_imageView;
}@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];//设置背景
UIImage *backgroundImage=[UIImage imageNamed:@"background.jpg"];
self.view.backgroundColor=[UIColor colorWithPatternImage:backgroundImage];//创建图像显示控件
_imageView=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"petal.png"]];
_imageView.center=CGPointMake(50, 150);
[self.view addSubview:_imageView];
}-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//UITouch *touch=touches.anyObject;
//CGPoint location= [touch locationInView:self.view];/*关键帧动画
options:
*/
[UIView animateKeyframesWithDuration:5.0 delay:0 options: UIViewAnimationOptionCurveLinear| UIViewAnimationOptionCurveLinear animations:^{
//第二个关键帧(准确的说第一个关键帧是开始位置):从0秒开始持续50%的时间,也就是5.0*0.5=2.5秒
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{
_imageView.center=CGPointMake(80.0, 220.0);
}];
//第三个关键帧,从0.5*5.0秒开始,持续5.0*0.25=1.25秒
[UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.25 animations:^{
_imageView.center=CGPointMake(45.0, 300.0);
}];
//第四个关键帧:从0.75*5.0秒开始,持所需5.0*0.25=1.25秒
[UIView addKeyframeWithRelativeStartTime:0.75 relativeDuration:0.25 animations:^{
_imageView.center=CGPointMake(55.0, 400.0);
}];} completion:^(BOOL finished) {
NSLog(@"Animation end.");
}];
}
@end

补充--动画设置参数
对于关键帧动画也有一些动画参数设置options,UIViewKeyframeAnimationOptions类型,和上面基本动画参数设置有些差别,关键帧动画设置参数分为两类,可以组合使用:
1.常规动画属性设置(可以同时选择多个进行设置)
UIViewAnimationOptionLayoutSubviews:动画过程中保证子视图跟随运动。
UIViewAnimationOptionAllowUserInteraction:动画过程中允许用户交互。
UIViewAnimationOptionBeginFromCurrentState:所有视图从当前状态开始运行。
UIViewAnimationOptionRepeat:重复运行动画。
UIViewAnimationOptionAutoreverse :动画运行到结束点后仍然以动画方式回到初始点。
UIViewAnimationOptionOverrideInheritedDuration:忽略嵌套动画时间设置。UIViewAnimationOptionOverrideInheritedOptions :不继承父动画设置或动画类型。
2.动画模式设置(同前面关键帧动画动画模式一一对应,可以从其中选择一个进行设置)
UIViewKeyframeAnimationOptionCalculationModeLinear:连续运算模式。
UIViewKeyframeAnimationOptionCalculationModeDiscrete :离散运算模式。
UIViewKeyframeAnimationOptionCalculationModePaced:均匀执行运算模式。
UIViewKeyframeAnimationOptionCalculationModeCubic:平滑运算模式。
UIViewKeyframeAnimationOptionCalculationModeCubicPaced:平滑均匀运算模式。
注意:前面说过关键帧动画有两种形式,上面演示的是属性值关键帧动画,路径关键帧动画目前UIView还不支持。

转场动画

从iOS4.0开始,UIView直接封装了转场动画,使用起来同样很简单。
//
// UIView转场动画
// TransitionAnimation
//
// Created by Kenshin Cui on 14-3-12.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//#import "KCMainViewController.h"
#define IMAGE_COUNT 5@interface KCMainViewController (){
UIImageView *_imageView;
int _currentIndex;
}@end@implementation KCMainViewController- (void)viewDidLoad {
[super viewDidLoad];//定义图片控件
_imageView=[[UIImageView alloc]init];
_imageView.frame=[UIScreen mainScreen].applicationFrame;
_imageView.contentMode=UIViewContentModeScaleAspectFit;
_imageView.image=[UIImage imageNamed:@"0.jpg"];//默认图片
[self.view addSubview:_imageView];
//添加手势
UISwipeGestureRecognizer *leftSwipeGesture=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(leftSwipe:)];
leftSwipeGesture.direction=UISwipeGestureRecognizerDirectionLeft;
[self.view addGestureRecognizer:leftSwipeGesture];UISwipeGestureRecognizer *rightSwipeGesture=[[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(rightSwipe:)];
rightSwipeGesture.direction=UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:rightSwipeGesture];
}#pragma mark 向左滑动浏览下一张图片
-(void)leftSwipe:(UISwipeGestureRecognizer *)gesture{
[self transitionAnimation:YES];
}#pragma mark 向右滑动浏览上一张图片
-(void)rightSwipe:(UISwipeGestureRecognizer *)gesture{
[self transitionAnimation:NO];
}#pragma mark 转场动画
-(void)transitionAnimation:(BOOL)isNext{
UIViewAnimationOptions option;
if (isNext) {
option=UIViewAnimationOptionCurveLinear|UIViewAnimationOptionTransitionFlipFromRight;
}else{
option=UIViewAnimationOptionCurveLinear|UIViewAnimationOptionTransitionFlipFromLeft;
}[UIView transitionWithView:_imageView duration:1.0 options:option animations:^{
_imageView.image=[self getImage:isNext];
} completion:nil];
}#pragma mark 取得当前图片
-(UIImage *)getImage:(BOOL)isNext{
if (isNext) {
_currentIndex=(_currentIndex+1)%IMAGE_COUNT;
}else{
_currentIndex=(_currentIndex-1+IMAGE_COUNT)%IMAGE_COUNT;
}
NSString *imageName=[NSString stringWithFormat:@"%i.jpg",_currentIndex];
return [UIImage imageNamed:imageName];
}
@end

上面的转场动画演示中,其实仅仅有一个视图UIImageView做转场动画,每次转场通过切换UIImageView的内容而已。如果有两个完全不同的视图,并且每个视图布局都很复杂,此时要在这两个视图之间进行转场可以使用+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration
options:(UIViewAnimationOptions)options completion:(void (^)(BOOL finished))completion NS_AVAILABLE_IOS(4_0)
方法进行两个视图间的转场,需要注意的是默认情况下转出的视图会从父视图移除,转入后重新添加,可以通过UIViewAnimationOptionShowHideTransitionViews参数设置,设置此参数后转出的视图会隐藏(不会移除)转入后再显示。
注意:转场动画设置参数完全同基本动画参数设置;同直接使用转场动画不同的是使用UIView的装饰方法进行转场动画其动画效果较少,因为这里无法直接使用私有API。yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy单例是一种类,该类只能在第一次用的时候实例化一个对象,后期直接调用此对象(有点共享的意思)。
在Foundation框架中比如NSFileManger和NSNotificationCenter,分别通过它们的类方法defaultManager和defaultCenter获取。尽管不是严格意义的单例,这些类方法返回一个可以在应用的所有代码中访问到的类的共享实例。使用Objective-C实现单例模式的最佳方式向来有很多争论,开发者似乎每几年就会改变他们的想法。他们也引入了一个很适合用于实现单例模式的函数。该函数就是dispatch_once:void dispatch_once( dispatch_once_t *predicate, dispatch_block_t block);该函数接收一个dispatch_once用于检查该代码块是否已经被调度的谓词(是一个长整型,实际上作为BOOL使用)。它还接收一个希望在应用的生命周期内仅被调度一次的代码块,对于本例就用于shared实例的实例化。dispatch_once不仅意味着代码仅会被运行一次,而且还是线程安全的,这就意味着你不需要使用诸如@synchronized之类的来防止使用多个线程或者队列时不同步的问题。如果被多个线程调用,该函数会同步等等直至代码块完成。实际要如何使用这些呢?好吧,假设有一个AccountManager类,你想在整个应用中访问该类的共享实例。你可以按如下代码简单实现一个类方法:+ (AccountManager *)sharedManager {static AccountManager *sharedAccountManagerInstance = nil;static dispatch_once_t predicate;dispatch_once(&predicate, ^{sharedAccountManagerInstance = [[self alloc] init];});return sharedAccountManagerInstance;}这就意味着你任何时候访问共享实例,需要做的仅是:AccountManager *accountManager = [AccountManager sharedManager];就这些,你现在在应用中就有一个共享的实例,该实例只会被创建一次。该方法有很多优势:1 线程安全2 很好满足静态分析器要求3 和自动引用计数(ARC)兼容4 仅需要少量代码该方法的劣势就是它仍然运行创建一个非共享的实例:AccountManager *accountManager = [[AccountManager alloc] init];有些时候你希望有这种行为,但如果正在想要的是仅一个实例被实例化就需要注意这点。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: