iOS-Core-Animation-Advanced-Techniques(六:專用圖層-2)

本文轉(zhuǎn)載自:http://www.cocoachina.com/ios/20150105/10827.html? 為了防止cocochina以后刪除該文章,故轉(zhuǎn)載至此;

CATiledLayer

有些時(shí)候你可能需要繪制一個(gè)很大的圖片,常見的例子就是一個(gè)高像素的照片或者是地球表面的詳細(xì)地圖。iOS應(yīng)用通暢運(yùn)行在內(nèi)存受限的設(shè)備上,所以讀取整個(gè)圖片到內(nèi)存中是不明智的。載入大圖可能會(huì)相當(dāng)?shù)芈?,那些?duì)你看上去比較方便的做法(在主線程調(diào)用UIImage的-imageNamed:方法或者-imageWithContentsOfFile:方法)將會(huì)阻塞你的用戶界面,至少會(huì)引起動(dòng)畫卡頓現(xiàn)象。

能高效繪制在iOS上的圖片也有一個(gè)大小限制。所有顯示在屏幕上的圖片最終都會(huì)被轉(zhuǎn)化為OpenGL紋理,同時(shí)OpenGL有一個(gè)最大的紋理尺寸(通常是2048*2048,或4096*4096,這個(gè)取決于設(shè)備型號(hào))。如果你想在單個(gè)紋理中顯示一個(gè)比這大的圖,即便圖片已經(jīng)存在于內(nèi)存中了,你仍然會(huì)遇到很大的性能問題,因?yàn)镃ore Animation強(qiáng)制用CPU處理圖片而不是更快的GPU(見第12章『速度的曲調(diào)』,和第13章『高效繪圖』,它更加詳細(xì)地解釋了軟件繪制和硬件繪制)。

CATiledLayer為載入大圖造成的性能問題提供了一個(gè)解決方案:將大圖分解成小片然后將他們單獨(dú)按需載入。讓我們用實(shí)驗(yàn)來證明一下。

小片裁剪

這個(gè)示例中,我們將會(huì)從一個(gè)2048*2048分辨率的雪人圖片入手。為了能夠從CATiledLayer中獲益,我們需要把這個(gè)圖片裁切成許多小一些的圖片。你可以通過代碼來完成這件事情,但是如果你在運(yùn)行時(shí)讀入整個(gè)圖片并裁切,那CATiledLayer這些所有的性能優(yōu)點(diǎn)就損失殆盡了。理想情況下來說,最好能夠逐個(gè)步驟來實(shí)現(xiàn)。

清單6.11 演示了一個(gè)簡單的Mac OS命令行程序,它用CATiledLayer將一個(gè)圖片裁剪成小圖并存儲(chǔ)到不同的文件中。

清單6.11 裁剪圖片成小圖的終端程序

#import?int?main(int?argc,?const?char?*?argv[]){

@autoreleasepool{

//handle?incorrect?arguments

if?(argc?<?2)?{

NSLog(@"TileCutter?arguments:?inputfile");

return?0;

}

//input?file

NSString?*inputFile?=?[NSString?stringWithCString:argv[1]?encoding:NSUTF8StringEncoding];

//tile?size

CGFloat?tileSize?=?256;?//output?path

NSString?*outputPath?=?[inputFile?stringByDeletingPathExtension];

//load?image

NSImage?*image?=?[[NSImage?alloc]?initWithContentsOfFile:inputFile];

NSSize?size?=?[image?size];

NSArray?*representations?=?[image?representations];

if?([representations?count]){

NSBitmapImageRep?*representation?=?representations[0];

size.width?=?[representation?pixelsWide];

size.height?=?[representation?pixelsHigh];

}

NSRect?rect?=?NSMakeRect(0.0,?0.0,?size.width,?size.height);

CGImageRef?imageRef?=?[image?CGImageForProposedRect:&rect?context:NULL?hints:nil];

//calculate?rows?and?columns

NSInteger?rows?=?ceil(size.height?/?tileSize);

NSInteger?cols?=?ceil(size.width?/?tileSize);

//generate?tiles

for?(int?y?=?0;?y?<?rows;?++y)?{

for?(int?x?=?0;?x?<?cols;?++x)?{

//extract?tile?image

CGRect?tileRect?=?CGRectMake(x*tileSize,?y*tileSize,?tileSize,?tileSize);

CGImageRef?tileImage?=?CGImageCreateWithImageInRect(imageRef,?tileRect);

//convert?to?jpeg?data

NSBitmapImageRep?*imageRep?=?[[NSBitmapImageRep?alloc]?initWithCGImage:tileImage];

NSData?*data?=?[imageRep?representationUsingType:?NSJPEGFileType?properties:nil];

CGImageRelease(tileImage);

//save?file

NSString?*path?=?[outputPath?stringByAppendingFormat:?@"_i_i.jpg",?x,?y];

[data?writeToFile:path?atomically:NO];

}

}

}

return?0;

}

這個(gè)程序?qū)?048*2048分辨率的雪人圖案裁剪成了64個(gè)不同的256*256的小圖。(256*256是CATiledLayer的默認(rèn)小圖大小,默認(rèn)大小可以通過tileSize屬性更改)。程序接受一個(gè)圖片路徑作為命令行的第一個(gè)參數(shù)。我們可以在編譯的scheme將路徑參數(shù)硬編碼然后就可以在Xcode中運(yùn)行了,但是以后作用在另一個(gè)圖片上就不方便了。所以,我們編譯了這個(gè)程序并把它保存到敏感的地方,然后從終端調(diào)用,如下面所示:

>?path/to/TileCutterApp?path/to/Snowman.jpg

The app is very basic, but could easily be extended to support additional arguments such as tile size, or to export images in formats other than JPEG. The result of running it is a sequence of 64 new images, named as follows:

這個(gè)程序相當(dāng)基礎(chǔ),但是能夠輕易地?cái)U(kuò)展支持額外的參數(shù)比如小圖大小,或者導(dǎo)出格式等等。運(yùn)行結(jié)果是64個(gè)新圖的序列,如下面命名:

Snowman_00_00.jpg

Snowman_00_01.jpg

Snowman_00_02.jpg

...

Snowman_07_07.jpg

既然我們有了裁切后的小圖,我們就要讓iOS程序用到他們。CATiledLayer很好地和UIScrollView集成在一起。除了設(shè)置圖層和滑動(dòng)視圖邊界以適配整個(gè)圖片大小,我們真正要做的就是實(shí)現(xiàn)-drawLayer:inContext:方法,當(dāng)需要載入新的小圖時(shí),CATiledLayer就會(huì)調(diào)用到這個(gè)方法。

清單6.12演示了代碼。圖6.12是代碼運(yùn)行結(jié)果。

清單6.12 一個(gè)簡單的滾動(dòng)CATiledLayer實(shí)現(xiàn)

#import?"ViewController.h"

#import?@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIScrollView?*scrollView;

@end

@implementation?ViewController

-?(void)viewDidLoad{

[super?viewDidLoad];

//add?the?tiled?layer

CATiledLayer?*tileLayer?=?[CATiledLayer?layer];?

tileLayer.frame?=?CGRectMake(0,?0,?2048,?2048);

tileLayer.delegate?=?self;?[self.scrollView.layer?addSublayer:tileLayer];

//configure?the?scroll?view

self.scrollView.contentSize?=?tileLayer.frame.size;

//draw?layer

[tileLayer?setNeedsDisplay];

}

-?(void)drawLayer:(CATiledLayer?*)layer?inContext:(CGContextRef)ctx{

//determine?tile?coordinate

CGRect?bounds?=?CGContextGetClipBoundingBox(ctx);

NSInteger?x?=?floor(bounds.origin.x?/?layer.tileSize.width);

NSInteger?y?=?floor(bounds.origin.y?/?layer.tileSize.height);

//load?tile?image

NSString?*imageName?=?[NSString?stringWithFormat:?@"Snowman_i_i,?x,?y];

NSString?*imagePath?=?[[NSBundle?mainBundle]?pathForResource:imageName?ofType:@"jpg"];

UIImage?*tileImage?=?[UIImage?imageWithContentsOfFile:imagePath];

//draw?tile

UIGraphicsPushContext(ctx);

[tileImage?drawInRect:bounds];

UIGraphicsPopContext();

}

@end

圖6.12 用UIScrollView滾動(dòng)CATiledLayer

當(dāng)你滑動(dòng)這個(gè)圖片,你會(huì)發(fā)現(xiàn)當(dāng)CATiledLayer載入小圖的時(shí)候,他們會(huì)淡入到界面中。這是CATiledLayer的默認(rèn)行為。(你可能已經(jīng)在iOS 6之前的蘋果地圖程序中見過這個(gè)效果)你可以用fadeDuration屬性改變淡入時(shí)長或直接禁用掉。CATiledLayer(不同于大部分的UIKit和Core Animation方法)支持多線程繪制,-drawLayer:inContext:方法可以在多個(gè)線程中同時(shí)地并發(fā)調(diào)用,所以請(qǐng)小心謹(jǐn)慎地確保你在這個(gè)方法中實(shí)現(xiàn)的繪制代碼是線程安全的。

Retina小圖

你也許已經(jīng)注意到了這些小圖并不是以Retina的分辨率顯示的。為了以屏幕的原生分辨率來渲染CATiledLayer,我們需要設(shè)置圖層的contentsScale來匹配UIScreen的scale屬性:

tileLayer.contentsScale?=?[UIScreen?mainScreen].scale;

有趣的是,tileSize是以像素為單位,而不是點(diǎn),所以增大了contentsScale就自動(dòng)有了默認(rèn)的小圖尺寸(現(xiàn)在它是128*128的點(diǎn)而不是256*256).所以,我們不需要手工更新小圖的尺寸或是在Retina分辨率下指定一個(gè)不同的小圖。我們需要做的是適應(yīng)小圖渲染代碼以對(duì)應(yīng)安排scale的變化,然而:

//determine?tile?coordinate

CGRect?bounds?=?CGContextGetClipBoundingBox(ctx);

CGFloat?scale?=?[UIScreen?mainScreen].scale;

NSInteger?x?=?floor(bounds.origin.x?/?layer.tileSize.width?*?scale);

NSInteger?y?=?floor(bounds.origin.y?/?layer.tileSize.height?*?scale);

通過這個(gè)方法糾正scale也意味著我們的雪人圖將以一半的大小渲染在Retina設(shè)備上(總尺寸是1024*1024,而不是2048*2048)。這個(gè)通常都不會(huì)影響到用CATiledLayer正常顯示的圖片類型(比如照片和地圖,他們?cè)谠O(shè)計(jì)上就是要支持放大縮小,能夠在不同的縮放條件下顯示),但是也需要在心里明白。

CAEmitterLayer

在iOS 5中,蘋果引入了一個(gè)新的CALayer子類叫做CAEmitterLayer。CAEmitterLayer是一個(gè)高性能的粒子引擎,被用來創(chuàng)建實(shí)時(shí)例子動(dòng)畫如:煙霧,火,雨等等這些效果。

CAEmitterLayer看上去像是許多CAEmitterCell的容器,這些CAEmitierCell定義了一個(gè)例子效果。你將會(huì)為不同的例子效果定義一個(gè)或多個(gè)CAEmitterCell作為模版,同時(shí)CAEmitterLayer負(fù)責(zé)基于這些模版實(shí)例化一個(gè)粒子流。一個(gè)CAEmitterCell類似于一個(gè)CALayer:它有一個(gè)contents屬性可以定義為一個(gè)CGImage,另外還有一些可設(shè)置屬性控制著表現(xiàn)和行為。我們不會(huì)對(duì)這些屬性逐一進(jìn)行詳細(xì)的描述,你們可以在CAEmitterCell類的頭文件中找到。

我們來舉個(gè)例子。我們將利用在一圓中發(fā)射不同速度和透明度的粒子創(chuàng)建一個(gè)火爆炸的效果。清單6.13包含了生成爆炸的代碼。圖6.13是運(yùn)行結(jié)果

清單6.13 用CAEmitterLayer創(chuàng)建爆炸效果

#import?"ViewController.h"

#import?@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIView?*containerView;

@end

@implementation?ViewController

-?(void)viewDidLoad{

[super?viewDidLoad];

//create?particle?emitter?layer

CAEmitterLayer?*emitter?=?[CAEmitterLayer?layer];

emitter.frame?=?self.containerView.bounds;

[self.containerView.layer?addSublayer:emitter];

//configure?emitter

emitter.renderMode?=?kCAEmitterLayerAdditive;

emitter.emitterPosition?=?CGPointMake(emitter.frame.size.width?/?2.0,?emitter.frame.size.height?/?2.0);

//create?a?particle?template

CAEmitterCell?*cell?=?[[CAEmitterCell?alloc]?init];

cell.contents?=?(__bridge?id)[UIImage?imageNamed:@"Spark.png"].CGImage;

cell.birthRate?=?150;

cell.lifetime?=?5.0;

cell.color?=?[UIColor?colorWithRed:1?green:0.5?blue:0.1?alpha:1.0].CGColor;

cell.alphaSpeed?=?-0.4;

cell.velocity?=?50;

cell.velocityRange?=?50;

cell.emissionRange?=?M_PI?*?2.0;

//add?particle?template?to?emitter

emitter.emitterCells?=?@[cell];

}

@end

圖6.13 火焰爆炸效果

CAEMitterCell的屬性基本上可以分為三種:

這種粒子的某一屬性的初始值。比如,color屬性指定了一個(gè)可以混合圖片內(nèi)容顏色的混合色。在示例中,我們將它設(shè)置為桔色。

例子某一屬性的變化范圍。比如emissionRange屬性的值是2π,這意味著例子可以從360度任意位置反射出來。如果指定一個(gè)小一些的值,就可以創(chuàng)造出一個(gè)圓錐形

指定值在時(shí)間線上的變化。比如,在示例中,我們將alphaSpeed設(shè)置為-0.4,就是說例子的透明度每過一秒就是減少0.4,這樣就有發(fā)射出去之后逐漸小時(shí)的效果。

CAEmitterLayer的屬性它自己控制著整個(gè)例子系統(tǒng)的位置和形狀。一些屬性比如birthRate,lifetime和celocity,這些屬性在CAEmitterCell中也有。這些屬性會(huì)以相乘的方式作用在一起,這樣你就可以用一個(gè)值來加速或者擴(kuò)大整個(gè)例子系統(tǒng)。其他值得提到的屬性有以下這些:

preservesDepth,是否將3D例子系統(tǒng)平面化到一個(gè)圖層(默認(rèn)值)或者可以在3D空間中混合其他的圖層

renderMode,控制著在視覺上粒子圖片是如何混合的。你可能已經(jīng)注意到了示例中我們把它設(shè)置為kCAEmitterLayerAdditive,它實(shí)現(xiàn)了這樣一個(gè)效果:合并例子重疊部分的亮度使得看上去更亮。如果我們把它設(shè)置為默認(rèn)的kCAEmitterLayerUnordered,效果就沒那么好看了(見圖6.14).

圖6.14 禁止混色之后的火焰粒子

CAEAGLLayer

當(dāng)iOS要處理高性能圖形繪制,必要時(shí)就是OpenGL。應(yīng)該說它應(yīng)該是最后的殺手锏,至少對(duì)于非游戲的應(yīng)用來說是的。因?yàn)橄啾菴ore Animation和UIkit框架,它不可思議地復(fù)雜。

OpenGL提供了Core Animation的基礎(chǔ),它是底層的C接口,直接和iPhone,iPad的硬件通信,極少地抽象出來的方法。OpenGL沒有對(duì)象或是圖層的繼承概念。它只是簡單地處理三角形。OpenGL中所有東西都是3D空間中有顏色和紋理的三角形。用起來非常復(fù)雜和強(qiáng)大,但是用OpenGL繪制iOS用戶界面就需要很多很多的工作了。

為了能夠以高性能使用Core Animation,你需要判斷你需要繪制哪種內(nèi)容(矢量圖形,例子,文本,等等),但后選擇合適的圖層去呈現(xiàn)這些內(nèi)容,Core Animation中只有一些類型的內(nèi)容是被高度優(yōu)化的;所以如果你想繪制的東西并不能找到標(biāo)準(zhǔn)的圖層類,想要得到高性能就比較費(fèi)事情了。

因?yàn)镺penGL根本不會(huì)對(duì)你的內(nèi)容進(jìn)行假設(shè),它能夠繪制得相當(dāng)快。利用OpenGL,你可以繪制任何你知道必要的集合信息和形狀邏輯的內(nèi)容。所以很多游戲都喜歡用OpenGL(這些情況下,Core Animation的限制就明顯了:它優(yōu)化過的內(nèi)容類型并不一定能滿足需求),但是這樣依賴,方便的高度抽象接口就沒了。

在iOS 5中,蘋果引入了一個(gè)新的框架叫做GLKit,它去掉了一些設(shè)置OpenGL的復(fù)雜性,提供了一個(gè)叫做CLKView的UIView的子類,幫你處理大部分的設(shè)置和繪制工作。前提是各種各樣的OpenGL繪圖緩沖的底層可配置項(xiàng)仍然需要你用CAEAGLLayer完成,它是CALayer的一個(gè)子類,用來顯示任意的OpenGL圖形。

大部分情況下你都不需要手動(dòng)設(shè)置CAEAGLLayer(假設(shè)用GLKView),過去的日子就不要再提了。特別的,我們將設(shè)置一個(gè)OpenGL ES 2.0的上下文,它是現(xiàn)代的iOS設(shè)備的標(biāo)準(zhǔn)做法。

盡管不需要GLKit也可以做到這一切,但是GLKit囊括了很多額外的工作,比如設(shè)置頂點(diǎn)和片段著色器,這些都以類C語言叫做GLSL自包含在程序中,同時(shí)在運(yùn)行時(shí)載入到圖形硬件中。編寫GLSL代碼和設(shè)置EAGLayer沒有什么關(guān)系,所以我們將用GLKBaseEffect類將著色邏輯抽象出來。其他的事情,我們還是會(huì)有以往的方式。

在開始之前,你需要將GLKit和OpenGLES框架加入到你的項(xiàng)目中,然后就可以實(shí)現(xiàn)清單6.14中的代碼,里面是設(shè)置一個(gè)GAEAGLLayer的最少工作,它使用了OpenGL ES 2.0 的繪圖上下文,并渲染了一個(gè)有色三角(見圖6.15).

清單6.14 用CAEAGLLayer繪制一個(gè)三角形

#import?"ViewController.h"

#import?#import?@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIView?*glView;

@property?(nonatomic,?strong)?EAGLContext?*glContext;

@property?(nonatomic,?strong)?CAEAGLLayer?*glLayer;

@property?(nonatomic,?assign)?GLuint?framebuffer;

@property?(nonatomic,?assign)?GLuint?colorRenderbuffer;

@property?(nonatomic,?assign)?GLint?framebufferWidth;

@property?(nonatomic,?assign)?GLint?framebufferHeight;

@property?(nonatomic,?strong)?GLKBaseEffect?*effect;

@end

@implementation?ViewController

-?(void)setUpBuffers{

//set?up?frame?buffer

glGenFramebuffers(1,?&_framebuffer);

glBindFramebuffer(GL_FRAMEBUFFER,?_framebuffer);

//set?up?color?render?buffer

glGenRenderbuffers(1,?&_colorRenderbuffer);

glBindRenderbuffer(GL_RENDERBUFFER,?_colorRenderbuffer);

glFramebufferRenderbuffer(GL_FRAMEBUFFER,?GL_COLOR_ATTACHMENT0,?GL_RENDERBUFFER,?_colorRenderbuffer);

[self.glContext?renderbufferStorage:GL_RENDERBUFFER?fromDrawable:self.glLayer];

glGetRenderbufferParameteriv(GL_RENDERBUFFER,?GL_RENDERBUFFER_WIDTH,?&_framebufferWidth);

glGetRenderbufferParameteriv(GL_RENDERBUFFER,?GL_RENDERBUFFER_HEIGHT,?&_framebufferHeight);

//check?success

if?(glCheckFramebufferStatus(GL_FRAMEBUFFER)?!=?GL_FRAMEBUFFER_COMPLETE)?{

NSLog(@"Failed?to?make?complete?framebuffer?object:?%i",?glCheckFramebufferStatus(GL_FRAMEBUFFER));

}

}

-?(void)tearDownBuffers{

if?(_framebuffer)?{

//delete?framebuffer

glDeleteFramebuffers(1,?&_framebuffer);

_framebuffer?=?0;

}

if?(_colorRenderbuffer)?{

//delete?color?render?buffer

glDeleteRenderbuffers(1,?&_colorRenderbuffer);

_colorRenderbuffer?=?0;

}

}

-?(void)drawFrame?{

//bind?framebuffer?&?set?viewport

glBindFramebuffer(GL_FRAMEBUFFER,?_framebuffer);

glViewport(0,?0,?_framebufferWidth,?_framebufferHeight);

//bind?shader?program

[self.effect?prepareToDraw];

//clear?the?screen

glClear(GL_COLOR_BUFFER_BIT);?glClearColor(0.0,?0.0,?0.0,?1.0);

//set?up?vertices

GLfloat?vertices[]?=?{

-0.5f,?-0.5f,?-1.0f,?0.0f,?0.5f,?-1.0f,?0.5f,?-0.5f,?-1.0f,

};

//set?up?colors

GLfloat?colors[]?=?{

0.0f,?0.0f,?1.0f,?1.0f,?0.0f,?1.0f,?0.0f,?1.0f,?1.0f,?0.0f,?0.0f,?1.0f,

};

//draw?triangle

glEnableVertexAttribArray(GLKVertexAttribPosition);

glEnableVertexAttribArray(GLKVertexAttribColor);

glVertexAttribPointer(GLKVertexAttribPosition,?3,?GL_FLOAT,?GL_FALSE,?0,?vertices);

glVertexAttribPointer(GLKVertexAttribColor,4,?GL_FLOAT,?GL_FALSE,?0,?colors);

glDrawArrays(GL_TRIANGLES,?0,?3);

//present?render?buffer

glBindRenderbuffer(GL_RENDERBUFFER,?_colorRenderbuffer);

[self.glContext?presentRenderbuffer:GL_RENDERBUFFER];

}

-?(void)viewDidLoad{

[super?viewDidLoad];

//set?up?context

self.glContext?=?[[EAGLContext?alloc]?initWithAPI:?kEAGLRenderingAPIOpenGLES2];

[EAGLContext?setCurrentContext:self.glContext];

//set?up?layer

self.glLayer?=?[CAEAGLLayer?layer];

self.glLayer.frame?=?self.glView.bounds;

[self.glView.layer?addSublayer:self.glLayer];

self.glLayer.drawableProperties?=?@{kEAGLDrawablePropertyRetainedBacking:@NO,?kEAGLDrawablePropertyColorFormat:?kEAGLColorFormatRGBA8};

//set?up?base?effect

self.effect?=?[[GLKBaseEffect?alloc]?init];

//set?up?buffers

[self?setUpBuffers];

//draw?frame

[self?drawFrame];

}

-?(void)viewDidUnload

{

[self?tearDownBuffers];

[super?viewDidUnload];

}

-?(void)dealloc{

[self?tearDownBuffers];

[EAGLContext?setCurrentContext:nil];

}

@end

圖6.15 用OpenGL渲染的CAEAGLLayer圖層

在一個(gè)真正的OpenGL應(yīng)用中,我們可能會(huì)用NSTimer或CADisplayLink周期性地每秒鐘調(diào)用-drawRrame方法60次,同時(shí)會(huì)將幾何圖形生成和繪制分開以便不會(huì)每次都重新生成三角形的頂點(diǎn)(這樣也可以讓我們繪制其他的一些東西而不是一個(gè)三角形而已),不過上面這個(gè)例子已經(jīng)足夠演示了繪圖原則了。

AVPlayerLayer

最后一個(gè)圖層類型是AVPlayerLayer。盡管它不是Core Animation框架的一部分(AV前綴看上去像),AVPlayerLayer是有別的框架(AVFoundation)提供的,它和Core Animation緊密地結(jié)合在一起,提供了一個(gè)CALayer子類來顯示自定義的內(nèi)容類型。

AVPlayerLayer是用來在iOS上播放視頻的。他是高級(jí)接口例如MPMoivePlayer的底層實(shí)現(xiàn),提供了顯示視頻的底層控制。AVPlayerLayer的使用相當(dāng)簡單:你可以用+playerLayerWithPlayer:方法創(chuàng)建一個(gè)已經(jīng)綁定了視頻播放器的圖層,或者你可以先創(chuàng)建一個(gè)圖層,然后用player屬性綁定一個(gè)AVPlayer實(shí)例。

在我們開始之前,我們需要添加AVFoundation到我們的項(xiàng)目中。然后,清單6.15創(chuàng)建了一個(gè)簡單的電影播放器,圖6.16是代碼運(yùn)行結(jié)果。

清單6.15 用AVPlayerLayer播放視頻

#import?"ViewController.h"

#import?#import?@interface?ViewController?()

@property?(nonatomic,?weak)?IBOutlet?UIView?*containerView;?@end

@implementation?ViewController

-?(void)viewDidLoad{

[super?viewDidLoad];

//get?video?URL

NSURL?*URL?=?[[NSBundle?mainBundle]?URLForResource:@"Ship"?withExtension:@"mp4"];

//create?player?and?player?layer

AVPlayer?*player?=?[AVPlayer?playerWithURL:URL];

AVPlayerLayer?*playerLayer?=?[AVPlayerLayer?playerLayerWithPlayer:player];

//set?player?layer?frame?and?attach?it?to?our?view

playerLayer.frame?=?self.containerView.bounds;

[self.containerView.layer?addSublayer:playerLayer];

//play?the?video

[player?play];

}

@end

圖6.16 用AVPlayerLayer圖層播放視頻的截圖

我們用代碼創(chuàng)建了一個(gè)AVPlayerLayer,但是我們?nèi)匀话阉砑拥搅艘粋€(gè)容器視圖中,而不是直接在controller中的主視圖上添加。這樣其實(shí)是為了可以使用自動(dòng)布局限制使得圖層在最中間;否則,一旦設(shè)備被旋轉(zhuǎn)了我們就要手動(dòng)重新放置位置,因?yàn)镃ore Animation并不支持自動(dòng)大小和自動(dòng)布局(見第三章『圖層幾何學(xué)』)。

當(dāng)然,因?yàn)锳VPlayerLayer是CALayer的子類,它繼承了父類的所有特性。我們并不會(huì)受限于要在一個(gè)矩形中播放視頻;清單6.16演示了在3D,圓角,有色邊框,蒙板,陰影等效果(見圖6.17).

清單6.16 給視頻增加變換,邊框和圓角

-?(void)viewDidLoad{

...

//set?player?layer?frame?and?attach?it?to?our?view

playerLayer.frame?=?self.containerView.bounds;

[self.containerView.layer?addSublayer:playerLayer];

//transform?layer

CATransform3D?transform?=?CATransform3DIdentity;

transform.m34?=?-1.0?/?500.0;

transform?=?CATransform3DRotate(transform,?M_PI_4,?1,?1,?0);

playerLayer.transform?=?transform;

//add?rounded?corners?and?border

playerLayer.masksToBounds?=?YES;

playerLayer.cornerRadius?=?20.0;

playerLayer.borderColor?=?[UIColor?redColor].CGColor;

playerLayer.borderWidth?=?5.0;

//play?the?video

[player?play];

}

圖6.17 3D視角下的邊框和圓角AVPlayerLayer

總結(jié)

這一章我們簡要概述了一些專用圖層以及用他們實(shí)現(xiàn)的一些效果,我們只是了解到這些圖層的皮毛,像CATiledLayer和CAEMitterLayer這些類可以單獨(dú)寫一章的。但是,重點(diǎn)是記住CALayer是用處很大的,而且它并沒有為所有可能的場(chǎng)景進(jìn)行優(yōu)化。為了獲得Core Animation最好的性能,你需要為你的工作選對(duì)正確的工具,希望你能夠挖掘這些不同的CALayer子類的功能。 這一章我們通過CAEmitterLayer和AVPlayerLayer類簡單地接觸到了一些動(dòng)畫,在第二章,我們將繼續(xù)深入研究動(dòng)畫,就從隱式動(dòng)畫開始。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容