3atv精品不卡视频,97人人超碰国产精品最新,中文字幕av一区二区三区人妻少妇,久久久精品波多野结衣,日韩一区二区三区精品

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

CoreAnimation

發布時間:2023/12/14 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 CoreAnimation 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、圖層的樹狀結構

本節轉載自ios核心動畫高級技巧

巨妖有圖層,洋蔥也有圖層,你有嗎?我們都有圖層 – 史萊克

Core Animation其實是一個令人誤解的命名。你可能認為它只是用來做動畫的,但實際上它是從一個叫做Layer Kit這么一個不怎么和動畫有關的名字演變而來,所以做動畫這只是Core Animation特性的冰山一角。

Core Animation是一個復合引擎,它的職責就是盡可能快地組合屏幕上不同的可視內容,這個內容是被分解成獨立的圖層,存儲在一個叫做圖層樹的體系之中。于是這個樹形成了UIKit以及在iOS應用程序當中你所能在屏幕上看見的一切的基礎。

在我們討論動畫之前,我們將從圖層樹開始,涉及一下Core Animation的靜態組合以及布局特性。


1.1 圖層與視圖

如果你曾經在iOS或者Mac OS平臺上寫過應用程序,你可能會對視圖的概念比較熟悉。一個視圖就是在屏幕上顯示的一個矩形塊(比如圖片,文字或者視頻),它能夠攔截類似于鼠標點擊或者觸摸手勢等用戶輸入。視圖在層級關系中可以互相嵌套,一個視圖可以管理它的所有子視圖的位置。圖1.1顯示了一種典型的視圖層級關系

在iOS當中,所有的視圖都從一個叫做UIVIew的基類派生而來,UIView可以處理觸摸事件,可以支持基于Core Graphics繪圖,可以做仿射變換(例如旋轉或者縮放),或者簡單的類似于滑動或者漸變的動畫。

CALayer

CALayer類在概念上和UIView類似,同樣也是一些被層級關系樹管理的矩形塊,同樣也可以包含一些內容(像圖片,文本或者背景色),管理子圖層的位置。它們有一些方法和屬性用來做動畫和變換。和UIView最大的不同是CALayer不處理用戶的交互。

CALayer并不清楚具體的響應鏈(iOS通過視圖層級關系用來傳送觸摸事件的機制),于是它并不能夠響應事件,即使它提供了一些方法來判斷是否一個觸點在圖層的范圍之內(具體見第三章,“圖層的幾何學”)

平行的層級關系

每一個UIview都有一個CALayer實例的圖層屬性,也就是所謂的backing layer,視圖的職責就是創建并管理這個圖層,以確保當子視圖在層級關系中添加或者被移除的時候,他們關聯的圖層也同樣對應在層級關系樹當中有相同的操作(見圖1.2)。

實際上這些背后關聯的圖層才是真正用來在屏幕上顯示和做動畫,UIView僅僅是對它的一個封裝,提供了一些iOS類似于處理觸摸的具體功能,以及Core Animation底層方法的高級接口。

但是為什么iOS要基于UIView和CALayer提供兩個平行的層級關系呢?為什么不用一個簡單的層級來處理所有事情呢?原因在于要做職責分離,這樣也能避免很多重復代碼。在iOS和Mac OS兩個平臺上,事件和用戶交互有很多地方的不同,基于多點觸控的用戶界面和基于鼠標鍵盤有著本質的區別,這就是為什么iOS有UIKit和UIView,但是Mac OS有AppKit和NSView的原因。他們功能上很相似,但是在實現上有著顯著的區別。

繪圖,布局和動畫,相比之下就是類似Mac筆記本和桌面系列一樣應用于iPhone和iPad觸屏的概念。把這種功能的邏輯分開并應用到獨立的Core Animation框架,蘋果就能夠在iOS和Mac OS之間共享代碼,使得對蘋果自己的OS開發團隊和第三方開發者去開發兩個平臺的應用更加便捷。

實際上,這里并不是兩個層級關系,而是四個,每一個都扮演不同的角色,除了視圖層級和圖層樹之外,還存在呈現樹和渲染樹,將在第七章“隱式動畫”和第十二章“性能調優”分別討論。

1.2 圖層的能力

如果說CALayer是UIView內部實現細節,那我們為什么要全面地了解它呢?蘋果當然為我們提供了優美簡潔的UIView接口,那么我們是否就沒必要直接去處理Core Animation的細節了呢?

某種意義上說的確是這樣,對一些簡單的需求來說,我們確實沒必要處理CALayer,因為蘋果已經通過UIView的高級API間接地使得動畫變得很簡單。

但是這種簡單會不可避免地帶來一些靈活上的缺陷。如果你略微想在底層做一些改變,或者使用一些蘋果沒有在UIView上實現的接口功能,這時除了介入Core Animation底層之外別無選擇。

我們已經證實了圖層不能像視圖那樣處理觸摸事件,那么他能做哪些視圖不能做的呢?這里有一些UIView沒有暴露出來的CALayer的功能:
- 陰影,圓角,帶顏色的邊框
- 3D變換
- 非矩形范圍
- 透明遮罩
- 多級非線性動畫

我們將會在后續章節中探索這些功能,首先我們要關注一下在應用程序當中CALayer是怎樣被利用起來的。

1.3 使用圖層

首先我們來創建一個簡單的項目,來操縱一些layer的屬性。打開Xcode,使用Single View Application模板創建一個工程。

在屏幕中央創建一個小視圖(大約200 X 200的尺寸),當然你可以手工編碼,或者使用Interface Builder(隨你方便)。確保你的視圖控制器要添加一個視圖的屬性以便可以直接訪問它。我們把它稱作layerView。

運行項目,應該能在淺灰色屏幕背景中看見一個白色方塊(圖1.3),如果沒看見,可能需要調整一下背景window或者view的顏色

這并沒有什么令人激動的地方,我們來添加一個色塊,在白色方塊中間添加一個小的藍色塊。

我們當然可以簡單地在已經存在的UIView上添加一個子視圖(隨意用代碼或者IB),但這不能真正學到任何關于圖層的東西。

于是我們來創建一個CALayer,并且把它作為我們視圖相關圖層的子圖層。盡管UIView類的接口中暴露了圖層屬性,但是標準的Xcode項目模板并沒有包含Core Animation相關頭文件。所以如果我們不給項目添加合適的庫,是不能夠使用任何圖層相關的方法或者訪問它的屬性。所以首先需要添加QuartzCore框架到Build Phases標簽(圖1.4),然后在vc的.m文件中引入庫。

之后就可以在代碼中直接引用CALayer的屬性和方法。在清單1.1中,我們用創建了一個CALayer,設置了它的backgroundColor屬性,然后添加到layerView背后相關圖層的子圖層(這段代碼的前提是通過IB創建了layerView并做好了連接),圖1.5顯示了結果。

清單1.1 給視圖添加一個藍色子圖層

#import "ViewController.h" #import @interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *layerView;  @end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//create sublayerCALayer *blueLayer = [CALayer layer];blueLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);blueLayer.backgroundColor = [UIColor blueColor].CGColor;//add it to our view[self.layerView.layer addSublayer:blueLayer]; } @end

一個視圖只有一個相關聯的圖層(自動創建),同時它也可以支持添加無數多個子圖層,從清單1.1可以看出,你可以顯示創建一個單獨的圖層,并且把它直接添加到視圖關聯圖層的子圖層。盡管可以這樣添加圖層,但往往我們只是見簡單地處理視圖,他們關聯的圖層并不需要額外地手動添加子圖層。

在Mac OS平臺,10.8版本之前,一個顯著的性能缺陷就是由于用了視圖層級而不是單獨在一個視圖內使用CALayer樹狀層級。但是在iOS平臺,使用輕量級的UIView類并沒有顯著的性能影響(當然在Mac OS 10.8之后,NSView的性能同樣也得到很大程度的提高)。

使用圖層關聯的視圖而不是CALayer的好處在于,你能在使用所有CALayer底層特性的同時,也可以使用UIView的高級API(比如自動排版,布局和事件處理)。

然而,當滿足以下條件的時候,你可能更需要使用CALayer而不是UIView:
- 開發同時可以在Mac OS上運行的跨平臺應用
- 使用多種CALayer的子類(見第六章,“特殊的圖層“),并且不想創建額外的UIView去包封裝它們所有
- 做一些對性能特別挑剔的工作,比如對UIView一些可忽略不計的操作都會引起顯著的不同(盡管如此,你可能會直接想使用OpenGL繪圖)

但是這些例子都很少見,總的來說,處理視圖會比單獨處理圖層更加方便。

1.4 總結

這一章闡述了圖層的樹狀結構,說明了如何在iOS中由UIView的層級關系形成的一種平行的CALayer層級關系,在后面的實驗中,我們創建了自己的CALayer,并把它添加到圖層樹中。

在第二章,“圖層關聯的圖片”,我們將要研究一下CALayer關聯的圖片,以及Core Animation提供的操作顯示的一些特性。

二、寄宿圖

本節轉載自ios核心動畫高級技巧

圖片勝過千言萬語,界面抵得上千圖片 ——Ben Shneiderman

我們在第一章『圖層樹』中介紹了CALayer類并創建了一個簡單的有藍色背景的圖層。背景顏色還好啦,但是如果它僅僅是展現了一個單調的顏色未免也太無聊了。事實上CALayer類能夠包含一張你喜歡的圖片,這一章節我們將來探索CALayer的寄宿圖(即圖層中包含的圖)。


2.1 contents屬性

CALayer 有一個屬性叫做contents,這個屬性的類型被定義為id,意味著它可以是任何類型的對象。在這種情況下,你可以給contents屬性賦任何值,你的app仍然能夠編譯通過。但是,在實踐中,如果你給contents賦的不是CGImage,那么你得到的圖層將是空白的。

contents這個奇怪的表現是由Mac OS的歷史原因造成的。它之所以被定義為id類型,是因為在Mac OS系統上,這個屬性對CGImage和NSImage類型的值都起作用。如果你試圖在iOS平臺上將UIImage的值賦給它,只能得到一個空白的圖層。一些初識Core Animation的iOS開發者可能會對這個感到困惑。

頭疼的不僅僅是我們剛才提到的這個問題。事實上,你真正要賦值的類型應該是CGImageRef,它是一個指向CGImage結構的指針。UIImage有一個CGImage屬性,它返回一個”CGImageRef”,如果你想把這個值直接賦值給CALayer的contents,那你將會得到一個編譯錯誤。因為CGImageRef并不是一個真正的Cocoa對象,而是一個Core Foundation類型。

盡管Core Foundation類型跟Cocoa對象在運行時貌似很像(被稱作toll-free bridging),他們并不是類型兼容的,不過你可以通過bridged關鍵字轉換。如果要給圖層的寄宿圖賦值,你可以按照以下這個方法:

layer.contents = (__bridge id)image.CGImage;

如果你沒有使用ARC(自動引用計數),你就不需要__bridge這部分。但是,你干嘛不用ARC?!

讓我們來繼續修改我們在第一章新建的工程,以便能夠展示一張圖片而不僅僅是一個背景色。我們已經用代碼的方式建立一個圖層,那我們就不需要額外的圖層了。那么我們就直接把layerView的宿主圖層的contents屬性設置成圖片。

清單2.1 更新后的代碼。 @implementation ViewController- (void)viewDidLoad {[super viewDidLoad]; //load an imageUIImage *image = [UIImage imageNamed:@"Snowman.png"];//add it directly to our view's layerself.layerView.layer.contents = (__bridge id)image.CGImage; } @end

我們用這些簡單的代碼做了一件很有趣的事情:我們利用CALayer在一個普通的UIView中顯示了一張圖片。這不是一個UIImageView,它不是我們通常用來展示圖片的方法。通過直接操作圖層,我們使用了一些新的函數,使得UIView更加有趣了。

contentGravity

你可能已經注意到了我們的雪人看起來有點。。。胖 ==! 我們加載的圖片并不剛好是一個方的,為了適應這個視圖,它有一點點被拉伸了。在使用UIImageView的時候遇到過同樣的問題,解決方法就是把contentMode屬性設置成更合適的值,像這樣:

view.contentMode = UIViewContentModeScaleAspectFit;

這個方法基本和我們遇到的情況的解決方法已經接近了(你可以試一下 :) ),不過UIView大多數視覺相關的屬性比如contentMode,對這些屬性的操作其實是對對應圖層的操作。

CALayer與contentMode對應的屬性叫做contentsGravity,但是它是一個NSString類型,而不是像對應的UIKit部分,那里面的值是枚舉。contentsGravity可選的常量值有以下一些:
- kCAGravityCenter
- kCAGravityTop
- kCAGravityBottom
- kCAGravityLeft
- kCAGravityRight
- kCAGravityTopLeft
- kCAGravityTopRight
- kCAGravityBottomLeft
- kCAGravityBottomRight
- kCAGravityResize
- kCAGravityResizeAspect
- kCAGravityResizeAspectFill

和cotentMode一樣,contentsGravity的目的是為了決定內容在圖層的邊界中怎么對齊,我們將使用kCAGravityResizeAspect,它的效果等同于UIViewContentModeScaleAspectFit, 同時它還能在圖層中等比例拉伸以適應圖層的邊界。圖2.2 可以看到結果:

self.layerView.layer.contentsGravity = kCAGravityResizeAspect;

contentsScale

contentsScale屬性定義了寄宿圖的像素尺寸和視圖大小的比例,默認情況下它是一個值為1.0的浮點數。

contentsScale的目的并不是那么明顯。它并不是總會對屏幕上的寄宿圖有影響。如果你嘗試對我們的例子設置不同的值,你就會發現根本沒任何影響。因為contents由于設置了contentsGravity屬性,所以它已經被拉伸以適應圖層的邊界。

如果你只是單純地想放大圖層的contents圖片,你可以通過使用圖層的transform和affineTransform屬性來達到這個目的(見第五章『Transforms』,里面對此有解釋),這(指放大)也不是contengsScale的目的所在.

contentsScale屬性其實屬于支持高分辨率(又稱Hi-DPI或Retina)屏幕機制的一部分。它用來判斷在繪制圖層的時候應該為寄宿圖創建的空間大小,和需要顯示的圖片的拉伸度(假設并沒有設置contentsGravity屬性)。UIView有一個類似功能但是非常少用到的contentScaleFactor屬性。

如果contentsScale設置為1.0,將會以每個點1個像素繪制圖片,如果設置為2.0,則會以每個點2個像素繪制圖片,這就是我們熟知的Retina屏幕。(如果你對像素和點的概念不是很清楚的話,這個章節的后面部分將會對此做出解釋)。

這并不會對我們在使用kCAGravityResizeAspect時產生任何影響,因為它就是拉伸圖片以適應圖層而已,根本不會考慮到分辨率問題。但是如果我們把contentsGravity設置為kCAGravityCenter(這個值并不會拉伸圖片),那將會有很明顯的變化(如圖2.3)

如你所見,我們的雪人不僅有點大還有點像素的顆粒感。那是因為和UIImage不同,CGImage沒有拉伸的概念。當我們使用UIImage類去讀取我們的雪人圖片的時候,他讀取了高質量的Retina版本的圖片。但是當我們用CGImage來設置我們的圖層的內容時,拉伸這個因素在轉換的時候就丟失了。不過我們可以通過手動設置contentsScale來修復這個問題(如2.2清單),圖2.4是結果

@implementation ViewController - (void)viewDidLoad {[super viewDidLoad]; //load an imageUIImage *image = [UIImage imageNamed:@"Snowman.png"]; //add it directly to our view's layerself.layerView.layer.contents = (__bridge id)image.CGImage; //center the imageself.layerView.layer.contentsGravity = kCAGravityCenter;//set the contentsScale to match imageself.layerView.layer.contentsScale = image.scale; } @end

當用代碼的方式來處理寄宿圖的時候,一定要記住要手動的設置圖層的contentsScale屬性,否則,你的圖片在Retina設備上就顯示得不正確啦。代碼如下:

layer.contentsScale = [UIScreen mainScreen].scale;

maskToBounds

現在我們的雪人總算是顯示了正確的大小,不過你也許已經發現了另外一些事情:他超出了視圖的邊界。默認情況下,UIView仍然會繪制超過邊界的內容或是子視圖,在CALayer下也是這樣的。
UIView有一個叫做clipsToBounds的屬性可以用來決定是否顯示超出邊界的內容,CALayer對應的屬性叫做masksToBounds,把它設置為YES,雪人就在邊界里啦~(如圖2.5)

contentsRect

CALayer的contentsRect屬性允許我們在圖層邊框里顯示寄宿圖的一個子域。這涉及到圖片是如何顯示和拉伸的,所以要比contentsGravity靈活多了
和bounds,frame不同,contentsRect不是按點來計算的,它使用了單位坐標,單位坐標指定在0到1之間,是一個相對值(像素和點就是絕對值)。所以他們是相對與寄宿圖的尺寸的。iOS使用了以下的坐標系統:
- 點 —— 在iOS和Mac OS中最常見的坐標體系。點就像是虛擬的像素,也被稱作邏輯像素。在標準設備上,一個點就是一個像素,但是在Retina設備上,一個點等于2*2個像素。iOS用點作為屏幕的坐標測算體系就是為了在Retina設備和普通設備上能有一致的視覺效果。
- 像素 —— 物理像素坐標并不會用來屏幕布局,但是仍然與圖片有相對關系。UIImage是一個屏幕分辨率解決方案,所以指定點來度量大小。但是一些底層的圖片表示如CGImage就會使用像素,所以你要清楚在Retina設備和普通設備上,他們表現出來了不同的大小。
- 單位 —— 對于與圖片大小或是圖層邊界相關的顯示,單位坐標是一個方便的度量方式, 當大小改變的時候,也不需要再次調整。單位坐標在OpenGL這種紋理坐標系統中用得很多,Core Animation中也用到了單位坐標。

默認的contentsRect是{0, 0, 1, 1},這意味著整個寄宿圖默認都是可見的,如果我們指定一個小一點的矩形,圖片就會被裁剪(如圖2.6)

事實上給contentsRect設置一個負數的原點或是大于{1, 1}的尺寸也是可以的。這種情況下,最外面的像素會被拉伸以填充剩下的區域。

contentsRect在app中最有趣的地方在于一個叫做image sprites(圖片拼合)的用法。如果你有游戲編程的經驗,那么你一定對圖片拼合的概念很熟悉,圖片能夠在屏幕上獨立地變更位置。拋開游戲編程不談,這個技術常用來指代載入拼合的圖片,跟移動圖片一點關系也沒有。

典型地,圖片拼合后可以打包整合到一張大圖上一次性載入。相比多次載入不同的圖片,這樣做能夠帶來很多方面的好處:內存使用,載入時間,渲染性能等等。

2D游戲引擎入Cocos2D使用了拼合技術,它使用OpenGL來顯示圖片。不過我們可以使用拼合在一個普通的UIKit應用中,對!就是使用contentsRect
首先,我們需要一個拼合后的圖表 —— 一個包含小一些的拼合圖的大圖片。如圖2.7所示:

接下來,我們要在app中載入并顯示這些拼合圖。規則很簡單:像平常一樣載入我們的大圖,然后把它賦值給四個獨立的圖層的contents,然后設置每個圖層的contentsRect來去掉我們不想顯示的部分。

我們的工程中需要一些額外的視圖。(為了避免太多代碼。我們將使用Interface Builder來拜訪他們的位置,如果你愿意還是可以用代碼的方式來實現的)。清單2.3有需要的代碼,圖2.8展示了結果

@interface ViewController () @property (nonatomic, weak) IBOutlet UIView *coneView; @property (nonatomic, weak) IBOutlet UIView *shipView; @property (nonatomic, weak) IBOutlet UIView *iglooView; @property (nonatomic, weak) IBOutlet UIView *anchorView; @end@implementation ViewController- (void)addSpriteImage:(UIImage *)image withContentRect:(CGRect)rect toLayer:(CALayer *)layer //set image {layer.contents = (__bridge id)image.CGImage;//scale contents to fitlayer.contentsGravity = kCAGravityResizeAspect;//set contentsRectlayer.contentsRect = rect; }- (void)viewDidLoad {[super viewDidLoad]; //load sprite sheetUIImage *image = [UIImage imageNamed:@"Sprites.png"];//set igloo sprite[self addSpriteImage:image withContentRect:CGRectMake(0, 0, 0.5, 0.5) toLayer:self.iglooView.layer];//set cone sprite[self addSpriteImage:image withContentRect:CGRectMake(0.5, 0, 0.5, 0.5) toLayer:self.coneView.layer];//set anchor sprite[self addSpriteImage:image withContentRect:CGRectMake(0, 0.5, 0.5, 0.5) toLayer:self.anchorView.layer];//set spaceship sprite[self addSpriteImage:image withContentRect:CGRectMake(0.5, 0.5, 0.5, 0.5) toLayer:self.shipView.layer]; } @end

拼合不僅給app提供了一個整潔的載入方式,還有效地提高了載入性能(單張大圖比多張小圖載入地更快),但是如果有手動安排的話,他們還是有一些不方便的,如果你需要在一個已經創建好的品和圖上做一些尺寸上的修改或者其他變動,無疑是比較麻煩的。

Mac上有一些商業軟件可以為你自動拼合圖片,這些工具自動生成一個包含拼合后的坐標的XML或者plist文件,拼合圖片的使用大大簡化。這個文件可以和圖片一同載入,并給每個拼合的圖層設置contentsRect,這樣開發者就不用手動寫代碼來擺放位置了。

這些文件通常在OpenGL游戲中使用,不過呢,你要是有興趣在一些常見的app中使用拼合技術,那么一個叫做LayerSprites的開源庫,它能夠讀取Cocos2D格式中的拼合圖并在普通的Core Animation層中顯示出來。

contentsCenter

本章我們介紹的最后一個和內容有關的屬性是contentsCenter,看名字你可能會以為它可能跟圖片的位置有關,不過這名字著實誤導了你。contentsCenter其實是一個CGRect,它定義了一個固定的邊框和一個在圖層上可拉伸的區域。 改變contentsCenter的值并不會影響到寄宿圖的顯示,除非這個圖層的大小改變了,你才看得到效果。

默認情況下,contentsCenter是{0, 0, 1, 1},這意味著如果大小(由conttensGravity決定)改變了,那么寄宿圖將會均勻地拉伸開。但是如果我們增加原點的值并減小尺寸。我們會在圖片的周圍創造一個邊框。圖2.9展示了contentsCenter設置為{0.25, 0.25, 0.5, 0.5}的效果。

這意味著我們可以隨意重設尺寸,邊框仍然會是連續的。他工作起來的效果和UIImage里的-resizableImageWithCapInsets: 方法效果非常類似,只是它可以運用到任何寄宿圖,甚至包括在Core Graphics運行時繪制的圖形(本章稍后會講到)。

清單2.4 演示了如何編寫這些可拉伸視圖。不過,contentsCenter的另一個很酷的特性就是,它可以在Interface Builder里面配置,根本不用寫代碼。如圖2.11
清單2.4 用contentsCenter設置可拉伸視圖

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *button1; @property (nonatomic, weak) IBOutlet UIView *button2;@end@implementation ViewController- (void)addStretchableImage:(UIImage *)image withContentCenter:(CGRect)rect toLayer:(CALayer *)layer { //set imagelayer.contents = (__bridge id)image.CGImage;//set contentsCenterlayer.contentsCenter = rect; }- (void)viewDidLoad {[super viewDidLoad]; //load button imageUIImage *image = [UIImage imageNamed:@"Button.png"];//set button 1[self addStretchableImage:image withContentCenter:CGRectMake(0.25, 0.25, 0.5, 0.5) toLayer:self.button1.layer];//set button 2[self addStretchableImage:image withContentCenter:CGRectMake(0.25, 0.25, 0.5, 0.5) toLayer:self.button2.layer]; } @end

2.2 Custom Drawing

給contents賦CGImage的值不是唯一的設置寄宿圖的方法。我們也可以直接用Core Graphics直接繪制寄宿圖。能夠通過繼承UIView并實現-drawRect:方法來自定義繪制。

-drawRect:方法沒有默認的實現,因為對UIView來說,寄宿圖并不是必須的,它不在意那到底是單調的顏色還是有一個圖片的實例。如果UIView檢測到-drawRect:方法被調用了,它就會為視圖分配一個寄宿圖,這個寄宿圖的像素尺寸等于視圖大小乘以 contentsScale的值。

如果你不需要寄宿圖,那就不要創建這個方法了,這會造成CPU資源和內存的浪費,這也是為什么蘋果建議:如果沒有自定義繪制的任務就不要在子類中寫一個空的-drawRect:方法。

當視圖在屏幕上出現的時候-drawRect:方法就會被自動調用。-drawRect:方法里面的代碼利用Core Graphics去繪制一個寄宿圖,然后內容就會被緩存起來直到它需要被更新(通常是因為開發者調用了-setNeedsDisplay方法,盡管影響到表現效果的屬性值被更改時,一些視圖類型會被自動重繪,如bounds屬性)。雖然-drawRect:方法是一個UIView方法,事實上都是底層的CALayer安排了重繪工作和保存了因此產生的圖片。

CALayer有一個可選的delegate屬性,實現了CALayerDelegate協議,當CALayer需要一個內容特定的信息時,就會從協議中請求。CALayerDelegate是一個非正式協議,其實就是說沒有CALayerDelegate @protocol可以讓你在類里面引用啦。你只需要調用你想調用的方法,CALayer會幫你做剩下的。(delegate屬性被聲明為id類型,所有的代理方法都是可選的)。

當需要被重繪時,CALayer會請求它的代理給他一個寄宿圖來顯示。它通過調用下面這個方法做到的:

(void)displayLayer:(CALayerCALayer *)layer;

趁著這個機會,如果代理想直接設置contents屬性的話,它就可以這么做,不然沒有別的方法可以調用了。如果代理不實現-displayLayer:方法,CALayer就會轉而嘗試調用下面這個方法:

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;

在調用這個方法之前,CALayer創建了一個合適尺寸的空寄宿圖(尺寸由bounds和contentsScale決定)和一個Core Graphics的繪制上下文環境,為繪制寄宿圖做準備,他作為ctx參數傳入。

讓我們來繼續第一章的項目讓它實現CALayerDelegate并做一些繪圖工作吧(見清單2.5).圖2.12是他的結果

清單2.5 實現CALayerDelegate

@implementation ViewController - (void)viewDidLoad {[super viewDidLoad];//create sublayerCALayer *blueLayer = [CALayer layer];blueLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);blueLayer.backgroundColor = [UIColor blueColor].CGColor;//set controller as layer delegateblueLayer.delegate = self;//ensure that layer backing image uses correct scaleblueLayer.contentsScale = [UIScreen mainScreen].scale; //add layer to our view[self.layerView.layer addSublayer:blueLayer];//force layer to redraw[blueLayer display]; }- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {//draw a thick red circleCGContextSetLineWidth(ctx, 10.0f);CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor);CGContextStrokeEllipseInRect(ctx, layer.bounds); } @end

注意一下一些有趣的事情:
我們在blueLayer上顯式地調用了-display。不同于UIView,當圖層顯示在屏幕上時,CALayer不會自動重繪它的內容。它把重繪的決定權交給了開發者。
盡管我們沒有用masksToBounds屬性,繪制的那個圓仍然沿邊界被裁剪了。這是因為當你使用CALayerDelegate繪制寄宿圖的時候,并沒有對超出邊界外的內容提供繪制支持。

現在你理解了CALayerDelegate,并知道怎么使用它。但是除非你創建了一個單獨的圖層,你幾乎沒有機會用到CALayerDelegate協議。因為當UIView創建了它的宿主圖層時,它就會自動地把圖層的delegate設置為它自己,并提供了一個-displayLayer:的實現,那所有的問題就都沒了。

當使用寄宿了視圖的圖層的時候,你也不必實現-displayLayer:和-drawLayer:inContext:方法來繪制你的寄宿圖。通常做法是實現UIView的-drawRect:方法,UIView就會幫你做完剩下的工作,包括在需要重繪的時候調用-display方法。

2.3 總結

本章介紹了寄宿圖和一些相關的屬性。你學到了如何顯示和放置圖片, 使用拼合技術來顯示, 以及用CALayerDelegate和Core Graphics來繪制圖層內容。

在第三章,”圖層幾何學”中,我們將會探討一下圖層的幾何,觀察他們是如何放置和改變相互的尺寸的。

三、圖層幾何學

本節轉載自ios核心動畫高級技巧

不熟悉幾何學的人就不要來這里了 –柏拉圖學院入口的簽名

在第二章里面,我們介紹了圖層背后的圖片,和一些控制圖層坐標和旋轉的屬性。在這一章中,我們將要看一看圖層內部是如何根據父圖層和兄弟圖層來控制位置和尺寸的。另外我們也會涉及如何管理圖層的幾何結構,以及它是如何被自動調整和自動布局影響的。

3.1 布局

UIView有三個比較重要的布局屬性:frame,bounds和center,CALayer對應地叫做frame,bounds和position。為了能清楚區分,圖層用了“position”,視圖用了“center”,但是他們都代表同樣的值。

frame代表了圖層的外部坐標(也就是在父圖層上占據的空間),bounds是內部坐標({0, 0}通常是圖層的左上角),center和position都代表了相對于父圖層anchorPoint所在的位置。anchorPoint的屬性將會在后續介紹到,現在把它想成圖層的中心點就好了。圖3.1顯示了這些屬性是如何相互依賴的。

視圖的frame,bounds和center屬性僅僅是存取方法,當操縱視圖的frame,實際上是在改變位于視圖下方CALayer的frame,不能夠獨立于圖層之外改變視圖的frame。

對于視圖或者圖層來說,frame并不是一個非常清晰的屬性,它其實是一個虛擬屬性,是根據bounds,position和transform計算而來,所以當其中任何一個值發生改變,frame都會變化。相反,改變frame的值同樣會影響到他們當中的值

記住當對圖層做變換的時候,比如旋轉或者縮放,frame實際上代表了覆蓋在圖層旋轉之后的整個軸對齊的矩形區域,也就是說frame的寬高可能和bounds的寬高不再一致了(圖3.2)

3.2錨點

之前提到過,視圖的center屬性和圖層的position屬性都指定了anchorPoint相對于父圖層的位置。圖層的anchorPoint通過position來控制它的frame的位置,你可以認為anchorPoint是用來移動圖層的把柄。

默認來說,anchorPoint位于圖層的中點,所以圖層的將會以這個點為中心放置。anchorPoint屬性并沒有被UIView接口暴露出來,這也是視圖的position屬性被叫做“center”的原因。但是圖層的anchorPoint可以被移動,比如你可以把它置于圖層frame的左上角,于是圖層的內容將會向右下角的position方向移動(圖3.3),而不是居中了。

和第二章提到的contentsRect和contentsCenter屬性類似,anchorPoint用單位坐標來描述,也就是圖層的相對坐標,圖層左上角是{0, 0},右下角是{1, 1},因此默認坐標是{0.5, 0.5}。anchorPoint可以通過指定x和y值小于0或者大于1,使它放置在圖層范圍之外。

注意在圖3.3中,當改變了anchorPoint,position屬性保持固定的值并沒有發生改變,但是frame卻移動了。

那在什么場合需要改變anchorPoint呢?既然我們可以隨意改變圖層位置,那改變anchorPoint不會造成困惑么?為了舉例說明,我們來舉一個實用的例子,創建一個模擬鬧鐘的項目。

鐘面和鐘表由四張圖片組成(圖3.4),為了簡單說明,我們還是用傳統的方式來裝載和加載圖片,使用四個UIImageView實例(當然你也可以用正常的視圖,設置他們圖層的contents圖片)。

鬧鐘的組件通過IB來排列(圖3.5),這些圖片視圖嵌套在一個容器視圖之內,并且自動調整和自動布局都被禁用了。這是因為自動調整會影響到視圖的frame,而根據圖3.2的演示,當視圖旋轉的時候,frame是會發生改變的,這將會導致一些布局上的失靈。

我們用NSTimer來更新鬧鐘,使用視圖的transform屬性來旋轉鐘表(如果你對這個屬性不太熟悉,不要著急,我們將會在第5章“變換”當中詳細說明),具體代碼見清單3.1

清單3.1 Clock

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIImageView *hourHand; @property (nonatomic, weak) IBOutlet UIImageView *minuteHand; @property (nonatomic, weak) IBOutlet UIImageView *secondHand; @property (nonatomic, weak) NSTimer *timer;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//start timerself.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES];//set initial hand positions[self tick]; }- (void)tick {//convert time to hours, minutes and secondsNSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];NSUInteger units = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;NSDateComponents *components = [calendar components:units fromDate:[NSDate date]];CGFloat hoursAngle = (components.hour / 12.0) * M_PI * 2.0;//calculate hour hand angle //calculate minute hand angleCGFloat minsAngle = (components.minute / 60.0) * M_PI * 2.0;//calculate second hand angleCGFloat secsAngle = (components.second / 60.0) * M_PI * 2.0;//rotate handsself.hourHand.transform = CGAffineTransformMakeRotation(hoursAngle);self.minuteHand.transform = CGAffineTransformMakeRotation(minsAngle);self.secondHand.transform = CGAffineTransformMakeRotation(secsAngle); } @end

運行項目,看起來有點奇怪(圖3.6),因為鐘表的圖片在圍繞著中心旋轉,這并不是我們期待的一個支點。

你也許會認為可以在Interface Builder當中調整指針圖片的位置來解決,但其實并不能達到目的,因為如果不放在鐘面中間的話,同樣不能正確的旋轉。

也許在圖片末尾添加一個透明空間也是個解決方案,但這樣會讓圖片變大,也會消耗更多的內存,這樣并不優雅。

更好的方案是使用anchorPoint屬性,我們來在-viewDidLoad方法中添加幾行代碼來給每個鐘指針的anchorPoint做一些平移(清單3.2),圖3.7顯示了正確的結果。

清單3.2

- (void)viewDidLoad {[super viewDidLoad];// adjust anchor pointsself.secondHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f); self.minuteHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f); self.hourHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);// start timer }

3.3 坐標系

和視圖一樣,圖層在圖層樹當中也是相對于父圖層按層級關系放置,一個圖層的position依賴于它父圖層的bounds,如果父圖層發生了移動,它的所有子圖層也會跟著移動。

這樣對于放置圖層會更加方便,因為你可以通過移動根圖層來將它的子圖層作為一個整體來移動,但是有時候你需要知道一個圖層的絕對位置,或者是相對于另一個圖層的位置,而不是它當前父圖層的位置。

CALayer給不同坐標系之間的圖層轉換提供了一些工具類方法:

- (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer *)layer; - (CGPoint)convertPoint:(CGPoint)point toLayer:(CALayer *)layer; - (CGRect)convertRect:(CGRect)rect fromLayer:(CALayer *)layer; - (CGRect)convertRect:(CGRect)rect toLayer:(CALayer *)layer;

這些方法可以把定義在一個圖層坐標系下的點或者矩形轉換成另一個圖層坐標系下的點或者矩形.

翻轉的幾何結構

常規說來,在iOS上,一個圖層的position位于父圖層的左上角,但是在Mac OS上,通常是位于左下角。Core Animation可以通過geometryFlipped屬性來適配這兩種情況,它決定了一個圖層的坐標是否相對于父圖層垂直翻轉,是一個BOOL類型。在iOS上通過設置它為YES意味著它的子圖層將會被垂直翻轉,也就是將會沿著底部排版而不是通常的頂部(它的所有子圖層也同理,除非把它們的geometryFlipped屬性也設為YES)。

Z坐標軸

和UIView嚴格的二維坐標系不同,CALayer存在于一個三維空間當中。除了我們已經討論過的position和anchorPoint屬性之外,CALayer還有另外兩個屬性,zPosition和anchorPointZ,二者都是在Z軸上描述圖層位置的浮點類型。

注意這里并沒有更深的屬性來描述由寬和高做成的bounds了,圖層是一個完全扁平的對象,你可以把它們想象成類似于一頁二維的堅硬的紙片,用膠水粘成一個空洞,就像三維結構的折紙一樣。

zPosition屬性在大多數情況下其實并不常用。在第五章,我們將會涉及CATransform3D,你會知道如何在三維空間移動和旋轉圖層,除了做變換之外,zPosition最實用的功能就是改變圖層的顯示順序了。

通常,圖層是根據它們子圖層的sublayers出現的順序來類繪制的,這就是所謂的畫家的算法–就像一個畫家在墻上作畫–后被繪制上的圖層將會遮蓋住之前的圖層,但是通過增加圖層的zPosition,就可以把圖層向相機方向前置,于是它就在所有其他圖層的前面了(或者至少是小于它的zPosition值的圖層的前面)。

這里所謂的“相機”實際上是相對于用戶是視角,這里和iPhone背后的內置相機沒任何關系。

圖3.8顯示了在Interface Builder內的一對視圖,正如你所見,首先出現在視圖層級綠色的視圖被繪制在紅色視圖的后面。

我們希望在真實的應用中也能顯示出繪圖的順序,同樣地,如果我們提高綠色視圖的zPosition(清單3.3),我們會發現順序就反了(圖3.9)。其實并不需要增加太多,視圖都非常地薄,所以給zPosition提高一個像素就可以讓綠色視圖前置,當然0.1或者0.0001也能夠做到,但是最好不要這樣,因為浮點類型四舍五入的計算可能會造成一些不便的麻煩。

清單3.3

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *greenView; @property (nonatomic, weak) IBOutlet UIView *redView;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//move the green view zPosition nearer to the cameraself.greenView.layer.zPosition = 1.0f; } @end

3.4 Hit Testing

第一章“圖層樹”證實了最好使用圖層相關視圖,而不是創建獨立的圖層關系。其中一個原因就是要處理額外復雜的觸摸事件。

CALayer并不關心任何響應鏈事件,所以不能直接處理觸摸事件或者手勢。但是它有一系列的方法幫你處理事件:-containsPoint:和-hitTest:。

-containsPoint:接受一個在本圖層坐標系下的CGPoint,如果這個點在圖層frame范圍內就返回YES。如清單3.4所示第一章的項目的另一個合適的版本,也就是使用-containsPoint:方法來判斷到底是白色還是藍色的圖層被觸摸了 (圖3.10)。這需要把觸摸坐標轉換成每個圖層坐標系下的坐標,結果很不方便。

清單3.4 使用containsPoint判斷被點擊的圖層

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *layerView; @property (nonatomic, weak) CALayer *blueLayer;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//create sublayerself.blueLayer = [CALayer layer];self.blueLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);self.blueLayer.backgroundColor = [UIColor blueColor].CGColor;//add it to our view[self.layerView.layer addSublayer:self.blueLayer]; }- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {//get touch position relative to main viewCGPoint point = [[touches anyObject] locationInView:self.view];//convert point to the white layer's coordinatespoint = [self.layerView.layer convertPoint:point fromLayer:self.view.layer];//get layer using containsPoint:if ([self.layerView.layer containsPoint:point]) {//convert point to blueLayer’s coordinatespoint = [self.blueLayer convertPoint:point fromLayer:self.layerView.layer];if ([self.blueLayer containsPoint:point]) {[[[UIAlertView alloc] initWithTitle:@"Inside Blue Layer"message:nildelegate:nilcancelButtonTitle:@"OK"otherButtonTitles:nil] show];} else {[[[UIAlertView alloc] initWithTitle:@"Inside White Layer"message:nildelegate:nilcancelButtonTitle:@"OK"otherButtonTitles:nil] show];}} } @end

注意當調用圖層的-hitTest:方法時,測算的順序嚴格依賴于圖層樹當中的圖層順序(和UIView處理事件類似)。之前提到的zPosition屬性可以明顯改變屏幕上圖層的順序,但不能改變事件傳遞的順序。

這意味著如果改變了圖層的z軸順序,你會發現將不能夠檢測到最前方的視圖點擊事件,這是因為被另一個圖層遮蓋住了,雖然它的zPosition值較小,但是在圖層樹中的順序靠前。我們將在第五章詳細討論這個問題。

3.5 自動布局

你可能用過UIViewAutoresizingMask類型的一些常量,應用于當父視圖改變尺寸的時候,相應UIView的frame也跟著更新的場景(通常用于橫豎屏切換)。

在iOS6中,蘋果介紹了自動排版機制,它和自動調整不同,并且更加復雜。

在Mac OS平臺,CALayer有一個叫做layoutManager的屬性可以通過CALayoutManager協議和CAConstraintLayoutManager類來實現自動排版的機制。但由于某些原因,這在iOS上并不適用。

當使用視圖的時候,可以充分利用UIView類接口暴露出來的UIViewAutoresizingMask和NSLayoutConstraintAPI,但如果想隨意控制CALayer的布局,就需要手工操作。最簡單的方法就是使用CALayerDelegate如下函數:

- (void)layoutSublayersOfLayer:(CALayer *)layer;

當圖層的bounds發生改變,或者圖層的-setNeedsLayout方法被調用的時候,這個函數將會被執行。這使得你可以手動地重新擺放或者重新調整子圖層的大小,但是不能像UIView的autoresizingMask和constraints屬性做到自適應屏幕旋轉。

這也是為什么最好使用視圖而不是單獨的圖層來構建應用程序的另一個重要原因之一。

3.6 總結

本章涉及了CALayer的集合結構,包括它的frame,position和bounds,介紹了三維空間內圖層的概念,以及如何在獨立的圖層內響應事件,最后簡單說明了在iOS平臺,Core Animation對自動調整和自動布局支持的缺乏。

在第四章“視覺效果”當中,我們接著介紹一些圖層外表的特性。

四、視覺效果

本節轉載自ios核心動畫高級技巧

嗯,圓和橢圓還不錯,但如果是帶圓角的矩形呢?
我們現在能做到那樣了么?
史蒂芬·喬布斯

我們在第三章『圖層幾何學』中討論了圖層的frame,第二章『寄宿圖』則討論了圖層的寄宿圖。但是圖層不僅僅可以是圖片或是顏色的容器;還有一系列內建的特性使得創造美麗優雅的令人深刻的界面元素成為可能。在這一章,我們將會探索一些能夠通過使用CALayer屬性實現的視覺效果。

4.1 圓角

圓角矩形是iOS的一個標志性審美特性。這在iOS的每一個地方都得到了體現,不論是主屏幕圖標,還是警告彈框,甚至是文本框。按照這流行程度,你可能會認為一定有不借助Photoshop就能輕易創建圓角舉行的方法。恭喜你,猜對了。

CALayer有一個叫做conrnerRadius的屬性控制著圖層角的曲率。它是一個浮點數,默認為0(為0的時候就是直角),但是你可以把它設置成任意值。默認情況下,這個曲率值只影響背景顏色而不影響背景圖片或是子圖層。不過,如果把masksToBounds設置成YES的話,圖層里面的所有東西都會被截取。

我們可以通過一個簡單的項目來演示這個效果。在Interface Builder中,我們放置一些視圖,他們有一些子視圖。而且這些子視圖有一些超出了邊界(如圖4.1)。你可能無法看到他們超出了邊界,因為在編輯界面的時候,超出的部分總是被Interface Builder裁切掉了。不過,你相信我就好了 :)

然后在代碼中,我們設置角的半徑為20個點,并裁剪掉第一個視圖的超出部分(見清單4.1)。技術上來說,這些屬性都可以在Interface Builder的探測板中分別通過『用戶定義運行時屬性』和勾選『裁剪子視圖』(Clip Subviews)選擇框來直接設置屬性的值。不過,在這個示例中,代碼能夠表示得更清楚。圖4.2是運行代碼的結果

清單4.1 設置cornerRadius和masksToBounds

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *layerView1; @property (nonatomic, weak) IBOutlet UIView *layerView2;@end@implementation ViewController - (void)viewDidLoad {[super viewDidLoad];//set the corner radius on our layersself.layerView1.layer.cornerRadius = 20.0f;self.layerView2.layer.cornerRadius = 20.0f;//enable clipping on the second layerself.layerView2.layer.masksToBounds = YES; } @end

右圖中,紅色的子視圖沿角半徑被裁剪了

如你所見,右邊的子視圖沿邊界被裁剪了。

單獨控制每個層的圓角曲率也不是不可能的。如果想創建有些圓角有些直角的圖層或視圖時,你可能需要一些不同的方法。比如使用一個圖層蒙板(本章稍后會講到)或者是CAShapeLayer(見第六章『專用圖層』)。

4.2 圖層邊框

CALayer另外兩個非常有用屬性就是borderWidth和borderColor。二者共同定義了圖層邊的繪制樣式。這條線(也被稱作stroke)沿著圖層的bounds繪制,同時也包含圖層的角。

borderWidth是以點為單位的定義邊框粗細的浮點數,默認為0.borderColor定義了邊框的顏色,默認為黑色。

borderColor是CGColorRef類型,而不是UIColor,所以它不是Cocoa的內置對象。不過呢,你肯定也清楚圖層引用了borderColor,雖然屬性聲明并不能證明這一點。CGColorRef在引用/釋放時候的行為表現得與NSObject極其相似。但是Objective-C語法并不支持這一做法,所以CGColorRef屬性即便是強引用也只能通過assign關鍵字來聲明。

邊框是繪制在圖層邊界里面的,而且在所有子內容之前,也在子圖層之前。如果我們在之前的示例中(清單4.2)加入圖層的邊框,你就能看到到底是怎么一回事了(如圖4.3).

清單4.2 加上邊框

@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//set the corner radius on our layersself.layerView1.layer.cornerRadius = 20.0f;self.layerView2.layer.cornerRadius = 20.0f;//add a border to our layersself.layerView1.layer.borderWidth = 5.0f;self.layerView2.layer.borderWidth = 5.0f;//enable clipping on the second layerself.layerView2.layer.masksToBounds = YES; } @end

仔細觀察會發現邊框并不會把寄宿圖或子圖層的形狀計算進來,如果圖層的子圖層超過了邊界,或者是寄宿圖在透明區域有一個透明蒙板,邊框仍然會沿著圖層的邊界繪制出來(如圖4.4).

4.3 陰影

iOS的另一個常見特性呢,就是陰影。陰影往往可以達到圖層深度暗示的效果。也能夠用來強調正在顯示的圖層和優先級(比如說一個在其他視圖之前的彈出框),不過有時候他們只是單純的裝飾目的。

給shadowOpacity屬性一個大于默認值(也就是0)的值,陰影就可以顯示在任意圖層之下。shadowOpacity是一個必須在0.0(不可見)和1.0(完全不透明)之間的浮點數。如果設置為1.0,將會顯示一個有輕微模糊的黑色陰影稍微在圖層之上。若要改動陰影的表現,你可以使用CALayer的另外三個屬性:shadowColor,shadowOffset和shadowRadius。

顯而易見,shadowColor屬性控制著陰影的顏色,和borderColor和backgroundColor一樣,它的類型也是CGColorRef。陰影默認是黑色,大多數時候你需要的陰影也是黑色的(其他顏色的陰影看起來是不是有一點點奇怪。。)。

shadowOffset屬性控制著陰影的方向和距離。它是一個CGSize的值,寬度控制這陰影橫向的位移,高度控制著縱向的位移。shadowOffset的默認值是 {0, -3},意即陰影相對于Y軸有3個點的向上位移。

為什么要默認向上的陰影呢?盡管Core Animation是從圖層套裝演變而來(可以認為是為iOS創建的私有動畫框架),但是呢,它卻是在Mac OS上面世的,前面有提到,二者的Y軸是顛倒的。這就導致了默認的3個點位移的陰影是向上的。在Mac上,shadowOffset的默認值是陰影向下的,這樣你就能理解為什么iOS上的陰影方向是向上的了(如圖4.5).

蘋果更傾向于用戶界面的陰影應該是垂直向下的,所以在iOS把陰影寬度設為0,然后高度設為一個正值不失為一個做法。

shadowRadius屬性控制著陰影的模糊度,當它的值是0的時候,陰影就和視圖一樣有一個非常確定的邊界線。當值越來越大的時候,邊界線看上去就會越來越模糊和自然。蘋果自家的應用設計更偏向于自然的陰影,所以一個非零值再合適不過了。

通常來講,如果你想讓視圖或控件非常醒目獨立于背景之外(比如彈出框遮罩層),你就應該給shadowRadius設置一個稍大的值。陰影越模糊,圖層的深度看上去就會更明顯(如圖4.6).

陰影裁剪

和圖層邊框不同,圖層的陰影繼承自內容的外形,而不是根據邊界和角半徑來確定。為了計算出陰影的形狀,Core Animation會將寄宿圖(包括子視圖,如果有的話)考慮在內,然后通過這些來完美搭配圖層形狀從而創建一個陰影(見圖4.7)。

當陰影和裁剪扯上關系的時候就有一個頭疼的限制:陰影通常就是在Layer的邊界之外,如果你開啟了masksToBounds屬性,所有從圖層中突出來的內容都會被才剪掉。如果我們在我們之前的邊框示例項目中增加圖層的陰影屬性時,你就會發現問題所在(見圖4.8).

從技術角度來說,這個結果是可以是可以理解的,但確實又不是我們想要的效果。如果你想沿著內容裁切,你需要用到兩個圖層:一個只畫陰影的空的外圖層,和一個用masksToBounds裁剪內容的內圖層。

如果我們把之前項目的右邊用單獨的視圖把裁剪的視圖包起來,我們就可以解決這個問題(如圖4.9).

我們只把陰影用在最外層的視圖上,內層視圖進行裁剪。清單4.3是代碼實現,圖4.10是運行結果。

清單4.3 用一個額外的視圖來解決陰影裁切的問題

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *layerView1; @property (nonatomic, weak) IBOutlet UIView *layerView2; @property (nonatomic, weak) IBOutlet UIView *shadowView;@end@implementation ViewController  - (void)viewDidLoad {[super viewDidLoad];//set the corner radius on our layersself.layerView1.layer.cornerRadius = 20.0f;self.layerView2.layer.cornerRadius = 20.0f;//add a border to our layersself.layerView1.layer.borderWidth = 5.0f;self.layerView2.layer.borderWidth = 5.0f;//add a shadow to layerView1self.layerView1.layer.shadowOpacity = 0.5f;self.layerView1.layer.shadowOffset = CGSizeMake(0.0f, 5.0f);self.layerView1.layer.shadowRadius = 5.0f;//add same shadow to shadowView (not layerView2)self.shadowView.layer.shadowOpacity = 0.5f;self.shadowView.layer.shadowOffset = CGSizeMake(0.0f, 5.0f);self.shadowView.layer.shadowRadius = 5.0f;//enable clipping on the second layerself.layerView2.layer.masksToBounds = YES; } @end

shadowPath屬性

我們已經知道圖層陰影并不總是方的,而是從圖層內容的形狀繼承而來。這看上去不錯,但是實時計算陰影也是一個非常消耗資源的,尤其是圖層有多個子圖層,每個圖層還有一個有透明效果的寄宿圖的時候。

如果你事先知道你的陰影形狀會是什么樣子的,你可以通過指定一個shadowPath來提高性能。shadowPath是一個CGPathRef類型(一個指向CGPath的指針)。CGPath是一個Core Graphics對象,用來指定任意的一個矢量圖形。我們可以通過這個屬性單獨于圖層形狀之外指定陰影的形狀。

圖4.11 展示了同一寄宿圖的不同陰影設定。如你所見,我們使用的圖形很簡單,但是它的陰影可以是你想要的任何形狀。清單4.4是代碼實現。

清單4.4 創建簡單的陰影形狀

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *layerView1; @property (nonatomic, weak) IBOutlet UIView *layerView2; @end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//enable layer shadowsself.layerView1.layer.shadowOpacity = 0.5f;self.layerView2.layer.shadowOpacity = 0.5f;//create a square shadowCGMutablePathRef squarePath = CGPathCreateMutable();CGPathAddRect(squarePath, NULL, self.layerView1.bounds);self.layerView1.layer.shadowPath = squarePath; CGPathRelease(squarePath);//create a circular shadowCGMutablePathRef circlePath = CGPathCreateMutable();CGPathAddEllipseInRect(circlePath, NULL, self.layerView2.bounds);self.layerView2.layer.shadowPath = circlePath; CGPathRelease(circlePath); } @end

如果是一個矩形或者是圓,用CGPath會相當簡單明了。但是如果是更加復雜一點的圖形,UIBezierPath類會更合適,它是一個由UIKit提供的在CGPath基礎上的Objective-C包裝類。

圖4.6 大一些的陰影位移和角半徑會增加圖層的深度即視感

4.4 圖層蒙板

通過masksToBounds屬性,我們可以沿邊界裁剪圖形;通過cornerRadius屬性,我們還可以設定一個圓角。但是有時候你希望展現的內容不是在一個矩形或圓角矩形。比如,你想展示一個有星形框架的圖片,又或者想讓一些古卷文字慢慢漸變成背景色,而不是一個突兀的邊界。

使用一個32位有alpha通道的png圖片通常是創建一個無矩形視圖最方便的方法,你可以給它指定一個透明蒙板來實現。但是這個方法不能讓你以編碼的方式動態地生成蒙板,也不能讓子圖層或子視圖裁剪成同樣的形狀。

CALayer有一個屬性叫做mask可以解決這個問題。這個屬性本身就是個CALayer類型,有和其他圖層一樣的繪制和布局屬性。它類似于一個子圖層,相對于父圖層(即擁有該屬性的圖層)布局,但是它卻不是一個普通的子圖層。不同于那些繪制在父圖層中的子圖層,mask圖層定義了父圖層的部分可見區域。

mask圖層的Color屬性是無關緊要的,真正重要的是圖層的輪廓。mask屬性就像是一個餅干切割機,mask圖層實心的部分會被保留下來,其他的則會被拋棄。(如圖4.12)

如果mask圖層比父圖層要小,只有在mask圖層里面的內容才是它關心的,除此以外的一切都會被隱藏起來。

我們將代碼演示一下這個過程,創建一個簡單的項目,通過圖層的mask屬性來作用于圖片之上。為了簡便一些,我們用Interface Builder來創建一個包含UIImageView的圖片圖層。這樣我們就只要代碼實現蒙板圖層了。清單4.5是最終的代碼,圖4.13是運行后的結果。

清單4.5 應用蒙板圖層

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIImageView *imageView; @end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//create mask layerCALayer *maskLayer = [CALayer layer];maskLayer.frame = self.layerView.bounds;UIImage *maskImage = [UIImage imageNamed:@"Cone.png"];maskLayer.contents = (__bridge id)maskImage.CGImage;//apply mask to image layerself.imageView.layer.mask = maskLayer; } @end

CALayer蒙板圖層真正厲害的地方在于蒙板圖不局限于靜態圖。任何有圖層構成的都可以作為mask屬性,這意味著你的蒙板可以通過代碼甚至是動畫實時生成。

4.5 拉伸過濾

最后我們再來談談minificationFilter和magnificationFilter屬性。總得來講,當我們視圖顯示一個圖片的時候,都應該正確地顯示這個圖片(意即:以正確的比例和正確的1:1像素顯示在屏幕上)。原因如下:
- 能夠顯示最好的畫質,像素既沒有被壓縮也沒有被拉伸。
- 能更好的使用內存,因為這就是所有你要存儲的東西。
- 最好的性能表現,CPU不需要為此額外的計算。

不過有時候,顯示一個非真實大小的圖片確實是我們需要的效果。比如說一個頭像或是圖片的縮略圖,再比如說一個可以被拖拽和伸縮的大圖。這些情況下,為同一圖片的不同大小存儲不同的圖片顯得又不切實際。

當圖片需要顯示不同的大小的時候,有一種叫做拉伸過濾的算法就起到作用了。它作用于原圖的像素上并根據需要生成新的像素顯示在屏幕上。

事實上,重繪圖片大小也沒有一個統一的通用算法。這取決于需要拉伸的內容,放大或是縮小的需求等這些因素。CALayer為此提供了三種拉伸過濾方法,他們是:
- kCAFilterLinear
- kCAFilterNearest
- kCAFilterTrilinear

minification(縮小圖片)和magnification(放大圖片)默認的過濾器都是kCAFilterLinear,這個過濾器采用雙線性濾波算法,它在大多數情況下都表現良好。雙線性濾波算法通過對多個像素取樣最終生成新的值,得到一個平滑的表現不錯的拉伸。但是當放大倍數比較大的時候圖片就模糊不清了。

kCAFilterTrilinear和kCAFilterLinear非常相似,大部分情況下二者都看不出來有什么差別。但是,較雙線性濾波算法而言,三線性濾波算法存儲了多個大小情況下的圖片(也叫多重貼圖),并三維取樣,同時結合大圖和小圖的存儲進而得到最后的結果。

這個方法的好處在于算法能夠從一系列已經接近于最終大小的圖片中得到想要的結果,也就是說不要對很多像素同步取樣。這不僅提高了性能,也避免了小概率因舍入錯誤引起的取樣失靈的問題

kCAFilterNearest是一種比較武斷的方法。從名字不難看出,這個算法(也叫最近過濾)就是取樣最近的單像素點而不管其他的顏色。這樣做非常快,也不會使圖片模糊。但是,最明顯的效果就是,會使得壓縮圖片更糟,圖片放大之后也顯得塊狀或是馬賽克嚴重。

總的來說,對于比較小的圖或者是差異特別明顯,極少斜線的大圖,最近過濾算法會保留這種差異明顯的特質以呈現更好的結果。但是對于大多數的圖尤其是有很多斜線或是曲線輪廓的圖片來說,最近過濾算法會導致更差的結果。換句話說,線性過濾保留了形狀,最近過濾則保留了像素的差異。

讓我們來實驗一下。我們對第三章的時鐘項目改動一下,用LCD風格的數字方式顯示。我們用簡單的像素字體(一種用像素構成字符的字體,而非矢量圖形)創造數字顯示方式,用圖片存儲起來,而且用第二章介紹過的拼合技術來顯示(如圖4.16)。

我們在Interface Builder中放置了六個視圖,小時、分鐘、秒鐘各兩個,圖4.17顯示了這六個視圖是如何在Interface Builder中放置的。如果每個都用一個淡出的outlets對象就會顯得太多了,所以我們就用了一個IBOutletCollection對象把他們和控制器聯系起來,這樣我們就可以以數組的方式訪問視圖了。清單4.6是代碼實現。

清單4.6 顯示一個LCD風格的時鐘

@interface ViewController ()@property (nonatomic, strong) IBOutletCollection(UIView) NSArray *digitViews; @property (nonatomic, weak) NSTimer *timer;  @end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad]; //get spritesheet imageUIImage *digits = [UIImage imageNamed:@"Digits.png"];//set up digit viewsfor (UIView *view in self.digitViews) {//set contentsview.layer.contents = (__bridge id)digits.CGImage;view.layer.contentsRect = CGRectMake(0, 0, 0.1, 1.0);view.layer.contentsGravity = kCAGravityResizeAspect;}//start timerself.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES];//set initial clock time[self tick]; }- (void)setDigit:(NSInteger)digit forView:(UIView *)view {//adjust contentsRect to select correct digitview.layer.contentsRect = CGRectMake(digit * 0.1, 0, 0.1, 1.0); }- (void)tick {//convert time to hours, minutes and secondsNSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar];NSUInteger units = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;NSDateComponents *components = [calendar components:units fromDate:[NSDate date]];//set hours[self setDigit:components.hour / 10 forView:self.digitViews[0]];[self setDigit:components.hour % 10 forView:self.digitViews[1]];//set minutes[self setDigit:components.minute / 10 forView:self.digitViews[2]];[self setDigit:components.minute % 10 forView:self.digitViews[3]];//set seconds[self setDigit:components.second / 10 forView:self.digitViews[4]];[self setDigit:components.second % 10 forView:self.digitViews[5]]; } @end

如圖4.18,這樣做的確起了效果,但是圖片看起來模糊了。看起來默認的kCAFilterLinear選項讓我們失望了。

為了能像圖4.19中那樣,我們需要在for循環中加入如下代碼:
view.layer.magnificationFilter = kCAFilterNearest;

4.6 組透明

UIView有一個叫做alpha的屬性來確定視圖的透明度。CALayer有一個等同的屬性叫做opacity,這兩個屬性都是影響子層級的。也就是說,如果你給一個圖層設置了opacity屬性,那它的子圖層都會受此影響。
iOS常見的做法是把一個控件的alpha值設置為0.5(50%)以使其看上去呈現為不可用狀態。對于獨立的視圖來說還不錯,但是當一個控件有子視圖的時候就有點奇怪了,圖4.20展示了一個內嵌了UILabel的自定義UIButton;左邊是一個不透明的按鈕,右邊是50%透明度的相同按鈕。我們可以注意到,里面的標簽的輪廓跟按鈕的背景很不搭調。

這是由透明度的混合疊加造成的,當你顯示一個50%透明度的圖層時,圖層的每個像素都會一半顯示自己的顏色,另一半顯示圖層下面的顏色。這是正常的透明度的表現。但是如果圖層包含一個同樣顯示50%透明的子圖層時,你所看到的視圖,50%來自子視圖,25%來了圖層本身的顏色,另外的25%則來自背景色。

在我們的示例中,按鈕和表情都是白色背景。雖然他們都是50%的可見度,但是合起來的可見度是75%,所以標簽所在的區域看上去就沒有周圍的部分那么透明。所以看上去子視圖就高亮了,使得這個顯示效果都糟透了。

理想狀況下,當你設置了一個圖層的透明度,你希望它包含的整個圖層樹像一個整體一樣的透明效果。你可以通過設置Info.plist文件中的UIViewGroupOpacity為YES來達到這個效果,但是這個設置會影響到這個應用,整個app可能會受到不良影響。如果UIViewGroupOpacity并未設置,iOS 6和以前的版本會默認為NO(也許以后的版本會有一些改變)。

另一個方法就是,你可以設置CALayer的一個叫做shouldRasterize屬性(見清單4.7)來實現組透明的效果,如果它被設置為YES,在應用透明度之前,圖層及其子圖層都會被整合成一個整體的圖片,這樣就沒有透明度混合的問題了(如圖4.21)。

為了啟用shouldRasterize屬性,我們設置了圖層的rasterizationScale屬性。默認情況下,所有圖層拉伸都是1.0, 所以如果你使用了shouldRasterize屬性,你就要確保你設置了rasterizationScale屬性去匹配屏幕,以防止出現Retina屏幕像素化的問題。

當shouldRasterize和UIViewGroupOpacity一起的時候,性能問題就出現了(我們在第12章『速度』和第15章『圖層性能』將做出介紹),但是性能碰撞都本地化了(譯者注:這句話需要再翻譯)。

清單4.7 使用shouldRasterize屬性解決組透明問題

@interface ViewController () @property (nonatomic, weak) IBOutlet UIView *containerView; @end@implementation ViewController- (UIButton *)customButton {//create buttonCGRect frame = CGRectMake(0, 0, 150, 50);UIButton *button = [[UIButton alloc] initWithFrame:frame];button.backgroundColor = [UIColor whiteColor];button.layer.cornerRadius = 10;//add labelframe = CGRectMake(20, 10, 110, 30);UILabel *label = [[UILabel alloc] initWithFrame:frame];label.text = @"Hello World";label.textAlignment = NSTextAlignmentCenter;[button addSubview:label];return button; }- (void)viewDidLoad {[super viewDidLoad];//create opaque buttonUIButton *button1 = [self customButton];button1.center = CGPointMake(50, 150);[self.containerView addSubview:button1];//create translucent buttonUIButton *button2 = [self customButton];button2.center = CGPointMake(250, 150);button2.alpha = 0.5;[self.containerView addSubview:button2];//enable rasterization for the translucent buttonbutton2.layer.shouldRasterize = YES;button2.layer.rasterizationScale = [UIScreen mainScreen].scale; } @end

4.7 總結

這一章介紹了一些可以通過代碼應用到圖層上的視覺效果,比如圓角,陰影和蒙板。我們也了解了拉伸過濾器和組透明。

在第五章,『變換』中,我們將會研究圖層變化和3D轉換

五、變換

本節轉載自ios核心動畫高級技巧

很不幸,沒人能告訴你母體是什么,你只能自己體會 – 駭客帝國

在第四章“可視效果”中,我們研究了一些增強圖層和它的內容顯示效果的一些技術,在這一章中,我們將要研究可以用來對圖層旋轉,擺放或者扭曲的CGAffineTransform,以及可以將扁平物體轉換成三維空間對象的CATransform3D(而不是僅僅對圓角矩形添加下沉陰影)。

5.1 仿射變換

在第三章“圖層幾何學”中,我們使用了UIView的transform屬性旋轉了鐘的指針,但并沒有解釋背后運作的原理,實際上UIView的transform屬性是一個CGAffineTransform類型,用于在二維空間做旋轉,縮放和平移。CGAffineTransform是一個可以和二維空間向量(例如CGPoint)做乘法的3X2的矩陣(見圖5.1)。

用CGPoint的每一列和CGAffineTransform矩陣的每一行對應元素相乘再求和,就形成了一個新的CGPoint類型的結果。要解釋一下圖中顯示的灰色元素,為了能讓矩陣做乘法,左邊矩陣的列數一定要和右邊矩陣的行數個數相同,所以要給矩陣填充一些標志值,使得既可以讓矩陣做乘法,又不改變運算結果,并且沒必要存儲這些添加的值,因為它們的值不會發生變化,但是要用來做運算。

因此,通常會用3×3(而不是2×3)的矩陣來做二維變換,你可能會見到3行2列格式的矩陣,這是所謂的以列為主的格式,圖5.1所示的是以行為主的格式,只要能保持一致,用哪種格式都無所謂。

當對圖層應用變換矩陣,圖層矩形內的每一個點都被相應地做變換,從而形成一個新的四邊形的形狀。CGAffineTransform中的“仿射”的意思是無論變換矩陣用什么值,圖層中平行的兩條線在變換之后任然保持平行,CGAffineTransform可以做出任意符合上述標注的變換,圖5.2顯示了一些仿射的和非仿射的變換:

創建一個CGAffineTransform

對矩陣數學做一個全面的闡述就超出本書的討論范圍了,不過如果你對矩陣完全不熟悉的話,矩陣變換可能會使你感到畏懼。幸運的是,Core Graphics提供了一系列函數,對完全沒有數學基礎的開發者也能夠簡單地做一些變換。如下幾個函數都創建了一個CGAffineTransform實例:

CGAffineTransformMakeRotation(CGFloat angle) CGAffineTransformMakeScale(CGFloat sx, CGFloat sy) CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)

旋轉和縮放變換都可以很好解釋–分別旋轉或者縮放一個向量的值。平移變換是指每個點都移動了向量指定的x或者y值–所以如果向量代表了一個點,那它就平移了這個點的距離。
我們用一個很簡單的項目來做個demo,把一個原始視圖旋轉45度角度(圖5.3)

UIView可以通過設置transform屬性做變換,但實際上它只是封裝了內部圖層的變換。
CALayer同樣也有一個transform屬性,但它的類型是CATransform3D,而不是CGAffineTransform,本章后續將會詳細解釋。CALayer對應于UIView的transform屬性叫做affineTransform,清單5.1的例子就是使用affineTransform對圖層做了45度順時針旋轉。
清單5.1 使用affineTransform對圖層旋轉45度

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *layerView;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//rotate the layer 45 degreesCGAffineTransform transform = CGAffineTransformMakeRotation(M_PI_4);self.layerView.layer.affineTransform = transform; }@end

注意我們使用的旋轉常量是M_PI_4,而不是你想象的45,因為iOS的變換函數使用弧度而不是角度作為單位。弧度用數學常量pi的倍數表示,一個pi代表180度,所以四分之一的pi就是45度。

C的數學函數庫(iOS會自動引入)提供了pi的一些簡便的換算,M_PI_4于是就是pi的四分之一,如果對換算不太清楚的話,可以用如下的宏做換算:
#define RADIANS_TO_DEGREES(x) ((x)/M_PI*180.0)

混合變換

Core Graphics提供了一系列的函數可以在一個變換的基礎上做更深層次的變換,如果做一個既要縮放又要旋轉的變換,這就會非常有用了。例如下面幾個函數:

CGAffineTransformRotate(CGAffineTransform t, CGFloat angle) CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy) CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty)

當操縱一個變換的時候,初始生成一個什么都不做的變換很重要–也就是創建一個CGAffineTransform類型的空值,矩陣論中稱作單位矩陣,Core Graphics同樣也提供了一個方便的常量:

CGAffineTransformIdentity

最后,如果需要混合兩個已經存在的變換矩陣,就可以使用如下方法,在兩個變換的基礎上創建一個新的變換:

CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2);

我們來用這些函數組合一個更加復雜的變換,先縮小50%,再旋轉30度,最后向右移動200個像素(清單5.2)。圖5.4顯示了圖層變換最后的結果。

清單5.2 使用若干方法創建一個復合變換

- (void)viewDidLoad {[super viewDidLoad];//create a new transformCGAffineTransform transform = CGAffineTransformIdentity; //scale by 50%transform = CGAffineTransformScale(transform, 0.5, 0.5);//rotate by 30 degreestransform = CGAffineTransformRotate(transform, M_PI / 180.0 * 30.0);//translate by 200 pointstransform = CGAffineTransformTranslate(transform, 200, 0);//apply transform to layerself.layerView.layer.affineTransform = transform; }

圖5.4中有些需要注意的地方:圖片向右邊發生了平移,但并沒有指定距離那么遠(200像素),另外它還有點向下發生了平移。原因在于當你按順序做了變換,上一個變換的結果將會影響之后的變換,所以200像素的向右平移同樣也被旋轉了30度,縮小了50%,所以它實際上是斜向移動了100像素。

這意味著變換的順序會影響最終的結果,也就是說旋轉之后的平移和平移之后的旋轉結果可能不同。

#define DEGREES_TO_RADIANS(x) ((x)/180.0*M_PI)

5.2 3D變換

CG的前綴告訴我們,CGAffineTransform類型屬于Core Graphics框架,Core Graphics實際上是一個嚴格意義上的2D繪圖API,并且CGAffineTransform僅僅對2D變換有效。

在第三章中,我們提到了zPosition屬性,可以用來讓圖層靠近或者遠離相機(用戶視角),transform屬性(CATransform3D類型)可以真正做到這點,即讓圖層在3D空間內移動或者旋轉。

和CGAffineTransform類似,CATransform3D也是一個矩陣,但是和2x3的矩陣不同,CATransform3D是一個可以在3維空間內做變換的4x4的矩陣(圖5.6)。

和CGAffineTransform矩陣類似,Core Animation提供了一系列的方法用來創建和組合CATransform3D類型的矩陣,和Core Graphics的函數類似,但是3D的平移和旋轉多處了一個z參數,并且旋轉函數除了angle之外多出了x,y,z三個參數,分別決定了每個坐標軸方向上的旋轉:

CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z) CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz) CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)

你應該對X軸和Y軸比較熟悉了,分別以右和下為正方向(回憶第三章,這是iOS上的標準結構,在Mac OS,Y軸朝上為正方向),Z軸和這兩個軸分別垂直,指向視角外為正方向(圖5.7)。

由圖所見,繞Z軸的旋轉等同于之前二維空間的仿射旋轉,但是繞X軸和Y軸的旋轉就突破了屏幕的二維空間,并且在用戶視角看來發生了傾斜。

舉個例子:清單5.4的代碼使用了CATransform3DMakeRotation對視圖內的圖層繞Y軸做了45度角的旋轉,我們可以把視圖向右傾斜,這樣會看得更清晰。

結果見圖5.8,但并不像我們期待的那樣。
清單5.4 繞Y軸旋轉圖層

@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//rotate the layer 45 degrees along the Y axisCATransform3D transform = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);self.layerView.layer.transform = transform; }@end

看起來圖層并沒有被旋轉,而是僅僅在水平方向上的一個壓縮,是哪里出了問題呢?
其實完全沒錯,視圖看起來更窄實際上是因為我們在用一個斜向的視角看它,而不是透視。

透視投影

在真實世界中,當物體遠離我們的時候,由于視角的原因看起來會變小,理論上說遠離我們的視圖的邊要比靠近視角的邊跟短,但實際上并沒有發生,而我們當前的視角是等距離的,也就是在3D變換中任然保持平行,和之前提到的仿射變換類似。

在等距投影中,遠處的物體和近處的物體保持同樣的縮放比例,這種投影也有它自己的用處(例如建筑繪圖,顛倒,和偽3D視頻),但當前我們并不需要。

為了做一些修正,我們需要引入投影變換(又稱作z變換)來對除了旋轉之外的變換矩陣做一些修改,Core Animation并沒有給我們提供設置透視變換的函數,因此我們需要手動修改矩陣值,幸運的是,很簡單:

CATransform3D的透視效果通過一個矩陣中一個很簡單的元素來控制:m34。m34(圖5.9)用于按比例縮放X和Y的值來計算到底要離視角多遠。

m34的默認值是0,我們可以通過設置m34為-1.0 / d來應用透視效果,d代表了想象中視角相機和屏幕之間的距離,以像素為單位,那應該如何計算這個距離呢?實際上并不需要,大概估算一個就好了。

因為視角相機實際上并不存在,所以可以根據屏幕上的顯示效果自由決定它的防止的位置。通常500-1000就已經很好了,但對于特定的圖層有時候更小后者更大的值會看起來更舒服,減少距離的值會增強透視效果,所以一個非常微小的值會讓它看起來更加失真,然而一個非常大的值會讓它基本失去透視效果,對視圖應用透視的代碼見清單5.5,結果見圖5.10。

清單5.5 對變換應用透視效果

@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//create a new transformCATransform3D transform = CATransform3DIdentity;//apply perspectivetransform.m34 = - 1.0 / 500.0;//rotate by 45 degrees along the Y axistransform = CATransform3DRotate(transform, M_PI_4, 0, 1, 0);//apply to layerself.layerView.layer.transform = transform; } @end

滅點

當在透視角度繪圖的時候,遠離相機視角的物體將會變小變遠,當遠離到一個極限距離,它們可能就縮成了一個點,于是所有的物體最后都匯聚消失在同一個點。
在現實中,這個點通常是視圖的中心(圖5.11),于是為了在應用中創建擬真效果的透視,這個點應該聚在屏幕中點,或者至少是包含所有3D對象的視圖中點。

Core Animation定義了這個點位于變換圖層的anchorPoint(通常位于圖層中心,但也有例外,見第三章)。這就是說,當圖層發生變換時,這個點永遠位于圖層變換之前anchorPoint的位置。

當改變一個圖層的position,你也改變了它的滅點,做3D變換的時候要時刻記住這一點,當你視圖通過調整m34來讓它更加有3D效果,應該首先把它放置于屏幕中央,然后通過平移來把它移動到指定位置(而不是直接改變它的position),這樣所有的3D圖層都共享一個滅點。

sublayerTransform屬性

如果有多個視圖或者圖層,每個都做3D變換,那就需要分別設置相同的m34值,并且確保在變換之前都在屏幕中央共享同一個position,如果用一個函數封裝這些操作的確會更加方便,但仍然有限制(例如,你不能在Interface Builder中擺放視圖),這里有一個更好的方法。

CALayer有一個屬性叫做sublayerTransform。它也是CATransform3D類型,但和對一個圖層的變換不同,它影響到所有的子圖層。這意味著你可以一次性對包含這些圖層的容器做變換,于是所有的子圖層都自動繼承了這個變換方法。

相較而言,通過在一個地方設置透視變換會很方便,同時它會帶來另一個顯著的優勢:滅點被設置在容器圖層的中點,從而不需要再對子圖層分別設置了。這意味著你可以隨意使用position和frame來放置子圖層,而不需要把它們放置在屏幕中點,然后為了保證統一的滅點用變換來做平移。

我們來用一個demo舉例說明。這里用Interface Builder并排放置兩個視圖(圖5.12),然后通過設置它們容器視圖的透視變換,我們可以保證它們有相同的透視和滅點,代碼見清單5.6,結果見圖5.13。

清單5.6 應用sublayerTransform

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *containerView; @property (nonatomic, weak) IBOutlet UIView *layerView1; @property (nonatomic, weak) IBOutlet UIView *layerView2;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//apply perspective transform to containerCATransform3D perspective = CATransform3DIdentity;perspective.m34 = - 1.0 / 500.0;self.containerView.layer.sublayerTransform = perspective;//rotate layerView1 by 45 degrees along the Y axisCATransform3D transform1 = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);self.layerView1.layer.transform = transform1;//rotate layerView2 by 45 degrees along the Y axisCATransform3D transform2 = CATransform3DMakeRotation(-M_PI_4, 0, 1, 0);self.layerView2.layer.transform = transform2; } @end

背面

我們既然可以在3D場景下旋轉圖層,那么也可以從背面去觀察它。如果我們在清單5.4中把角度修改為M_PI(180度)而不是當前的M_PI_4(45度),那么將會把圖層完全旋轉一個半圈,于是完全背對了相機視角。

那么從背部看圖層是什么樣的呢,見圖5.14

如你所見,圖層是雙面繪制的,反面顯示的是正面的一個鏡像圖片。

但這并不是一個很好的特性,因為如果圖層包含文本或者其他控件,那用戶看到這些內容的鏡像圖片當然會感到困惑。另外也有可能造成資源的浪費:想象用這些圖層形成一個不透明的固態立方體,既然永遠都看不見這些圖層的背面,那為什么浪費GPU來繪制它們呢?

CALayer有一個叫做doubleSided的屬性來控制圖層的背面是否要被繪制。這是一個BOOL類型,默認為YES,如果設置為NO,那么當圖層正面從相機視角消失的時候,它將不會被繪制。

扁平化圖層

如果對包含已經做過變換的圖層的圖層做反方向的變換將會發什么什么呢?是不是有點困惑?見圖5.15

注意做了-45度旋轉的內部圖層是怎樣抵消旋轉45度的圖層,從而恢復正常狀態的。
如果內部圖層相對外部圖層做了相反的變換(這里是繞Z軸的旋轉),那么按照邏輯這兩個變換將被相互抵消。

驗證一下,相應代碼見清單5.7,結果見5.16
清單5.7 繞Z軸做相反的旋轉變換

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *outerView; @property (nonatomic, weak) IBOutlet UIView *innerView;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//rotate the outer layer 45 degreesCATransform3D outer = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);self.outerView.layer.transform = outer;//rotate the inner layer -45 degreesCATransform3D inner = CATransform3DMakeRotation(-M_PI_4, 0, 0, 1);self.innerView.layer.transform = inner; } @end

運行結果和我們預期的一致。現在在3D情況下再試一次。修改代碼,讓內外兩個視圖繞Y軸旋轉而不是Z軸,再加上透視效果,以便我們觀察。注意不能用sublayerTransform屬性,因為內部的圖層并不直接是容器圖層的子圖層,所以這里分別對圖層設置透視變換(清單5.8)。

清單5.8 繞Y軸相反的旋轉變換

- (void)viewDidLoad {[super viewDidLoad];//rotate the outer layer 45 degreesCATransform3D outer = CATransform3DIdentity;outer.m34 = -1.0 / 500.0;outer = CATransform3DRotate(outer, M_PI_4, 0, 1, 0);self.outerView.layer.transform = outer;//rotate the inner layer -45 degreesCATransform3D inner = CATransform3DIdentity;inner.m34 = -1.0 / 500.0;inner = CATransform3DRotate(inner, -M_PI_4, 0, 1, 0);self.innerView.layer.transform = inner; }

預期的效果應該如圖5.17所示。

但其實這并不是我們所看到的,相反,我們看到的結果如圖5.18所示。發什么了什么呢?內部的圖層仍然向左側旋轉,并且發生了扭曲,但按道理說它應該保持正面朝上,并且顯示正常的方塊。+

這是由于盡管Core Animation圖層存在于3D空間之內,但它們并不都存在同一個3D空間。每個圖層的3D場景其實是扁平化的,當你從正面觀察一個圖層,看到的實際上由子圖層創建的想象出來的3D場景,但當你傾斜這個圖層,你會發現實際上這個3D場景僅僅是被繪制在圖層的表面。

類似的,當你在玩一個3D游戲,實際上僅僅是把屏幕做了一次傾斜,或許在游戲中可以看見有一面墻在你面前,但是傾斜屏幕并不能夠看見墻里面的東西。所有場景里面繪制的東西并不會隨著你觀察它的角度改變而發生變化;圖層也是同樣的道理。

這使得用Core Animation創建非常復雜的3D場景變得十分困難。你不能夠使用圖層樹去創建一個3D結構的層級關系–在相同場景下的任何3D表面必須和同樣的圖層保持一致,這是因為每個的父視圖都把它的子視圖扁平化了。

至少當你用正常的CALayer的時候是這樣,CALayer有一個叫做CATransformLayer的子類來解決這個問題。具體在第六章“特殊的圖層”中將會具體討論。

5.3 固體對象

現在你懂得了在3D空間的一些圖層布局的基礎,我們來試著創建一個固態的3D對象(實際上是一個技術上所謂的空洞對象,但它以固態呈現)。我們用六個獨立的視圖來構建一個立方體的各個面。

在這個例子中,我們用Interface Builder來構建立方體的面(圖5.19),我們當然可以用代碼來寫,但是用Interface Builder的好處是可以方便的在每一個面上添加子視圖。記住這些面僅僅是包含視圖和控件的普通的用戶界面元素,它們完全是我們界面交互的部分,并且當把它折成一個立方體之后也不會改變這個性質。

這些面視圖并沒有放置在主視圖當中,而是松散地排列在根nib文件里面。我們并不關心在這個容器中如何擺放它們的位置,因為后續將會用圖層的transform對它們進行重新布局,并且用Interface Builder在容器視圖之外擺放他們可以讓我們容易看清楚它們的內容,如果把它們一個疊著一個都塞進主視圖,將會變得很難看。

我們把一個有顏色的UILabel放置在視圖內部,是為了清楚的辨別它們之間的關系,并且UIButton被放置在第三個面視圖里面,后面會做簡單的解釋。
具體把視圖組織成立方體的代碼見清單5.9,結果見圖5.20
清單5.9 創建一個立方體

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *containerView; @property (nonatomic, strong) IBOutletCollection(UIView) NSArray *faces;@end@implementation ViewController- (void)addFace:(NSInteger)index withTransform:(CATransform3D)transform {//get the face view and add it to the containerUIView *face = self.faces[index];[self.containerView addSubview:face];//center the face view within the containerCGSize containerSize = self.containerView.bounds.size;face.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);// apply the transformface.layer.transform = transform; }- (void)viewDidLoad {[super viewDidLoad];//set up the container sublayer transformCATransform3D perspective = CATransform3DIdentity;perspective.m34 = -1.0 / 500.0;self.containerView.layer.sublayerTransform = perspective;//add cube face 1CATransform3D transform = CATransform3DMakeTranslation(0, 0, 100);[self addFace:0 withTransform:transform];//add cube face 2transform = CATransform3DMakeTranslation(100, 0, 0);transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);[self addFace:1 withTransform:transform];//add cube face 3transform = CATransform3DMakeTranslation(0, -100, 0);transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0);[self addFace:2 withTransform:transform];//add cube face 4transform = CATransform3DMakeTranslation(0, 100, 0);transform = CATransform3DRotate(transform, -M_PI_2, 1, 0, 0);[self addFace:3 withTransform:transform];//add cube face 5transform = CATransform3DMakeTranslation(-100, 0, 0);transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0);[self addFace:4 withTransform:transform];//add cube face 6transform = CATransform3DMakeTranslation(0, 0, -100);transform = CATransform3DRotate(transform, M_PI, 0, 1, 0);[self addFace:5 withTransform:transform]; } @end

從這個角度看立方體并不是很明顯;看起來只是一個方塊,為了更好地欣賞它,我們將更換一個不同的視角。

旋轉這個立方體將會顯得很笨重,因為我們要單獨對每個面做旋轉。另一個簡單的方案是通過調整容器視圖的sublayerTransform去旋轉照相機。
添加如下幾行去旋轉containerView圖層的perspective變換矩陣:

perspective = CATransform3DRotate(perspective, -M_PI_4, 1, 0, 0); perspective = CATransform3DRotate(perspective, -M_PI_4, 0, 1, 0);

這就對相機(或者相對相機的整個場景,你也可以這么認為)繞Y軸旋轉45度,并且繞X軸旋轉45度。現在從另一個角度去觀察立方體,就能看出它的真實面貌(圖5.21)。

光亮和陰影

現在它看起來更像是一個立方體沒錯了,但是對每個面之間的連接還是很難分辨。Core Animation可以用3D顯示圖層,但是它對光線并沒有概念。如果想讓立方體看起來更加真實,需要自己做一個陰影效果。你可以通過改變每個面的背景顏色或者直接用帶光亮效果的圖片來調整。

如果需要動態地創建光線效果,你可以根據每個視圖的方向應用不同的alpha值做出半透明的陰影圖層,但為了計算陰影圖層的不透明度,你需要得到每個面的正太向量(垂直于表面的向量),然后根據一個想象的光源計算出兩個向量叉乘結果。叉乘代表了光源和圖層之間的角度,從而決定了它有多大程度上的光亮。

清單5.10實現了這樣一個結果,我們用GLKit框架來做向量的計算(你需要引入GLKit庫來運行代碼),每個面的CATransform3D都被轉換成GLKMatrix4,然后通過GLKMatrix4GetMatrix3函數得出一個3×3的旋轉矩陣。這個旋轉矩陣指定了圖層的方向,然后可以用它來得到正太向量的值。

結果如圖5.22所示,試著調整LIGHT_DIRECTION和AMBIENT_LIGHT的值來切換光線效果
清單5.10 對立方體的表面應用動態的光線效果

#import "ViewController.h" #import #import #define LIGHT_DIRECTION 0, 1, -0.5 #define AMBIENT_LIGHT 0.5@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *containerView; @property (nonatomic, strong) IBOutletCollection(UIView) NSArray *faces;@end@implementation ViewController- (void)applyLightingToFace:(CALayer *)face {//add lighting layerCALayer *layer = [CALayer layer];layer.frame = face.bounds;[face addSublayer:layer];//convert the face transform to matrix//(GLKMatrix4 has the same structure as CATransform3D)//譯者注:GLKMatrix4和CATransform3D內存結構一致,但坐標類型有長度區別,所以理論上應該做一次float到CGFloat的轉換,感謝[@zihuyishi](https://github.com/zihuyishi)同學~CATransform3D transform = face.transform;GLKMatrix4 matrix4 = *(GLKMatrix4 *)&transform;GLKMatrix3 matrix3 = GLKMatrix4GetMatrix3(matrix4);//get face normalGLKVector3 normal = GLKVector3Make(0, 0, 1);normal = GLKMatrix3MultiplyVector3(matrix3, normal);normal = GLKVector3Normalize(normal);//get dot product with light directionGLKVector3 light = GLKVector3Normalize(GLKVector3Make(LIGHT_DIRECTION));float dotProduct = GLKVector3DotProduct(light, normal);//set lighting layer opacityCGFloat shadow = 1 + dotProduct - AMBIENT_LIGHT;UIColor *color = [UIColor colorWithWhite:0 alpha:shadow];layer.backgroundColor = color.CGColor; }- (void)addFace:(NSInteger)index withTransform:(CATransform3D)transform {//get the face view and add it to the containerUIView *face = self.faces[index];[self.containerView addSubview:face];//center the face view within the containerCGSize containerSize = self.containerView.bounds.size;face.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);// apply the transformface.layer.transform = transform;//apply lighting[self applyLightingToFace:face.layer]; }- (void)viewDidLoad {[super viewDidLoad];//set up the container sublayer transformCATransform3D perspective = CATransform3DIdentity;perspective.m34 = -1.0 / 500.0;perspective = CATransform3DRotate(perspective, -M_PI_4, 1, 0, 0);perspective = CATransform3DRotate(perspective, -M_PI_4, 0, 1, 0);self.containerView.layer.sublayerTransform = perspective;//add cube face 1CATransform3D transform = CATransform3DMakeTranslation(0, 0, 100);[self addFace:0 withTransform:transform];//add cube face 2transform = CATransform3DMakeTranslation(100, 0, 0);transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);[self addFace:1 withTransform:transform];//add cube face 3transform = CATransform3DMakeTranslation(0, -100, 0);transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0);[self addFace:2 withTransform:transform];//add cube face 4transform = CATransform3DMakeTranslation(0, 100, 0);transform = CATransform3DRotate(transform, -M_PI_2, 1, 0, 0);[self addFace:3 withTransform:transform];//add cube face 5transform = CATransform3DMakeTranslation(-100, 0, 0);transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0);[self addFace:4 withTransform:transform];//add cube face 6transform = CATransform3DMakeTranslation(0, 0, -100);transform = CATransform3DRotate(transform, M_PI, 0, 1, 0);[self addFace:5 withTransform:transform]; } @end

點擊事件

你應該能注意到現在可以在第三個表面的頂部看見按鈕了,點擊它,什么都沒發生,為什么呢?
這并不是因為iOS在3D場景下正確地處理響應事件,實際上是可以做到的。問題在于視圖順序。在第三章中我們簡要提到過,點擊事件的處理由視圖在父視圖中的順序決定的,并不是3D空間中的Z軸順序。當給立方體添加視圖的時候,我們實際上是按照一個順序添加,所以按照視圖/圖層順序來說,4,5,6在3的前面。
即使我們看不見4,5,6的表面(因為被1,2,3遮住了),iOS在事件響應上仍然保持之前的順序。當試圖點擊表面3上的按鈕,表面4,5,6截斷了點擊事件(取決于點擊的位置),這就和普通的2D布局在按鈕上覆蓋物體一樣。

你也許認為把doubleSided設置成NO可以解決這個問題,因為它不再渲染視圖后面的內容,但實際上并不起作用。因為背對相機而隱藏的視圖仍然會響應點擊事件(這和通過設置hidden屬性或者設置alpha為0而隱藏的視圖不同,那兩種方式將不會響應事件)。所以即使禁止了雙面渲染仍然不能解決這個問題(雖然由于性能問題,還是需要把它設置成NO)。

這里有幾種正確的方案:把除了表面3的其他視圖userInteractionEnabled屬性都設置成NO來禁止事件傳遞。或者簡單通過代碼把視圖3覆蓋在視圖6上。無論怎樣都可以點擊按鈕了(圖5.23)。

5.4 總結

這一章涉及了一些2D和3D的變換。你學習了一些矩陣計算的基礎,以及如何用Core Animation創建3D場景。你看到了圖層背后到底是如何呈現的,并且知道了不能把扁平的圖片做成真實的立體效果,最后我們用demo說明了觸摸事件的處理,視圖中圖層添加的層級順序會比屏幕上顯示的順序更有意義。
第六章我們會研究一些Core Animation提供不同功能的具體的CALayer子類。

六、專用圖層

本節轉載自ios核心動畫高級技巧

復雜的組織都是專門化的
Catharine R. Stimpson

到目前為止,我們已經探討過CALayer類了,同時我們也了解到了一些非常有用的繪圖和動畫功能。但是Core Animation圖層不僅僅能作用于圖片和顏色而已。本章就會學習其他的一些圖層類,進一步擴展使用Core Animation繪圖的能力。

6.1 CAShapeLayer

在第四章『視覺效果』我們學習到了不使用圖片的情況下用CGPath去構造任意形狀的陰影。如果我們能用同樣的方式創建相同形狀的圖層就好了。

CAShapeLayer是一個通過矢量圖形而不是bitmap來繪制的圖層子類。你指定諸如顏色和線寬等屬性,用CGPath來定義想要繪制的圖形,最后CAShapeLayer就自動渲染出來了。當然,你也可以用Core Graphics直接向原始的CALyer的內容中繪制一個路徑,相比直下,使用CAShapeLayer有以下一些優點:
- 渲染快速。CAShapeLayer使用了硬件加速,繪制同一圖形會比用Core Graphics快很多。
- 高效使用內存。一個CAShapeLayer不需要像普通CALayer一樣創建一個寄宿圖形,所以無論有多大,都不會占用太多的內存。
- 不會被圖層邊界剪裁掉。一個CAShapeLayer可以在邊界之外繪制。你的圖層路徑不會像在使用Core Graphics的普通CALayer一樣被剪裁掉(如我們在第二章所見)。
- 不會出現像素化。當你給CAShapeLayer做3D變換時,它不像一個有寄宿圖的普通圖層一樣變得像素化。

創建一個CGPath

CAShapeLayer可以用來繪制所有能夠通過CGPath來表示的形狀。這個形狀不一定要閉合,圖層路徑也不一定要不可破,事實上你可以在一個圖層上繪制好幾個不同的形狀。你可以控制一些屬性比如lineWith(線寬,用點表示單位),lineCap(線條結尾的樣子),和lineJoin(線條之間的結合點的樣子);但是在圖層層面你只有一次機會設置這些屬性。如果你想用不同顏色或風格來繪制多個形狀,就不得不為每個形狀準備一個圖層了。

清單6.1 的代碼用一個CAShapeLayer渲染一個簡單的火柴人。CAShapeLayer屬性是CGPathRef類型,但是我們用UIBezierPath幫助類創建了圖層路徑,這樣我們就不用考慮人工釋放CGPath了。圖6.1是代碼運行的結果。雖然還不是很完美,但是總算知道了大意對吧!

清單6.1 用CAShapeLayer繪制一個火柴人

#import "DrawingView.h" #import @interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *containerView;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//create pathUIBezierPath *path = [[UIBezierPath alloc] init];[path moveToPoint:CGPointMake(175, 100)];[path addArcWithCenter:CGPointMake(150, 100) radius:25 startAngle:0 endAngle:2*M_PI clockwise:YES];[path moveToPoint:CGPointMake(150, 125)];[path addLineToPoint:CGPointMake(150, 175)];[path addLineToPoint:CGPointMake(125, 225)];[path moveToPoint:CGPointMake(150, 175)];[path addLineToPoint:CGPointMake(175, 225)];[path moveToPoint:CGPointMake(100, 150)];[path addLineToPoint:CGPointMake(200, 150)];//create shape layerCAShapeLayer *shapeLayer = [CAShapeLayer layer];shapeLayer.strokeColor = [UIColor redColor].CGColor;shapeLayer.fillColor = [UIColor clearColor].CGColor;shapeLayer.lineWidth = 5;shapeLayer.lineJoin = kCALineJoinRound;shapeLayer.lineCap = kCALineCapRound;shapeLayer.path = path.CGPath;//add it to our view[self.containerView.layer addSublayer:shapeLayer]; } @end

圓角

第二章里面提到了CAShapeLayer為創建圓角視圖提供了一個方法,就是CALayer的cornerRadius屬性(譯者注:其實是在第四章提到的)。雖然使用CAShapeLayer類需要更多的工作,但是它有一個優勢就是可以單獨指定每個角。

我們創建圓角矩形其實就是人工繪制單獨的直線和弧度,但是事實上UIBezierPath有自動繪制圓角矩形的構造方法,下面這段代碼繪制了一個有三個圓角一個直角的矩形:

//define path parameters CGRect rect = CGRectMake(50, 50, 100, 100); CGSize radii = CGSizeMake(20, 20); UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft; //create path UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:radii];

我們可以通過這個圖層路徑繪制一個既有直角又有圓角的視圖。如果我們想依照此圖形來剪裁視圖內容,我們可以把CAShapeLayer作為視圖的宿主圖層,而不是添加一個子視圖(圖層蒙板的詳細解釋見第四章『視覺效果』)。

6.2 CATextLayer

用戶界面是無法從一個單獨的圖片里面構建的。一個設計良好的圖標能夠很好地表現一個按鈕或控件的意圖,不過你遲早都要需要一個不錯的老式風格的文本標簽。

如果你想在一個圖層里面顯示文字,完全可以借助圖層代理直接將字符串使用Core Graphics寫入圖層的內容(這就是UILabel的精髓)。如果越過寄宿于圖層的視圖,直接在圖層上操作,那其實相當繁瑣。你要為每一個顯示文字的圖層創建一個能像圖層代理一樣工作的類,還要邏輯上判斷哪個圖層需要顯示哪個字符串,更別提還要記錄不同的字體,顏色等一系列亂七八糟的東西。

萬幸的是這些都是不必要的,Core Animation提供了一個CALayer的子類CATextLayer,它以圖層的形式包含了UILabel幾乎所有的繪制特性,并且額外提供了一些新的特性。

同樣,CATextLayer也要比UILabel渲染得快得多。很少有人知道在iOS 6及之前的版本,UILabel其實是通過WebKit來實現繪制的,這樣就造成了當有很多文字的時候就會有極大的性能壓力。而CATextLayer使用了Core text,并且渲染得非常快。

讓我們來嘗試用CATextLayer來顯示一些文字。清單6.2的代碼實現了這一功能,結果如圖6.2所示。
清單6.2 用CATextLayer來實現一個UILabel

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *labelView;@end@implementation ViewController - (void)viewDidLoad {[super viewDidLoad];//create a text layerCATextLayer *textLayer = [CATextLayer layer];textLayer.frame = self.labelView.bounds;[self.labelView.layer addSublayer:textLayer];//set text attributestextLayer.foregroundColor = [UIColor blackColor].CGColor;textLayer.alignmentMode = kCAAlignmentJustified;textLayer.wrapped = YES;//choose a fontUIFont *font = [UIFont systemFontOfSize:15];//set layer fontCFStringRef fontName = (__bridge CFStringRef)font.fontName;CGFontRef fontRef = CGFontCreateWithFontName(fontName);textLayer.font = fontRef;textLayer.fontSize = font.pointSize;CGFontRelease(fontRef);//choose some textNSString *text = @"Lorem ipsum dolor sit amet, consectetur adipiscing \ elit. Quisque massa arcu, eleifend vel varius in, facilisis pulvinar \ leo. Nunc quis nunc at mauris pharetra condimentum ut ac neque. Nunc elementum, libero ut porttitor dictum, diam odio congue lacus, vel \ fringilla sapien diam at purus. Etiam suscipit pretium nunc sit amet \ lobortis";//set layer texttextLayer.string = text; } @end

如果你仔細看這個文本,你會發現一個奇怪的地方:這些文本有一些像素化了。這是因為并沒有以Retina的方式渲染,第二章提到了這個contentScale屬性,用來決定圖層內容應該以怎樣的分辨率來渲染。contentsScale并不關心屏幕的拉伸因素而總是默認為1.0。如果我們想以Retina的質量來顯示文字,我們就得手動地設置CATextLayer的contentsScale屬性,如下:
textLayer.contentsScale = [UIScreen mainScreen].scale;
這樣就解決了這個問題(如圖6.3)

CATextLayer的font屬性不是一個UIFont類型,而是一個CFTypeRef類型。這樣可以根據你的具體需要來決定字體屬性應該是用CGFontRef類型還是CTFontRef類型(Core Text字體)。同時字體大小也是用fontSize屬性單獨設置的,因為CTFontRef和CGFontRef并不像UIFont一樣包含點大小。這個例子會告訴你如何將UIFont轉換成CGFontRef。

另外,CATextLayer的string屬性并不是你想象的NSString類型,而是id類型。這樣你既可以用NSString也可以用NSAttributedString來指定文本了(注意,NSAttributedString并不是NSString的子類)。屬性化字符串是iOS用來渲染字體風格的機制,它以特定的方式來決定指定范圍內的字符串的原始信息,比如字體,顏色,字重,斜體等。

富文本

iOS 6中,Apple給UILabel和其他UIKit文本視圖添加了直接的屬性化字符串的支持,應該說這是一個很方便的特性。不過事實上從iOS3.2開始CATextLayer就已經支持屬性化字符串了。這樣的話,如果你想要支持更低版本的iOS系統,CATextLayer無疑是你向界面中增加富文本的好辦法,而且也不用去跟復雜的Core Text打交道,也省了用UIWebView的麻煩。

讓我們編輯一下示例使用到NSAttributedString(見清單6.3).iOS 6及以上我們可以用新的NSTextAttributeName實例來設置我們的字符串屬性,但是練習的目的是為了演示在iOS 5及以下,所以我們用了Core Text,也就是說你需要把Core Text framework添加到你的項目中。否則,編譯器是無法識別屬性常量的。
圖6.4是代碼運行結果(注意那個紅色的下劃線文本)
清單6.3 用NSAttributedString實現一個富文本標簽。

#import "DrawingView.h" #import #import @interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *labelView;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//create a text layerCATextLayer *textLayer = [CATextLayer layer];textLayer.frame = self.labelView.bounds;textLayer.contentsScale = [UIScreen mainScreen].scale;[self.labelView.layer addSublayer:textLayer];//set text attributestextLayer.alignmentMode = kCAAlignmentJustified;textLayer.wrapped = YES;//choose a fontUIFont *font = [UIFont systemFontOfSize:15];//choose some textNSString *text = @"Lorem ipsum dolor sit amet, consectetur adipiscing \ elit. Quisque massa arcu, eleifend vel varius in, facilisis pulvinar \ leo. Nunc quis nunc at mauris pharetra condimentum ut ac neque. Nunc \ elementum, libero ut porttitor dictum, diam odio congue lacus, vel \ fringilla sapien diam at purus. Etiam suscipit pretium nunc sit amet \ lobortis";//create attributed stringNSMutableAttributedString *string = nil;string = [[NSMutableAttributedString alloc] initWithString:text];//convert UIFont to a CTFontCFStringRef fontName = (__bridge CFStringRef)font.fontName;CGFloat fontSize = font.pointSize;CTFontRef fontRef = CTFontCreateWithName(fontName, fontSize, NULL);//set text attributesNSDictionary *attribs = @{(__bridge id)kCTForegroundColorAttributeName:(__bridge id)[UIColor blackColor].CGColor,(__bridge id)kCTFontAttributeName: (__bridge id)fontRef};[string setAttributes:attribs range:NSMakeRange(0, [text length])];attribs = @{(__bridge id)kCTForegroundColorAttributeName: (__bridge id)[UIColor redColor].CGColor,(__bridge id)kCTUnderlineStyleAttributeName: @(kCTUnderlineStyleSingle),(__bridge id)kCTFontAttributeName: (__bridge id)fontRef};[string setAttributes:attribs range:NSMakeRange(6, 5)];//release the CTFont we created earlierCFRelease(fontRef);//set layer texttextLayer.string = string; } @end

行距和字距

有必要提一下的是,由于繪制的實現機制不同(Core Text和WebKit),用CATextLayer渲染和用UILabel渲染出的文本行距和字距也不是不盡相同的。
有二者的差異程度(由使用的字體和字符決定)總的來說挺小,但是如果你想正確的顯示普通便簽和CATextLayer就一定要記住這一點。

UILabel的替代品

有我們已經證實了CATextLayer比UILabel有著更好的性能表現,同時還有額外的布局選項并且在iOS 5上支持富文本。但是與一般的標簽比較而言會更加繁瑣一些。如果我們真的在需求一個UILabel的可用替代品,最好是能夠在Interface Builder上創建我們的標簽,而且盡可能地像一般的視圖一樣正常工作。

有我們應該繼承UILabel,然后添加一個子圖層CATextLayer并重寫顯示文本的方法。但是仍然會有由UILabel的-drawRect:方法創建的空寄宿圖。而且由于CALayer不支持自動縮放和自動布局,子視圖并不是主動跟蹤視圖邊界的大小,所以每次視圖大小被更改,我們不得不手動更新子圖層的邊界。

有我們真正想要的是一個用CATextLayer作為宿主圖層的UILabel子類,這樣就可以隨著視圖自動調整大小而且也沒有冗余的寄宿圖啦。

有就像我們在第一章『圖層樹』討論的一樣,每一個UIView都是寄宿在一個CALayer的示例上。這個圖層是由視圖自動創建和管理的,那我們可以用別的圖層類型替代它么?一旦被創建,我們就無法代替這個圖層了。但是如果我們繼承了UIView,那我們就可以重寫+layerClass方法使得在創建的時候能返回一個不同的圖層子類。UIView會在初始化的時候調用+layerClass方法,然后用它的返回類型來創建宿主圖層。

有清單6.4 演示了一個UILabel子類LayerLabel用CATextLayer繪制它的問題,而不是調用一般的UILabel使用的較慢的-drawRect:方法。LayerLabel示例既可以用代碼實現,也可以在Interface Builder實現,只要把普通的標簽拖入視圖之中,然后設置它的類是LayerLabel就可以了。

清單6.4 使用CATextLayer的UILabel子類:LayerLabel

#import "LayerLabel.h" #import @implementation LayerLabel + (Class)layerClass {//this makes our label create a CATextLayer //instead of a regular CALayer for its backing layerreturn [CATextLayer class]; }- (CATextLayer *)textLayer {return (CATextLayer *)self.layer; }- (void)setUp {//set defaults from UILabel settingsself.text = self.text;self.textColor = self.textColor;self.font = self.font;//we should really derive these from the UILabel settings too//but that's complicated, so for now we'll just hard-code them[self textLayer].alignmentMode = kCAAlignmentJustified;[self textLayer].wrapped = YES;[self.layer display]; }- (id)initWithFrame:(CGRect)frame {//called when creating label programmaticallyif (self = [super initWithFrame:frame]) {[self setUp];}return self; }- (void)awakeFromNib {//called when creating label using Interface Builder[self setUp]; }- (void)setText:(NSString *)text {super.text = text;//set layer text[self textLayer].string = text; }- (void)setTextColor:(UIColor *)textColor {super.textColor = textColor;//set layer text color[self textLayer].foregroundColor = textColor.CGColor; }- (void)setFont:(UIFont *)font {super.font = font;//set layer fontCFStringRef fontName = (__bridge CFStringRef)font.fontName;CGFontRef fontRef = CGFontCreateWithFontName(fontName);[self textLayer].font = fontRef;[self textLayer].fontSize = font.pointSize;CGFontRelease(fontRef); } @end

如果你運行代碼,你會發現文本并沒有像素化,而我們也沒有設置contentsScale屬性。把CATextLayer作為宿主圖層的另一好處就是視圖自動設置了contentsScale屬性。

在這個簡單的例子中,我們只是實現了UILabel的一部分風格和布局屬性,不過稍微再改進一下我們就可以創建一個支持UILabel所有功能甚至更多功能的LayerLabel類(你可以在一些線上的開源項目中找到)。

如果你打算支持iOS 6及以上,基于CATextLayer的標簽可能就有有些局限性。但是總得來說,如果想在app里面充分利用CALayer子類,用+layerClass來創建基于不同圖層的視圖是一個簡單可復用的方法。

6.3 CATransformLayer

當我們在構造復雜的3D事物的時候,如果能夠組織獨立元素就太方便了。比如說,你想創造一個孩子的手臂:你就需要確定哪一部分是孩子的手腕,哪一部分是孩子的前臂,哪一部分是孩子的肘,哪一部分是孩子的上臂,哪一部分是孩子的肩膀等等。

當然是允許獨立地移動每個區域的啦。以肘為指點會移動前臂和手,而不是肩膀。Core Animation圖層很容易就可以讓你在2D環境下做出這樣的層級體系下的變換,但是3D情況下就不太可能,因為所有的圖層都把他的孩子都平面化到一個場景中(第五章『變換』有提到)。
CATransformLayer解決了這個問題,CATransformLayer不同于普通的CALayer,因為它不能顯示它自己的內容。只有當存在了一個能作用域子圖層的變換它才真正存在。CATransformLayer并不平面化它的子圖層,所以它能夠用于構造一個層級的3D結構,比如我的手臂示例。

用代碼創建一個手臂需要相當多的代碼,所以我就演示得更簡單一些吧:在第五章的立方體示例,我們將通過旋轉camara來解決圖層平面化問題而不是像立方體示例代碼中用的sublayerTransform。這是一個非常不錯的技巧,但是只能作用域單個對象上,如果你的場景包含兩個立方體,那我們就不能用這個技巧單獨旋轉他們了。

那么,就讓我們來試一試CATransformLayer吧,第一個問題就來了:在第五章,我們是用多個視圖來構造了我們的立方體,而不是單獨的圖層。我們不能在不打亂已有的視圖層次的前提下在一個本身不是有寄宿圖的圖層中放置一個寄宿圖圖層。我們可以創建一個新的UIView子類寄宿在CATransformLayer(用+layerClass方法)之上。但是,為了簡化案例,我們僅僅重建了一個單獨的圖層,而不是使用視圖。這意味著我們不能像第五章一樣在立方體表面顯示按鈕和標簽,不過我們現在也用不到這個特性。

清單6.5就是代碼。我們以我們在第五章使用過的相同基本邏輯放置立方體。但是并不像以前那樣直接將立方面添加到容器視圖的宿主圖層,我們將他們放置到一個CATransformLayer中創建一個獨立的立方體對象,然后將兩個這樣的立方體放進容器中。我們隨機地給立方面染色以將他們區分開來,這樣就不用靠標簽或是光亮來區分他們。圖6.5是運行結果。

清單6.5 用CATransformLayer裝配一個3D圖層體系

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *containerView;@end@implementation ViewController- (CALayer *)faceWithTransform:(CATransform3D)transform {//create cube face layerCALayer *face = [CALayer layer];face.frame = CGRectMake(-50, -50, 100, 100);//apply a random colorCGFloat red = (rand() / (double)INT_MAX);CGFloat green = (rand() / (double)INT_MAX);CGFloat blue = (rand() / (double)INT_MAX);face.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;//apply the transform and returnface.transform = transform;return face; }- (CALayer *)cubeWithTransform:(CATransform3D)transform {//create cube layerCATransformLayer *cube = [CATransformLayer layer];//add cube face 1CATransform3D ct = CATransform3DMakeTranslation(0, 0, 50);[cube addSublayer:[self faceWithTransform:ct]];//add cube face 2ct = CATransform3DMakeTranslation(50, 0, 0);ct = CATransform3DRotate(ct, M_PI_2, 0, 1, 0);[cube addSublayer:[self faceWithTransform:ct]];//add cube face 3ct = CATransform3DMakeTranslation(0, -50, 0);ct = CATransform3DRotate(ct, M_PI_2, 1, 0, 0);[cube addSublayer:[self faceWithTransform:ct]];//add cube face 4ct = CATransform3DMakeTranslation(0, 50, 0);ct = CATransform3DRotate(ct, -M_PI_2, 1, 0, 0);[cube addSublayer:[self faceWithTransform:ct]];//add cube face 5ct = CATransform3DMakeTranslation(-50, 0, 0);ct = CATransform3DRotate(ct, -M_PI_2, 0, 1, 0);[cube addSublayer:[self faceWithTransform:ct]];//add cube face 6ct = CATransform3DMakeTranslation(0, 0, -50);ct = CATransform3DRotate(ct, M_PI, 0, 1, 0);[cube addSublayer:[self faceWithTransform:ct]];//center the cube layer within the containerCGSize containerSize = self.containerView.bounds.size;cube.position = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);//apply the transform and returncube.transform = transform;return cube; }- (void)viewDidLoad {[super viewDidLoad];//set up the perspective transformCATransform3D pt = CATransform3DIdentity;pt.m34 = -1.0 / 500.0;self.containerView.layer.sublayerTransform = pt;//set up the transform for cube 1 and add itCATransform3D c1t = CATransform3DIdentity;c1t = CATransform3DTranslate(c1t, -100, 0, 0);CALayer *cube1 = [self cubeWithTransform:c1t];[self.containerView.layer addSublayer:cube1];//set up the transform for cube 2 and add itCATransform3D c2t = CATransform3DIdentity;c2t = CATransform3DTranslate(c2t, 100, 0, 0);c2t = CATransform3DRotate(c2t, -M_PI_4, 1, 0, 0);c2t = CATransform3DRotate(c2t, -M_PI_4, 0, 1, 0);CALayer *cube2 = [self cubeWithTransform:c2t];[self.containerView.layer addSublayer:cube2]; } @end

6.4 CAGradientLayer

CAGradientLayer是用來生成兩種或更多顏色平滑漸變的。用Core Graphics復制一個CAGradientLayer并將內容繪制到一個普通圖層的寄宿圖也是有可能的,但是CAGradientLayer的真正好處在于繪制使用了硬件加速。

基礎漸變

我們將從一個簡單的紅變藍的對角線漸變開始(見清單6.6).這些漸變色彩放在一個數組中,并賦給colors屬性。這個數組成員接受CGColorRef類型的值(并不是從NSObject派生而來),所以我們要用通過bridge轉換以確保編譯正常。

CAGradientLayer也有startPoint和endPoint屬性,他們決定了漸變的方向。這兩個參數是以單位坐標系進行的定義,所以左上角坐標是{0, 0},右下角坐標是{1, 1}。代碼運行結果如圖6.6
清單6.6 簡單的兩種顏色的對角線漸變

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *containerView;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//create gradient layer and add it to our container viewCAGradientLayer *gradientLayer = [CAGradientLayer layer];gradientLayer.frame = self.containerView.bounds;[self.containerView.layer addSublayer:gradientLayer];//set gradient colorsgradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor, (__bridge id)[UIColor blueColor].CGColor];//set gradient start and end pointsgradientLayer.startPoint = CGPointMake(0, 0);gradientLayer.endPoint = CGPointMake(1, 1); } @end

多重漸變

如果你愿意,colors屬性可以包含很多顏色,所以創建一個彩虹一樣的多重漸變也是很簡單的。默認情況下,這些顏色在空間上均勻地被渲染,但是我們可以用locations屬性來調整空間。locations屬性是一個浮點數值的數組(以NSNumber包裝)。這些浮點數定義了colors屬性中每個不同顏色的位置,同樣的,也是以單位坐標系進行標定。0.0代表著漸變的開始,1.0代表著結束。

locations數組并不是強制要求的,但是如果你給它賦值了就一定要確保locations的數組大小和colors數組大小一定要相同,否則你將會得到一個空白的漸變。

清單6.7展示了一個基于清單6.6的對角線漸變的代碼改造。現在變成了從紅到黃最后到綠色的漸變。locations數組指定了0.0,0.25和0.5三個數值,這樣這三個漸變就有點像擠在了左上角。(如圖6.7).
清單6.7 在漸變上使用locations

- (void)viewDidLoad {[super viewDidLoad];//create gradient layer and add it to our container viewCAGradientLayer *gradientLayer = [CAGradientLayer layer];gradientLayer.frame = self.containerView.bounds;[self.containerView.layer addSublayer:gradientLayer];//set gradient colorsgradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor, (__bridge id) [UIColor yellowColor].CGColor, (__bridge id)[UIColor greenColor].CGColor];//set locationsgradientLayer.locations = @[@0.0, @0.25, @0.5];//set gradient start and end pointsgradientLayer.startPoint = CGPointMake(0, 0);gradientLayer.endPoint = CGPointMake(1, 1); }

6.5 CAReplicatorLayer

CAReplicatorLayer的目的是為了高效生成許多相似的圖層。它會繪制一個或多個圖層的子圖層,并在每個復制體上應用不同的變換。看上去演示能夠更加解釋這些,我們來寫個例子吧。

重復圖層(Repeating Layers)

清單6.8中,我們在屏幕的中間創建了一個小白色方塊圖層,然后用CAReplicatorLayer生成十個圖層組成一個圓圈。instanceCount屬性指定了圖層需要重復多少次。instanceTransform指定了一個CATransform3D3D變換(這種情況下,下一圖層的位移和旋轉將會移動到圓圈的下一個點)。
變換是逐步增加的,每個實例都是相對于前一實例布局。這就是為什么這些復制體最終不會出現在同意位置上,圖6.8是代碼運行結果。
清單6.8 用CAReplicatorLayer重復圖層

@interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *containerView;@end@implementation ViewController - (void)viewDidLoad {[super viewDidLoad];//create a replicator layer and add it to our viewCAReplicatorLayer *replicator = [CAReplicatorLayer layer];replicator.frame = self.containerView.bounds;[self.containerView.layer addSublayer:replicator];//configure the replicatorreplicator.instanceCount = 10;//apply a transform for each instanceCATransform3D transform = CATransform3DIdentity;transform = CATransform3DTranslate(transform, 0, 200, 0);transform = CATransform3DRotate(transform, M_PI / 5.0, 0, 0, 1);transform = CATransform3DTranslate(transform, 0, -200, 0);replicator.instanceTransform = transform;//apply a color shift for each instancereplicator.instanceBlueOffset = -0.1;replicator.instanceGreenOffset = -0.1;//create a sublayer and place it inside the replicatorCALayer *layer = [CALayer layer];layer.frame = CGRectMake(100.0f, 100.0f, 100.0f, 100.0f);layer.backgroundColor = [UIColor whiteColor].CGColor;[replicator addSublayer:layer]; } @end

注意到當圖層在重復的時候,他們的顏色也在變化:這是用instanceBlueOffset和instanceGreenOffset屬性實現的。通過逐步減少藍色和綠色通道,我們逐漸將圖層顏色轉換成了紅色。這個復制效果看起來很酷,但是CAReplicatorLayer真正應用到實際程序上的場景比如:一個游戲中導彈的軌跡云,或者粒子爆炸(盡管iOS 5已經引入了CAEmitterLayer,它更適合創建任意的粒子效果)。除此之外,還有一個實際應用是:反射。

反射

使用CAReplicatorLayer并應用一個負比例變換于一個復制圖層,你就可以創建指定視圖(或整個視圖層次)內容的鏡像圖片,這樣就創建了一個實時的『反射』效果。讓我們來嘗試實現這個創意:指定一個繼承于UIView的ReflectionView,它會自動產生內容的反射效果。實現這個效果的代碼很簡單(見清單6.9),實際上用ReflectionView實現這個效果會更簡單,我們只需要把ReflectionView的實例放置于Interface Builder(見圖6.9),它就會實時生成子視圖的反射,而不需要別的代碼(見圖6.10).
清單6.9 用CAReplicatorLayer自動繪制反射

#import "ReflectionView.h" #import @implementation ReflectionView+ (Class)layerClass {return [CAReplicatorLayer class]; }- (void)setUp {//configure replicatorCAReplicatorLayer *layer = (CAReplicatorLayer *)self.layer;layer.instanceCount = 2;//move reflection instance below original and flip verticallyCATransform3D transform = CATransform3DIdentity;CGFloat verticalOffset = self.bounds.size.height + 2;transform = CATransform3DTranslate(transform, 0, verticalOffset, 0);transform = CATransform3DScale(transform, 1, -1, 0);layer.instanceTransform = transform;//reduce alpha of reflection layerlayer.instanceAlphaOffset = -0.6; }  - (id)initWithFrame:(CGRect)frame {//this is called when view is created in codeif ((self = [super initWithFrame:frame])) {[self setUp];}return self; }- (void)awakeFromNib {//this is called when view is created from a nib[self setUp]; } @end

開源代碼ReflectionView完成了一個自適應的漸變淡出效果(用CAGradientLayer
和圖層蒙板實現)

6.6 CAScrollLayer

對于一個未轉換的圖層,它的bounds和它的frame是一樣的,frame屬性是由bounds屬性自動計算而出的,所以更改任意一個值都會更新其他值。

但是如果你只想顯示一個大圖層里面的一小部分呢。比如說,你可能有一個很大的圖片,你希望用戶能夠隨意滑動,或者是一個數據或文本的長列表。在一個典型的iOS應用中,你可能會用到UITableView或是UIScrollView,但是對于獨立的圖層來說,什么會等價于剛剛提到的UITableView和UIScrollView呢?

在第二章中,我們探索了圖層的contentsRect屬性的用法,它的確是能夠解決在圖層中小地方顯示大圖片的解決方法。但是如果你的圖層包含子圖層那它就不是一個非常好的解決方案,因為,這樣做的話每次你想『滑動』可視區域的時候,你就需要手工重新計算并更新所有的子圖層位置。

這個時候就需要CAScrollLayer了。CAScrollLayer有一個-scrollToPoint:方法,它自動適應bounds的原點以便圖層內容出現在滑動的地方。注意,這就是它做的所有事情。前面提到過,Core Animation并不處理用戶輸入,所以CAScrollLayer并不負責將觸摸事件轉換為滑動事件,既不渲染滾動條,也不實現任何iOS指定行為例如滑動反彈(當視圖滑動超多了它的邊界的將會反彈回正確的地方)。

讓我們來用CAScrollLayer來常見一個基本的UIScrollView替代品。我們將會用CAScrollLayer作為視圖的宿主圖層,并創建一個自定義的UIView,然后用UIPanGestureRecognizer實現觸摸事件響應。這段代碼見清單6.10. 圖6.11是運行效果:ScrollView顯示了一個大于它的frame的UIImageView。
清單6.10 用CAScrollLayer實現滑動視圖

#import "ScrollView.h" #import @implementation ScrollView + (Class)layerClass {return [CAScrollLayer class]; }- (void)setUp {//enable clippingself.layer.masksToBounds = YES;//attach pan gesture recognizerUIPanGestureRecognizer *recognizer = nil;recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];[self addGestureRecognizer:recognizer]; }- (id)initWithFrame:(CGRect)frame {//this is called when view is created in codeif ((self = [super initWithFrame:frame])) {[self setUp];}return self; }- (void)awakeFromNib {//this is called when view is created from a nib[self setUp]; }- (void)pan:(UIPanGestureRecognizer *)recognizer {//get the offset by subtracting the pan gesture//translation from the current bounds originCGPoint offset = self.bounds.origin;offset.x -= [recognizer translationInView:self].x;offset.y -= [recognizer translationInView:self].y;//scroll the layer[(CAScrollLayer *)self.layer scrollToPoint:offset];//reset the pan gesture translation[recognizer setTranslation:CGPointZero inView:self]; } @end

圖6.11 用UIScrollView創建一個湊合的滑動視圖
不同于UIScrollView,我們定制的滑動視圖類并沒有實現任何形式的邊界檢查(bounds checking)。圖層內容極有可能滑出視圖的邊界并無限滑下去。CAScrollLayer并沒有等同于UIScrollView中contentSize的屬性,所以當CAScrollLayer滑動的時候完全沒有一個全局的可滑動區域的概念,也無法自適應它的邊界原點至你指定的值。它之所以不能自適應邊界大小是因為它不需要,內容完全可以超過邊界。

那你一定會奇怪用CAScrollLayer的意義到底何在,因為你可以簡單地用一個普通的CALayer然后手動適應邊界原點啊。真相其實并不復雜,UIScrollView并沒有用CAScrollLayer,事實上,就是簡單的通過直接操作圖層邊界來實現滑動。

CAScrollLayer有一個潛在的有用特性。如果你查看CAScrollLayer的頭文件,你就會注意到有一個擴展分類實現了一些方法和屬性:

- (void)scrollPoint:(CGPoint)p; - (void)scrollRectToVisible:(CGRect)r; @property(readonly) CGRect visibleRect;

看到這些方法和屬性名,你也許會以為這些方法給每個CALayer實例增加了滑動功能。但是事實上他們只是放置在CAScrollLayer中的圖層的實用方法。scrollPoint:方法從圖層樹中查找并找到第一個可用的CAScrollLayer,然后滑動它使得指定點成為可視的。scrollRectToVisible:方法實現了同樣的事情只不過是作用在一個矩形上的。visibleRect屬性決定圖層(如果存在的話)的哪部分是當前的可視區域。如果你自己實現這些方法就會相對容易明白一點,但是CAScrollLayer幫你省了這些麻煩,所以當涉及到實現圖層滑動的時候就可以用上了。

6.7 CATiledLayer

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

能高效繪制在iOS上的圖片也有一個大小限制。所有顯示在屏幕上的圖片最終都會被轉化為OpenGL紋理,同時OpenGL有一個最大的紋理尺寸(通常是2048 * 2048,或4096 * 4096,這個取決于設備型號)。如果你想在單個紋理中顯示一個比這大的圖,即便圖片已經存在于內存中了,你仍然會遇到很大的性能問題,因為Core Animation強制用CPU處理圖片而不是更快的GPU(見第12章『速度的曲調』,和第13章『高效繪圖』,它更加詳細地解釋了軟件繪制和硬件繪制)。
CATiledLayer為載入大圖造成的性能問題提供了一個解決方案:將大圖分解成小片然后將他們單獨按需載入。讓我們用實驗來證明一下。

小片裁剪

這個示例中,我們將會從一個2048 * 2048分辨率的雪人圖片入手。為了能夠從CATiledLayer中獲益,我們需要把這個圖片裁切成許多小一些的圖片。你可以通過代碼來完成這件事情,但是如果你在運行時讀入整個圖片并裁切,那CATiledLayer這些所有的性能優點就損失殆盡了。理想情況下來說,最好能夠逐個步驟來實現。
清單6.11 演示了一個簡單的Mac OS命令行程序,它用CATiledLayer將一個圖片裁剪成小圖并存儲到不同的文件中。
清單6.11 裁剪圖片成小圖的終端程序

#import int main(int argc, const char * argv[]) {@autoreleasepool{//handle incorrect argumentsif (argc < 2) {NSLog(@"TileCutter arguments: inputfile");return 0;}//input fileNSString *inputFile = [NSString stringWithCString:argv[1] encoding:NSUTF8StringEncoding];//tile sizeCGFloat tileSize = 256; //output pathNSString *outputPath = [inputFile stringByDeletingPathExtension];//load imageNSImage *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 columnsNSInteger rows = ceil(size.height / tileSize);NSInteger cols = ceil(size.width / tileSize);//generate tilesfor (int y = 0; y < rows; ++y) {for (int x = 0; x < cols; ++x) {//extract tile imageCGRect tileRect = CGRectMake(x*tileSize, y*tileSize, tileSize, tileSize);CGImageRef tileImage = CGImageCreateWithImageInRect(imageRef, tileRect);//convert to jpeg dataNSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:tileImage];NSData *data = [imageRep representationUsingType: NSJPEGFileType properties:nil];CGImageRelease(tileImage);//save fileNSString *path = [outputPath stringByAppendingFormat: @"_%02i_%02i.jpg", x, y];[data writeToFile:path atomically:NO];}}}return 0; }

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

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

這個程序相當基礎,但是能夠輕易地擴展支持額外的參數比如小圖大小,或者導出格式等等。運行結果是64個新圖的序列,如下面命名:

Snowman_00_00.jpg Snowman_00_01.jpg Snowman_00_02.jpg ... Snowman_07_07.jpg

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

清單6.12演示了代碼。圖6.12是代碼運行結果。
清單6.12 一個簡單的滾動CATiledLayer實現

#import "ViewController.h" #import @interface ViewController ()@property (nonatomic, weak) IBOutlet UIScrollView *scrollView;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//add the tiled layerCATiledLayer *tileLayer = [CATiledLayer layer];tileLayer.frame = CGRectMake(0, 0, 2048, 2048);tileLayer.delegate = self; [self.scrollView.layer addSublayer:tileLayer];//configure the scroll viewself.scrollView.contentSize = tileLayer.frame.size;//draw layer[tileLayer setNeedsDisplay]; }- (void)drawLayer:(CATiledLayer *)layer inContext:(CGContextRef)ctx {//determine tile coordinateCGRect bounds = CGContextGetClipBoundingBox(ctx);NSInteger x = floor(bounds.origin.x / layer.tileSize.width);NSInteger y = floor(bounds.origin.y / layer.tileSize.height);//load tile imageNSString *imageName = [NSString stringWithFormat: @"Snowman_%02i_%02i", x, y];NSString *imagePath = [[NSBundle mainBundle] pathForResource:imageName ofType:@"jpg"];UIImage *tileImage = [UIImage imageWithContentsOfFile:imagePath];//draw tileUIGraphicsPushContext(ctx);[tileImage drawInRect:bounds];UIGraphicsPopContext(); } @end

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

Retina小圖

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

tileLayer.contentsScale = [UIScreen mainScreen].scale;

有趣的是,tileSize是以像素為單位,而不是點,所以增大了contentsScale就自動有了默認的小圖尺寸(現在它是128*128的點而不是256*256).所以,我們不需要手工更新小圖的尺寸或是在Retina分辨率下指定一個不同的小圖。我們需要做的是適應小圖渲染代碼以對應安排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);

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

6.8 CAEmitterLayer

在iOS 5中,蘋果引入了一個新的CALayer子類叫做CAEmitterLayer。CAEmitterLayer是一個高性能的粒子引擎,被用來創建實時例子動畫如:煙霧,火,雨等等這些效果。
CAEmitterLayer看上去像是許多CAEmitterCell的容器,這些CAEmitierCell定義了一個例子效果。你將會為不同的例子效果定義一個或多個CAEmitterCell作為模版,同時CAEmitterLayer負責基于這些模版實例化一個粒子流。一個CAEmitterCell類似于一個CALayer:它有一個contents屬性可以定義為一個CGImage,另外還有一些可設置屬性控制著表現和行為。我們不會對這些屬性逐一進行詳細的描述,你們可以在CAEmitterCell類的頭文件中找到。
我們來舉個例子。我們將利用在一圓中發射不同速度和透明度的粒子創建一個火爆炸的效果。清單6.13包含了生成爆炸的代碼。圖6.13是運行結果
清單6.13 用CAEmitterLayer創建爆炸效果

#import "ViewController.h" #import @interface ViewController ()@property (nonatomic, weak) IBOutlet UIView *containerView;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];//create particle emitter layerCAEmitterLayer *emitter = [CAEmitterLayer layer];emitter.frame = self.containerView.bounds;[self.containerView.layer addSublayer:emitter];//configure emitteremitter.renderMode = kCAEmitterLayerAdditive;emitter.emitterPosition = CGPointMake(emitter.frame.size.width / 2.0, emitter.frame.size.height / 2.0);//create a particle templateCAEmitterCell *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 emitteremitter.emitterCells = @[cell]; } @end

圖6.13 火焰爆炸效果
CAEMitterCell的屬性基本上可以分為三種:
- 這種粒子的某一屬性的初始值。比如,color屬性指定了一個可以混合圖片內容顏色的混合色。在示例中,我們將它設置為桔色。
- 例子某一屬性的變化范圍。比如emissionRange屬性的值是2π,這意味著例子可以從360度任意位置反射出來。如果指定一個小一些的值,就可以創造出一個圓錐形
- 指定值在時間線上的變化。比如,在示例中,我們將alphaSpeed設置為-0.4,就是說例子的透明度每過一秒就是減少0.4,這樣就有發射出去之后逐漸小時的效果。

CAEmitterLayer的屬性它自己控制著整個例子系統的位置和形狀。一些屬性比如birthRate,lifetime和celocity,這些屬性在CAEmitterCell中也有。這些屬性會以相乘的方式作用在一起,這樣你就可以用一個值來加速或者擴大整個例子系統。其他值得提到的屬性有以下這些:
- preservesDepth,是否將3D例子系統平面化到一個圖層(默認值)或者可以在3D空間中混合其他的圖層
- renderMode,控制著在視覺上粒子圖片是如何混合的。你可能已經注意到了示例中我們把它設置為kCAEmitterLayerAdditive,它實現了這樣一個效果:合并例子重疊部分的亮度使得看上去更亮。如果我們把它設置為默認的kCAEmitterLayerUnordered,效果就沒那么好看了(見圖6.14).

6.8 CAEAGLLayer

當iOS要處理高性能圖形繪制,必要時就是OpenGL。應該說它應該是最后的殺手锏,至少對于非游戲的應用來說是的。因為相比Core Animation和UIkit框架,它不可思議地復雜。

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

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

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

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

大部分情況下你都不需要手動設置CAEAGLLayer(假設用GLKView),過去的日子就不要再提了。特別的,我們將設置一個OpenGL ES 2.0的上下文,它是現代的iOS設備的標準做法。

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

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

清單6.14 用CAEAGLLayer繪制一個三角形

#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 bufferglGenFramebuffers(1, &_framebuffer);glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);//set up color render bufferglGenRenderbuffers(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 successif (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {NSLog(@"Failed to make complete framebuffer object: %i", glCheckFramebufferStatus(GL_FRAMEBUFFER));} }- (void)tearDownBuffers {if (_framebuffer) {//delete framebufferglDeleteFramebuffers(1, &_framebuffer);_framebuffer = 0;}if (_colorRenderbuffer) {//delete color render bufferglDeleteRenderbuffers(1, &_colorRenderbuffer);_colorRenderbuffer = 0;} }- (void)drawFrame {//bind framebuffer & set viewportglBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);glViewport(0, 0, _framebufferWidth, _framebufferHeight);//bind shader program[self.effect prepareToDraw];//clear the screenglClear(GL_COLOR_BUFFER_BIT); glClearColor(0.0, 0.0, 0.0, 1.0);//set up verticesGLfloat vertices[] = {-0.5f, -0.5f, -1.0f, 0.0f, 0.5f, -1.0f, 0.5f, -0.5f, -1.0f,};//set up colorsGLfloat 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 triangleglEnableVertexAttribArray(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 bufferglBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);[self.glContext presentRenderbuffer:GL_RENDERBUFFER]; }- (void)viewDidLoad {[super viewDidLoad];//set up contextself.glContext = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2];[EAGLContext setCurrentContext:self.glContext];//set up layerself.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 effectself.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


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

6.10 AVPlayerLayer

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

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

在我們開始之前,我們需要添加AVFoundation到我們的項目中。然后,清單6.15創建了一個簡單的電影播放器,圖6.16是代碼運行結果。

清單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 URLNSURL *URL = [[NSBundle mainBundle] URLForResource:@"Ship" withExtension:@"mp4"];//create player and player layerAVPlayer *player = [AVPlayer playerWithURL:URL];AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];//set player layer frame and attach it to our viewplayerLayer.frame = self.containerView.bounds;[self.containerView.layer addSublayer:playerLayer];//play the video[player play]; } @end

我們用代碼創建了一個AVPlayerLayer,但是我們仍然把它添加到了一個容器視圖中,而不是直接在controller中的主視圖上添加。這樣其實是為了可以使用自動布局限制使得圖層在最中間;否則,一旦設備被旋轉了我們就要手動重新放置位置,因為Core Animation并不支持自動大小和自動布局(見第三章『圖層幾何學』)。

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

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

- (void)viewDidLoad {...//set player layer frame and attach it to our viewplayerLayer.frame = self.containerView.bounds;[self.containerView.layer addSublayer:playerLayer];//transform layerCATransform3D transform = CATransform3DIdentity;transform.m34 = -1.0 / 500.0;transform = CATransform3DRotate(transform, M_PI_4, 1, 1, 0);playerLayer.transform = transform;//add rounded corners and borderplayerLayer.masksToBounds = YES;playerLayer.cornerRadius = 20.0;playerLayer.borderColor = [UIColor redColor].CGColor;playerLayer.borderWidth = 5.0;//play the video[player play]; }

6.11 總結

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

總結

以上是生活随笔為你收集整理的CoreAnimation的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

天天躁日日躁狠狠躁免费麻豆 | 无遮挡啪啪摇乳动态图 | 亚洲理论电影在线观看 | 国产口爆吞精在线视频 | 日本精品久久久久中文字幕 | 又大又硬又爽免费视频 | 99久久婷婷国产综合精品青草免费 | 婷婷五月综合激情中文字幕 | 国产另类ts人妖一区二区 | 欧美性生交活xxxxxdddd | 骚片av蜜桃精品一区 | 乌克兰少妇性做爰 | 丰满少妇女裸体bbw | 爆乳一区二区三区无码 | 牲交欧美兽交欧美 | 久久www免费人成人片 | 日本精品人妻无码免费大全 | 99国产精品白浆在线观看免费 | 日本xxxx色视频在线观看免费 | 亚洲成av人在线观看网址 | 人妻无码αv中文字幕久久琪琪布 | 精品国产麻豆免费人成网站 | 狠狠亚洲超碰狼人久久 | 国产手机在线αⅴ片无码观看 | 妺妺窝人体色www在线小说 | 人人妻人人澡人人爽人人精品 | 中文字幕日产无线码一区 | 精品一二三区久久aaa片 | 四虎影视成人永久免费观看视频 | 日产国产精品亚洲系列 | 激情爆乳一区二区三区 | 福利一区二区三区视频在线观看 | 色情久久久av熟女人妻网站 | 女人和拘做爰正片视频 | 激情国产av做激情国产爱 | 亚拍精品一区二区三区探花 | 亚洲日韩一区二区 | 国产综合色产在线精品 | 性欧美牲交在线视频 | 亚洲中文字幕av在天堂 | 国产特级毛片aaaaaa高潮流水 | 澳门永久av免费网站 | aⅴ亚洲 日韩 色 图网站 播放 | 天天做天天爱天天爽综合网 | 亚洲一区二区观看播放 | 牲交欧美兽交欧美 | 欧美乱妇无乱码大黄a片 | 国产精品亚洲lv粉色 | 精品乱码久久久久久久 | 麻豆果冻传媒2021精品传媒一区下载 | 人妻中文无码久热丝袜 | 性欧美牲交xxxxx视频 | 亚洲色欲久久久综合网东京热 | av香港经典三级级 在线 | 天堂а√在线中文在线 | 激情综合激情五月俺也去 | 97夜夜澡人人双人人人喊 | 麻豆国产97在线 | 欧洲 | 未满成年国产在线观看 | 亚洲区欧美区综合区自拍区 | 中文毛片无遮挡高清免费 | 国产精品成人av在线观看 | 久久久精品456亚洲影院 | 无码人中文字幕 | 国产99久久精品一区二区 | 午夜无码区在线观看 | 国产sm调教视频在线观看 | 又湿又紧又大又爽a视频国产 | 久久 国产 尿 小便 嘘嘘 | 色五月五月丁香亚洲综合网 | 亚洲精品中文字幕乱码 | 国产在线一区二区三区四区五区 | 国产va免费精品观看 | 国产激情无码一区二区app | 奇米综合四色77777久久 东京无码熟妇人妻av在线网址 | 国产成人综合美国十次 | 久久久久久久女国产乱让韩 | 国产精品久久国产三级国 | 日欧一片内射va在线影院 | 久久久久免费精品国产 | 丰满少妇人妻久久久久久 | 亚洲色大成网站www | 国产真人无遮挡作爱免费视频 | 丰满人妻被黑人猛烈进入 | 爱做久久久久久 | 中文字幕av无码一区二区三区电影 | 久久亚洲精品中文字幕无男同 | 性色欲情网站iwww九文堂 | 久青草影院在线观看国产 | 中文字幕av日韩精品一区二区 | 中文字幕乱码中文乱码51精品 | 日韩亚洲欧美精品综合 | 国产午夜精品一区二区三区嫩草 | 精品一区二区三区波多野结衣 | 最新版天堂资源中文官网 | 四十如虎的丰满熟妇啪啪 | 国产亚洲tv在线观看 | 国产熟女一区二区三区四区五区 | 亚洲精品一区二区三区四区五区 | 亚洲国产精品一区二区第一页 | 超碰97人人做人人爱少妇 | 久久综合香蕉国产蜜臀av | 曰韩少妇内射免费播放 | 最新国产乱人伦偷精品免费网站 | 色欲综合久久中文字幕网 | 午夜福利电影 | 曰韩无码二三区中文字幕 | 人人妻人人藻人人爽欧美一区 | 国产午夜亚洲精品不卡下载 | 国内精品久久久久久中文字幕 | 欧美变态另类xxxx | 永久免费精品精品永久-夜色 | 中文字幕av伊人av无码av | 欧美丰满少妇xxxx性 | 亚洲伊人久久精品影院 | 在线欧美精品一区二区三区 | 精品熟女少妇av免费观看 | 精品人妻中文字幕有码在线 | 亚洲精品中文字幕乱码 | 无码成人精品区在线观看 | 乱码av麻豆丝袜熟女系列 | 性色欲网站人妻丰满中文久久不卡 | www国产亚洲精品久久网站 | 牲欲强的熟妇农村老妇女 | 久久精品女人的天堂av | 俺去俺来也在线www色官网 | 国产精品久久久午夜夜伦鲁鲁 | 免费人成在线视频无码 | 波多野结衣一区二区三区av免费 | 1000部啪啪未满十八勿入下载 | 人人澡人人透人人爽 | 国产激情无码一区二区app | 日本丰满熟妇videos | 欧美日韩亚洲国产精品 | 性生交片免费无码看人 | 亚洲s码欧洲m码国产av | 5858s亚洲色大成网站www | 一二三四社区在线中文视频 | 精品乱码久久久久久久 | 国产日产欧产精品精品app | 人妻少妇精品久久 | 午夜精品久久久内射近拍高清 | 又粗又大又硬毛片免费看 | 夜夜夜高潮夜夜爽夜夜爰爰 | 亚洲精品久久久久avwww潮水 | 午夜福利不卡在线视频 | 中文字幕乱码中文乱码51精品 | 无码福利日韩神码福利片 | 一本色道久久综合亚洲精品不卡 | 性做久久久久久久免费看 | 在线 国产 欧美 亚洲 天堂 | 亚洲日韩乱码中文无码蜜桃臀网站 | 中文字幕乱码人妻二区三区 | 亚洲精品国产品国语在线观看 | 国产乱码精品一品二品 | 国产精品多人p群无码 | 亚洲综合在线一区二区三区 | 免费国产成人高清在线观看网站 | 国产精品丝袜黑色高跟鞋 | 暴力强奷在线播放无码 | 巨爆乳无码视频在线观看 | 国内精品人妻无码久久久影院 | 精品国产aⅴ无码一区二区 | 国产区女主播在线观看 | 中文字幕 人妻熟女 | 国产精品无码永久免费888 | 中文字幕乱码中文乱码51精品 | 久久无码中文字幕免费影院蜜桃 | 亚洲爆乳大丰满无码专区 | av香港经典三级级 在线 | 激情内射亚州一区二区三区爱妻 | 欧美日韩综合一区二区三区 | 亚洲va欧美va天堂v国产综合 | 欧美成人免费全部网站 | 精品人人妻人人澡人人爽人人 | 国产人成高清在线视频99最全资源 | 久久久久久av无码免费看大片 | 国产高清av在线播放 | 激情国产av做激情国产爱 | 双乳奶水饱满少妇呻吟 | 日日天干夜夜狠狠爱 | 老熟妇乱子伦牲交视频 | 国产美女极度色诱视频www | 欧美精品国产综合久久 | 亚洲理论电影在线观看 | 国精产品一品二品国精品69xx | 高清国产亚洲精品自在久久 | 亚洲中文字幕在线无码一区二区 | 丰满人妻一区二区三区免费视频 | 少妇无码av无码专区在线观看 | 特级做a爰片毛片免费69 | 欧美日本精品一区二区三区 | 天天摸天天碰天天添 | 亚洲理论电影在线观看 | 亚洲中文字幕乱码av波多ji | 国产亚洲精品久久久久久久 | 亚洲日韩av一区二区三区中文 | 麻豆人妻少妇精品无码专区 | 亚洲综合另类小说色区 | 精品久久久久香蕉网 | 欧美一区二区三区视频在线观看 | 精品无码一区二区三区的天堂 | 水蜜桃色314在线观看 | 天堂亚洲免费视频 | 久久99热只有频精品8 | 日韩亚洲欧美中文高清在线 | 欧美一区二区三区视频在线观看 | av在线亚洲欧洲日产一区二区 | 人妻有码中文字幕在线 | 牲交欧美兽交欧美 | 国产特级毛片aaaaaaa高清 | 激情综合激情五月俺也去 | 无套内谢的新婚少妇国语播放 | 波多野结衣aⅴ在线 | 精品国产精品久久一区免费式 | 成人欧美一区二区三区 | 久久久久久九九精品久 | 色婷婷综合中文久久一本 | 色狠狠av一区二区三区 | 久久久久99精品成人片 | 亚洲最大成人网站 | 狠狠色丁香久久婷婷综合五月 | 九月婷婷人人澡人人添人人爽 | 日韩精品无码一区二区中文字幕 | а√资源新版在线天堂 | 东北女人啪啪对白 | 蜜桃视频插满18在线观看 | 国产农村妇女高潮大叫 | 天堂无码人妻精品一区二区三区 | 国产精品理论片在线观看 | 国产舌乚八伦偷品w中 | 人妻有码中文字幕在线 | 亚洲色无码一区二区三区 | 久久综合给久久狠狠97色 | 精品 日韩 国产 欧美 视频 | 激情亚洲一区国产精品 | 免费网站看v片在线18禁无码 | 成人性做爰aaa片免费看 | 精品厕所偷拍各类美女tp嘘嘘 | 日本在线高清不卡免费播放 | 无码av免费一区二区三区试看 | 国产精品高潮呻吟av久久 | 国产熟女一区二区三区四区五区 | 四十如虎的丰满熟妇啪啪 | 人妻插b视频一区二区三区 | 久久精品中文字幕一区 | 少妇性荡欲午夜性开放视频剧场 | 久久五月精品中文字幕 | 国产激情一区二区三区 | 国产另类ts人妖一区二区 | 天堂а√在线中文在线 | 国产精品a成v人在线播放 | 亚无码乱人伦一区二区 | 又色又爽又黄的美女裸体网站 | 精品欧洲av无码一区二区三区 | 亚洲国产精品无码一区二区三区 | 少妇高潮喷潮久久久影院 | a片在线免费观看 | 欧美人妻一区二区三区 | 国产电影无码午夜在线播放 | 一区二区三区高清视频一 | 亚洲s码欧洲m码国产av | 色窝窝无码一区二区三区色欲 | 黑人玩弄人妻中文在线 | 无码av中文字幕免费放 | 国色天香社区在线视频 | av香港经典三级级 在线 | 一区二区传媒有限公司 | 内射欧美老妇wbb | 色婷婷av一区二区三区之红樱桃 | 四虎4hu永久免费 | 亚洲综合久久一区二区 | 国产在热线精品视频 | 国产成人综合在线女婷五月99播放 | 国产激情一区二区三区 | 亚洲色大成网站www | 亚洲午夜久久久影院 | 国产人成高清在线视频99最全资源 | 精品人妻中文字幕有码在线 | 国产精品怡红院永久免费 | 国产亚洲精品久久久久久 | 国产av人人夜夜澡人人爽麻豆 | 日韩精品乱码av一区二区 | 老熟妇仑乱视频一区二区 | 天海翼激烈高潮到腰振不止 | 红桃av一区二区三区在线无码av | 最新国产乱人伦偷精品免费网站 | 国产精品久久久av久久久 | 鲁一鲁av2019在线 | 亚洲理论电影在线观看 | 亚洲成av人在线观看网址 | 国产性猛交╳xxx乱大交 国产精品久久久久久无码 欧洲欧美人成视频在线 | 自拍偷自拍亚洲精品被多人伦好爽 | 妺妺窝人体色www婷婷 | 国产 浪潮av性色四虎 | 乱人伦中文视频在线观看 | 无遮无挡爽爽免费视频 | 无码纯肉视频在线观看 | www成人国产高清内射 | av在线亚洲欧洲日产一区二区 | 亚洲自偷精品视频自拍 | 国产两女互慰高潮视频在线观看 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 成在人线av无码免费 | 欧美乱妇无乱码大黄a片 | 国产美女精品一区二区三区 | 荫蒂被男人添的好舒服爽免费视频 | 亚洲精品久久久久中文第一幕 | 国色天香社区在线视频 | 国产真实乱对白精彩久久 | 成 人 免费观看网站 | 精品无人区无码乱码毛片国产 | 亚洲自偷自偷在线制服 | 成人综合网亚洲伊人 | 免费中文字幕日韩欧美 | 中文字幕+乱码+中文字幕一区 | 国产两女互慰高潮视频在线观看 | 久久久中文字幕日本无吗 | 国产精品爱久久久久久久 | 亚洲精品成a人在线观看 | 99久久精品无码一区二区毛片 | 国产av久久久久精东av | 亚洲成a人片在线观看无码 | 亚洲区小说区激情区图片区 | 国产av无码专区亚洲awww | 久久精品女人天堂av免费观看 | 动漫av一区二区在线观看 | 青青青爽视频在线观看 | 爽爽影院免费观看 | 女高中生第一次破苞av | 日本乱偷人妻中文字幕 | 国产卡一卡二卡三 | 在线播放免费人成毛片乱码 | 少妇高潮一区二区三区99 | 大乳丰满人妻中文字幕日本 | 天天拍夜夜添久久精品 | 精品国产青草久久久久福利 | 欧美日韩色另类综合 | 性开放的女人aaa片 | 少妇久久久久久人妻无码 | 亚洲最大成人网站 | 少妇人妻偷人精品无码视频 | 精品久久久久香蕉网 | a在线观看免费网站大全 | 日日噜噜噜噜夜夜爽亚洲精品 | 久久久久99精品成人片 | 久久精品国产大片免费观看 | 国产亚洲精品久久久闺蜜 | 日日摸日日碰夜夜爽av | 成人无码影片精品久久久 | 99精品视频在线观看免费 | 欧美老妇交乱视频在线观看 | 精品无码成人片一区二区98 | 久久久久亚洲精品男人的天堂 | 欧美怡红院免费全部视频 | 精品厕所偷拍各类美女tp嘘嘘 | 少妇人妻大乳在线视频 | 一个人免费观看的www视频 | 国产午夜无码精品免费看 | 亚洲色在线无码国产精品不卡 | 精品人妻中文字幕有码在线 | 中文字幕无码日韩欧毛 | 少妇厨房愉情理9仑片视频 | 女人被爽到呻吟gif动态图视看 | 国产一区二区三区日韩精品 | 俺去俺来也www色官网 | 毛片内射-百度 | 青草视频在线播放 | 国产精品无码成人午夜电影 | 少妇无套内谢久久久久 | 午夜性刺激在线视频免费 | 国产精品美女久久久网av | 蜜桃视频插满18在线观看 | 丝袜美腿亚洲一区二区 | 综合人妻久久一区二区精品 | 国产亚洲日韩欧美另类第八页 | 亚洲精品久久久久中文第一幕 | 国产精品人妻一区二区三区四 | 久久精品人人做人人综合试看 | 婷婷丁香五月天综合东京热 | 国产网红无码精品视频 | 麻豆成人精品国产免费 | 国产特级毛片aaaaaa高潮流水 | 两性色午夜视频免费播放 | 十八禁视频网站在线观看 | 欧美兽交xxxx×视频 | 国产亚洲精品久久久闺蜜 | 2019午夜福利不卡片在线 | 亚洲熟妇色xxxxx欧美老妇 | 欧洲精品码一区二区三区免费看 | 99久久久无码国产aaa精品 | 久久99精品国产.久久久久 | 国产成人综合色在线观看网站 | 无码av最新清无码专区吞精 | 一本久久a久久精品亚洲 | 男女下面进入的视频免费午夜 | 伦伦影院午夜理论片 | 娇妻被黑人粗大高潮白浆 | 风流少妇按摩来高潮 | 亚洲精品久久久久久久久久久 | 亚洲欧美国产精品久久 | 亚洲欧美综合区丁香五月小说 | 草草网站影院白丝内射 | 国产又粗又硬又大爽黄老大爷视 | 精品国产一区二区三区四区在线看 | 奇米影视7777久久精品人人爽 | 久激情内射婷内射蜜桃人妖 | 人妻人人添人妻人人爱 | 99视频精品全部免费免费观看 | 国产色xx群视频射精 | 国产精品二区一区二区aⅴ污介绍 | 中国女人内谢69xxxxxa片 | 强伦人妻一区二区三区视频18 | 国产麻豆精品精东影业av网站 | 欧美性黑人极品hd | 国产人妻精品午夜福利免费 | 国产免费久久久久久无码 | 99久久久国产精品无码免费 | 无码福利日韩神码福利片 | 精品午夜福利在线观看 | 内射白嫩少妇超碰 | 欧美亚洲日韩国产人成在线播放 | 一本大道伊人av久久综合 | 中文字幕av日韩精品一区二区 | 亚洲s码欧洲m码国产av | 日本护士毛茸茸高潮 | 极品尤物被啪到呻吟喷水 | 久久久久久国产精品无码下载 | 性史性农村dvd毛片 | 性开放的女人aaa片 | 永久免费观看美女裸体的网站 | 无遮挡国产高潮视频免费观看 | 国产人妻人伦精品 | 精品少妇爆乳无码av无码专区 | 高清国产亚洲精品自在久久 | 日日噜噜噜噜夜夜爽亚洲精品 | 亚洲一区二区三区香蕉 | 好爽又高潮了毛片免费下载 | 少妇被粗大的猛进出69影院 | 2020久久香蕉国产线看观看 | 偷窥村妇洗澡毛毛多 | 国产精品美女久久久 | 中文字幕+乱码+中文字幕一区 | 国产激情精品一区二区三区 | 性欧美疯狂xxxxbbbb | 俺去俺来也在线www色官网 | 女人被男人躁得好爽免费视频 | 国产成人一区二区三区在线观看 | 亚洲va中文字幕无码久久不卡 | 国产电影无码午夜在线播放 | 久久亚洲中文字幕精品一区 | 国模大胆一区二区三区 | 欧美熟妇另类久久久久久多毛 | 国产亚洲日韩欧美另类第八页 | 四十如虎的丰满熟妇啪啪 | 中文久久乱码一区二区 | 荫蒂被男人添的好舒服爽免费视频 | 人妻熟女一区 | 青青久在线视频免费观看 | 欧美人与物videos另类 | 欧美喷潮久久久xxxxx | 东北女人啪啪对白 | 国产一区二区不卡老阿姨 | 人妻人人添人妻人人爱 | 午夜成人1000部免费视频 | 亚洲自偷自偷在线制服 | 亚洲成色在线综合网站 | 中文字幕人妻丝袜二区 | 免费中文字幕日韩欧美 | 牛和人交xxxx欧美 | 日本又色又爽又黄的a片18禁 | 一本久道久久综合婷婷五月 | 激情国产av做激情国产爱 | 中文字幕 亚洲精品 第1页 | 国产精品内射视频免费 | 色一情一乱一伦一视频免费看 | 亚洲精品一区二区三区在线观看 | 中文无码精品a∨在线观看不卡 | 1000部夫妻午夜免费 | 强伦人妻一区二区三区视频18 | 国产成人av免费观看 | 久久99国产综合精品 | 97se亚洲精品一区 | 成人免费视频视频在线观看 免费 | 亚洲 欧美 激情 小说 另类 | 中文字幕精品av一区二区五区 | 中文字幕精品av一区二区五区 | 亚洲人成无码网www | 2020久久香蕉国产线看观看 | 国产性生交xxxxx无码 | 正在播放东北夫妻内射 | 青春草在线视频免费观看 | 国产情侣作爱视频免费观看 | 欧美大屁股xxxxhd黑色 | 婷婷综合久久中文字幕蜜桃三电影 | 天堂久久天堂av色综合 | 久久久精品人妻久久影视 | 欧美兽交xxxx×视频 | 99精品国产综合久久久久五月天 | 成人无码视频在线观看网站 | 秋霞成人午夜鲁丝一区二区三区 | 日本成熟视频免费视频 | 88国产精品欧美一区二区三区 | 在线观看欧美一区二区三区 | 国产av久久久久精东av | 国产特级毛片aaaaaa高潮流水 | 亚洲精品午夜无码电影网 | 97夜夜澡人人爽人人喊中国片 | 亚洲中文字幕在线观看 | 国产激情无码一区二区app | 亚洲狠狠色丁香婷婷综合 | 人妻中文无码久热丝袜 | 性色欲网站人妻丰满中文久久不卡 | 中文字幕无线码免费人妻 | 日韩成人一区二区三区在线观看 | 亚洲va欧美va天堂v国产综合 | 人人澡人人透人人爽 | 亚洲自偷自偷在线制服 | 99久久精品国产一区二区蜜芽 | 亚洲日韩av一区二区三区四区 | 思思久久99热只有频精品66 | 国产精品多人p群无码 | 中文字幕无码av激情不卡 | 久久精品国产一区二区三区 | 亚洲中文字幕在线无码一区二区 | 国产亚洲欧美日韩亚洲中文色 | 一本无码人妻在中文字幕免费 | 我要看www免费看插插视频 | www一区二区www免费 | 免费无码肉片在线观看 | 中文无码成人免费视频在线观看 | 麻豆国产97在线 | 欧洲 | 天堂无码人妻精品一区二区三区 | 国产精品国产自线拍免费软件 | 国产凸凹视频一区二区 | 又粗又大又硬毛片免费看 | 理论片87福利理论电影 | 久久亚洲中文字幕无码 | 欧美丰满熟妇xxxx | 国产精品高潮呻吟av久久4虎 | 亚洲一区二区三区偷拍女厕 | 狠狠色丁香久久婷婷综合五月 | 中文字幕日产无线码一区 | 老熟妇仑乱视频一区二区 | av无码不卡在线观看免费 | 亚洲 另类 在线 欧美 制服 | 大地资源网第二页免费观看 | 欧美日本精品一区二区三区 | 强辱丰满人妻hd中文字幕 | 亚洲日韩精品欧美一区二区 | 久久亚洲国产成人精品性色 | 丰满诱人的人妻3 | 国产成人精品久久亚洲高清不卡 | 正在播放老肥熟妇露脸 | 人妻少妇精品久久 | 妺妺窝人体色www在线小说 | 任你躁在线精品免费 | 国产深夜福利视频在线 | 最近的中文字幕在线看视频 | a片在线免费观看 | 麻花豆传媒剧国产免费mv在线 | 国产精品va在线观看无码 | 特级做a爰片毛片免费69 | 伊人久久大香线蕉av一区二区 | 中文字幕 人妻熟女 | 精品人妻中文字幕有码在线 | 欧美精品免费观看二区 | 国产内射爽爽大片视频社区在线 | 人妻aⅴ无码一区二区三区 | 老子影院午夜精品无码 | 无码人妻精品一区二区三区不卡 | 日本护士xxxxhd少妇 | 99久久久国产精品无码免费 | 亚洲午夜无码久久 | 久久综合狠狠综合久久综合88 | 熟妇激情内射com | 国产精品无套呻吟在线 | 性欧美牲交xxxxx视频 | 四虎永久在线精品免费网址 | 久久精品无码一区二区三区 | av小次郎收藏 | 激情爆乳一区二区三区 | 麻豆国产人妻欲求不满谁演的 | 波多野结衣av一区二区全免费观看 | 成人精品视频一区二区三区尤物 | 国产精品无码成人午夜电影 | 久久久久99精品国产片 | 奇米影视7777久久精品 | 国产精品igao视频网 | 国产精品久久久久久久9999 | 亚洲中文字幕在线观看 | 蜜桃臀无码内射一区二区三区 | 中国女人内谢69xxxx | 日日噜噜噜噜夜夜爽亚洲精品 | 国产精品久久久久7777 | √天堂资源地址中文在线 | 日本xxxx色视频在线观看免费 | 女人和拘做爰正片视频 | 久久精品国产99久久6动漫 | 丰满人妻一区二区三区免费视频 | 亚洲gv猛男gv无码男同 | 午夜精品久久久内射近拍高清 | 国产午夜无码精品免费看 | 国产性猛交╳xxx乱大交 国产精品久久久久久无码 欧洲欧美人成视频在线 | 国产成人无码午夜视频在线观看 | 国产三级精品三级男人的天堂 | 国产精品成人av在线观看 | 青草青草久热国产精品 | 中文毛片无遮挡高清免费 | 东京热男人av天堂 | 国产sm调教视频在线观看 | 国产乱码精品一品二品 | 成人无码视频在线观看网站 | 国产乱子伦视频在线播放 | 狠狠cao日日穞夜夜穞av | 国产成人无码av片在线观看不卡 | aⅴ亚洲 日韩 色 图网站 播放 | 午夜精品久久久久久久 | 国产成人精品必看 | 日本免费一区二区三区最新 | 人人妻人人澡人人爽精品欧美 | 中文无码成人免费视频在线观看 | 中文久久乱码一区二区 | 国产精品自产拍在线观看 | 色五月五月丁香亚洲综合网 | 国产精品久久久 | 人妻少妇精品视频专区 | 亚洲欧美国产精品久久 | 俄罗斯老熟妇色xxxx | 午夜精品久久久久久久久 | 亚洲成a人片在线观看日本 | 国产深夜福利视频在线 | 4hu四虎永久在线观看 | 牲欲强的熟妇农村老妇女视频 | 中文字幕色婷婷在线视频 | 欧美阿v高清资源不卡在线播放 | 国产一区二区三区四区五区加勒比 | 亚洲国产av精品一区二区蜜芽 | 久久人妻内射无码一区三区 | 亚洲中文字幕在线无码一区二区 | 国产精品无码一区二区桃花视频 | 夜夜夜高潮夜夜爽夜夜爰爰 | 亚洲国产成人a精品不卡在线 | 77777熟女视频在线观看 а天堂中文在线官网 | 国产色xx群视频射精 | 又大又硬又爽免费视频 | 久久久久久久久888 | 小sao货水好多真紧h无码视频 | 国产乱码精品一品二品 | 99re在线播放 | 亚洲精品午夜无码电影网 | 国产精品久久福利网站 | 天天拍夜夜添久久精品大 | 亚洲 欧美 激情 小说 另类 | 色欲人妻aaaaaaa无码 | 男女爱爱好爽视频免费看 | 精品无码av一区二区三区 | 人人妻人人澡人人爽精品欧美 | 午夜丰满少妇性开放视频 | 婷婷六月久久综合丁香 | 日本熟妇人妻xxxxx人hd | 国产女主播喷水视频在线观看 | 精品偷自拍另类在线观看 | 蜜臀av在线播放 久久综合激激的五月天 | 久9re热视频这里只有精品 | 亚洲 另类 在线 欧美 制服 | 激情爆乳一区二区三区 | 欧美人与善在线com | 国产精品无码久久av | 人人澡人人妻人人爽人人蜜桃 | 色狠狠av一区二区三区 | 国产午夜无码视频在线观看 | 人人妻人人澡人人爽欧美一区 | 久久久久se色偷偷亚洲精品av | 精品亚洲韩国一区二区三区 | 久久99国产综合精品 | 国产成人午夜福利在线播放 | 欧美大屁股xxxxhd黑色 | 国产成人无码午夜视频在线观看 | 大地资源网第二页免费观看 | 美女毛片一区二区三区四区 | 中文字幕无码热在线视频 | 国产精品办公室沙发 | 无码国产激情在线观看 | 日本一区二区三区免费高清 | 美女极度色诱视频国产 | 大肉大捧一进一出好爽视频 | 国产农村乱对白刺激视频 | 精品夜夜澡人妻无码av蜜桃 | 动漫av网站免费观看 | 日韩av无码一区二区三区不卡 | 久久久久se色偷偷亚洲精品av | 国产精品高潮呻吟av久久4虎 | 亚洲国产日韩a在线播放 | 熟女少妇人妻中文字幕 | 色偷偷av老熟女 久久精品人妻少妇一区二区三区 | 国产口爆吞精在线视频 | 一个人免费观看的www视频 | 亚洲人成网站免费播放 | 国产激情一区二区三区 | 亚洲国产午夜精品理论片 | 精品乱码久久久久久久 | 欧美三级不卡在线观看 | 18禁黄网站男男禁片免费观看 | 婷婷五月综合激情中文字幕 | 红桃av一区二区三区在线无码av | 男人的天堂av网站 | 激情爆乳一区二区三区 | 国产人妻精品一区二区三区 | 在线观看国产午夜福利片 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 欧洲欧美人成视频在线 | 5858s亚洲色大成网站www | 无码av中文字幕免费放 | 牲交欧美兽交欧美 | 俄罗斯老熟妇色xxxx | 亚洲人成影院在线观看 | 久久久久成人精品免费播放动漫 | 99久久99久久免费精品蜜桃 | 性生交大片免费看女人按摩摩 | 欧美第一黄网免费网站 | 欧美精品国产综合久久 | 亚洲人成人无码网www国产 | 青青青手机频在线观看 | 丰满岳乱妇在线观看中字无码 | 国产激情一区二区三区 | 欧美日韩亚洲国产精品 | 老熟女乱子伦 | 国产熟妇另类久久久久 | 99精品无人区乱码1区2区3区 | 九九在线中文字幕无码 | 久久人人爽人人爽人人片ⅴ | а√资源新版在线天堂 | 亚洲性无码av中文字幕 | 日韩av激情在线观看 | 国产精品欧美成人 | 中文字幕无码av激情不卡 | 国产亚洲欧美日韩亚洲中文色 | 天天爽夜夜爽夜夜爽 | 免费人成网站视频在线观看 | 少妇太爽了在线观看 | 小sao货水好多真紧h无码视频 | 亚洲日韩av一区二区三区中文 | 国产真人无遮挡作爱免费视频 | 国产真实乱对白精彩久久 | 日本欧美一区二区三区乱码 | 国产亚洲精品久久久久久 | 无码av岛国片在线播放 | 特大黑人娇小亚洲女 | 色窝窝无码一区二区三区色欲 | 中文字幕人成乱码熟女app | 国产精品亚洲一区二区三区喷水 | www国产亚洲精品久久网站 | 亚洲精品国偷拍自产在线麻豆 | 2020久久香蕉国产线看观看 | 青草青草久热国产精品 | yw尤物av无码国产在线观看 | 日本精品少妇一区二区三区 | 日产精品高潮呻吟av久久 | 波多野结衣 黑人 | 国产亚洲欧美在线专区 | 国内丰满熟女出轨videos | 99久久亚洲精品无码毛片 | 国产精品成人av在线观看 | 国产精品久久福利网站 | 特级做a爰片毛片免费69 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 久久精品人妻少妇一区二区三区 | 日韩人妻系列无码专区 | 小鲜肉自慰网站xnxx | 亚洲国产精品久久久天堂 | 中文字幕+乱码+中文字幕一区 | 久久99精品国产麻豆蜜芽 | 性生交片免费无码看人 | 国产人妻久久精品二区三区老狼 | 久久亚洲日韩精品一区二区三区 | 亚洲色www成人永久网址 | 人人爽人人澡人人高潮 | 熟妇女人妻丰满少妇中文字幕 | 亚洲色www成人永久网址 | 荫蒂被男人添的好舒服爽免费视频 | 人妻与老人中文字幕 | 亚洲成av人综合在线观看 | 日韩精品成人一区二区三区 | 亚洲精品成人av在线 | 国产精品久久久久久久9999 | 秋霞成人午夜鲁丝一区二区三区 | 久久综合色之久久综合 | 亚洲欧洲中文日韩av乱码 | 欧美第一黄网免费网站 | 国产av一区二区精品久久凹凸 | 人妻互换免费中文字幕 | 亚洲一区二区三区播放 | 两性色午夜视频免费播放 | 欧洲精品码一区二区三区免费看 | 亚洲日本在线电影 | 疯狂三人交性欧美 | 日本爽爽爽爽爽爽在线观看免 | 国产精品久久久一区二区三区 | 丰满少妇高潮惨叫视频 | 99麻豆久久久国产精品免费 | 欧美日韩一区二区三区自拍 | 无码国产色欲xxxxx视频 | 色综合久久网 | 亚洲人交乣女bbw | 亚洲精品www久久久 | 日韩精品无码一区二区中文字幕 | 一个人看的www免费视频在线观看 | 日韩av无码一区二区三区 | 国产精品亚洲综合色区韩国 | 日韩 欧美 动漫 国产 制服 | 精品国产一区二区三区av 性色 | 久久精品人妻少妇一区二区三区 | 97色伦图片97综合影院 | 亚洲啪av永久无码精品放毛片 | 性做久久久久久久久 | 国产精品嫩草久久久久 | 久久久国产一区二区三区 | 人妻有码中文字幕在线 | 一本久道久久综合婷婷五月 | 亚洲人成网站在线播放942 | 激情内射亚州一区二区三区爱妻 | 波多野42部无码喷潮在线 | 麻豆国产97在线 | 欧洲 | 久热国产vs视频在线观看 | √8天堂资源地址中文在线 | 伊人色综合久久天天小片 | 日韩少妇内射免费播放 | 免费国产黄网站在线观看 | 久久久久人妻一区精品色欧美 | 少妇无码一区二区二三区 | 国产在线无码精品电影网 | 欧美人与善在线com | 樱花草在线播放免费中文 | 奇米影视7777久久精品人人爽 | 久久 国产 尿 小便 嘘嘘 | 亚洲精品国产a久久久久久 | 日产精品99久久久久久 | 亚洲国产av精品一区二区蜜芽 | 97精品人妻一区二区三区香蕉 | 国产激情无码一区二区app | 国产做国产爱免费视频 | 成熟妇人a片免费看网站 | 天海翼激烈高潮到腰振不止 | 中文字幕av日韩精品一区二区 | 日日鲁鲁鲁夜夜爽爽狠狠 | 亚洲国产精品久久久天堂 | 88国产精品欧美一区二区三区 | 精品国产乱码久久久久乱码 | 亚洲中文字幕在线观看 | 日韩少妇内射免费播放 | 亚洲色欲色欲天天天www | 国产精品久久久av久久久 | 亚洲成av人影院在线观看 | 人人妻人人澡人人爽精品欧美 | 国产成人亚洲综合无码 | 无码av岛国片在线播放 | 性欧美videos高清精品 | 精品一区二区不卡无码av | 日本精品久久久久中文字幕 | 国内精品久久毛片一区二区 | 真人与拘做受免费视频一 | 亚洲 激情 小说 另类 欧美 | 国产精品鲁鲁鲁 | 天堂久久天堂av色综合 | 18禁止看的免费污网站 | 奇米影视888欧美在线观看 | 无套内谢老熟女 | 国产精品久久福利网站 | 国产在线无码精品电影网 | 中文毛片无遮挡高清免费 | 国产精品va在线观看无码 | 麻豆精品国产精华精华液好用吗 | 天天av天天av天天透 | 亚洲欧美中文字幕5发布 | 国产香蕉尹人综合在线观看 | 国产凸凹视频一区二区 | 成人精品天堂一区二区三区 | 无码国产激情在线观看 | 蜜臀aⅴ国产精品久久久国产老师 | 久久久中文字幕日本无吗 | 亚洲爆乳无码专区 | 天天综合网天天综合色 | 国产成人无码午夜视频在线观看 | 97精品国产97久久久久久免费 | 欧美性生交活xxxxxdddd | 国内精品久久毛片一区二区 | 国产片av国语在线观看 | 午夜成人1000部免费视频 | 少妇被黑人到高潮喷出白浆 | 噜噜噜亚洲色成人网站 | 国色天香社区在线视频 | 77777熟女视频在线观看 а天堂中文在线官网 | 午夜精品久久久内射近拍高清 | 2020最新国产自产精品 | 久久伊人色av天堂九九小黄鸭 | 亚洲经典千人经典日产 | 国産精品久久久久久久 | 亚洲国产精品一区二区第一页 | 亚洲高清偷拍一区二区三区 | 久久精品一区二区三区四区 | 国产极品美女高潮无套在线观看 | 又粗又大又硬又长又爽 | 粗大的内捧猛烈进出视频 | 久久精品人妻少妇一区二区三区 | 亚洲日韩av片在线观看 | 国内精品人妻无码久久久影院蜜桃 | 中文字幕无码乱人伦 | 激情五月综合色婷婷一区二区 | 日本xxxx色视频在线观看免费 | 天堂а√在线地址中文在线 | 蜜桃av抽搐高潮一区二区 | 国产综合色产在线精品 | 少妇高潮一区二区三区99 | 久久精品人人做人人综合 | 国产精品a成v人在线播放 | 国产亚洲精品精品国产亚洲综合 | 特级做a爰片毛片免费69 | 99久久人妻精品免费一区 | 无码人妻精品一区二区三区下载 | 国产人妻精品一区二区三区不卡 | 国产色视频一区二区三区 | 九月婷婷人人澡人人添人人爽 | 又大又硬又黄的免费视频 | 国产极品美女高潮无套在线观看 | 成熟人妻av无码专区 | 欧美真人作爱免费视频 | 亚洲 高清 成人 动漫 | 午夜精品久久久久久久 | 久久这里只有精品视频9 | 99久久精品国产一区二区蜜芽 | 99久久99久久免费精品蜜桃 | 日本熟妇乱子伦xxxx | 久久这里只有精品视频9 | 亚洲综合色区中文字幕 | 国产香蕉尹人综合在线观看 | 亚洲人成网站在线播放942 | 日日碰狠狠丁香久燥 | 久久久久99精品国产片 | 美女张开腿让人桶 | 丰满妇女强制高潮18xxxx | 丰满人妻被黑人猛烈进入 | 18无码粉嫩小泬无套在线观看 | 国产精品高潮呻吟av久久 | 日韩视频 中文字幕 视频一区 | 波多野结衣av在线观看 | 亚洲人成影院在线无码按摩店 | 又粗又大又硬又长又爽 | 精品国产一区av天美传媒 | 在教室伦流澡到高潮hnp视频 | 疯狂三人交性欧美 | 精品国产青草久久久久福利 | 国产国产精品人在线视 | 在线 国产 欧美 亚洲 天堂 | 黑人巨大精品欧美黑寡妇 | 午夜无码区在线观看 | 国内精品一区二区三区不卡 | 野外少妇愉情中文字幕 | 内射后入在线观看一区 | 成人女人看片免费视频放人 | 国产精品无套呻吟在线 | 国产乡下妇女做爰 | 国产精品国产三级国产专播 | 亚洲自偷自偷在线制服 | 人妻aⅴ无码一区二区三区 | 午夜不卡av免费 一本久久a久久精品vr综合 | 欧美日韩亚洲国产精品 | 成人女人看片免费视频放人 | 亚洲第一无码av无码专区 | 亚洲精品午夜无码电影网 | 少妇无码一区二区二三区 | 国产精品-区区久久久狼 | 激情亚洲一区国产精品 | 成人毛片一区二区 | 18禁黄网站男男禁片免费观看 | av无码电影一区二区三区 | 乱人伦人妻中文字幕无码久久网 | 丁香花在线影院观看在线播放 | 蜜臀av在线观看 在线欧美精品一区二区三区 | 99国产精品白浆在线观看免费 | 国产美女极度色诱视频www | 波多野结衣av一区二区全免费观看 | 成人试看120秒体验区 | 日本精品少妇一区二区三区 | 国产精品久久国产三级国 | 日韩精品成人一区二区三区 | 日韩 欧美 动漫 国产 制服 | 精品国产麻豆免费人成网站 | 一本久久伊人热热精品中文字幕 | v一区无码内射国产 | 丰满少妇女裸体bbw | 鲁大师影院在线观看 | 我要看www免费看插插视频 | 久久久久亚洲精品中文字幕 | 国产一精品一av一免费 | 亚洲大尺度无码无码专区 | √8天堂资源地址中文在线 | 久久这里只有精品视频9 | 精品国偷自产在线 | 午夜无码人妻av大片色欲 | 国产精品久久久久久无码 | 国产麻豆精品一区二区三区v视界 | 伊人久久婷婷五月综合97色 | 精品欧美一区二区三区久久久 | 久久久婷婷五月亚洲97号色 | 日本一区二区三区免费播放 | 亚洲人成无码网www | 精品日本一区二区三区在线观看 | 欧美 日韩 亚洲 在线 | 国产精品二区一区二区aⅴ污介绍 | 久久久亚洲欧洲日产国码αv | 真人与拘做受免费视频一 | 自拍偷自拍亚洲精品10p | 草草网站影院白丝内射 | 麻豆果冻传媒2021精品传媒一区下载 | 丰满人妻一区二区三区免费视频 | 自拍偷自拍亚洲精品被多人伦好爽 | 天下第一社区视频www日本 | 色偷偷人人澡人人爽人人模 | 国产色精品久久人妻 | 国内精品久久毛片一区二区 | 婷婷丁香五月天综合东京热 | 国产精品第一国产精品 | 亚洲 日韩 欧美 成人 在线观看 | 精品一区二区三区无码免费视频 | 亚洲人成影院在线观看 | 亚洲日韩av片在线观看 | 亚洲日本在线电影 | 丁香啪啪综合成人亚洲 | 色综合久久88色综合天天 | 漂亮人妻洗澡被公强 日日躁 | 精品成在人线av无码免费看 | 国产网红无码精品视频 | av无码电影一区二区三区 | 久久久久久九九精品久 | www国产亚洲精品久久网站 | 精品 日韩 国产 欧美 视频 | 乱中年女人伦av三区 | 国产精品爱久久久久久久 | 亚洲色欲久久久综合网东京热 | 亚洲日韩一区二区 | 国产免费观看黄av片 | 香港三级日本三级妇三级 | 欧美激情一区二区三区成人 | 日韩人妻系列无码专区 | 成人性做爰aaa片免费看不忠 | 一个人免费观看的www视频 | 国内老熟妇对白xxxxhd | 国产明星裸体无码xxxx视频 | 亚洲熟妇色xxxxx亚洲 | 久久国语露脸国产精品电影 | 久久午夜无码鲁丝片 | 精品久久8x国产免费观看 | 国产精品嫩草久久久久 | 天堂а√在线中文在线 | 亚洲天堂2017无码中文 | 日本肉体xxxx裸交 | 欧美成人免费全部网站 | 纯爱无遮挡h肉动漫在线播放 | 无码人妻av免费一区二区三区 | 樱花草在线播放免费中文 | 婷婷色婷婷开心五月四房播播 | 国产精品免费大片 | 中文字幕乱码人妻二区三区 | 捆绑白丝粉色jk震动捧喷白浆 | 红桃av一区二区三区在线无码av | 亚洲精品一区二区三区四区五区 | 免费观看激色视频网站 | 日本va欧美va欧美va精品 | 女人被男人爽到呻吟的视频 | av人摸人人人澡人人超碰下载 | 在线欧美精品一区二区三区 | 亚洲熟女一区二区三区 | 国产极品美女高潮无套在线观看 | 日韩欧美中文字幕在线三区 | 精品国产国产综合精品 | 欧美午夜特黄aaaaaa片 | 亚洲国产av美女网站 | 婷婷六月久久综合丁香 | 成人精品视频一区二区三区尤物 | 大屁股大乳丰满人妻 | 亚洲 欧美 激情 小说 另类 | 久久久精品人妻久久影视 | 亚洲日韩一区二区 | 国产黄在线观看免费观看不卡 | 国产人妻久久精品二区三区老狼 | 天堂在线观看www | 亚洲阿v天堂在线 | 美女毛片一区二区三区四区 | 成人精品视频一区二区三区尤物 | 又紧又大又爽精品一区二区 | 少妇被粗大的猛进出69影院 | 樱花草在线社区www | 亚洲国产av精品一区二区蜜芽 | 国产片av国语在线观看 | 色综合视频一区二区三区 | 久青草影院在线观看国产 | 久久97精品久久久久久久不卡 | 亚洲国产精品毛片av不卡在线 | 在线成人www免费观看视频 | 奇米影视888欧美在线观看 | 日本又色又爽又黄的a片18禁 | 给我免费的视频在线观看 | 亚洲精品综合五月久久小说 | 性色欲情网站iwww九文堂 | 一个人看的www免费视频在线观看 | 国产精品二区一区二区aⅴ污介绍 | 精品无码国产自产拍在线观看蜜 | 亚洲 日韩 欧美 成人 在线观看 | 国产精品美女久久久久av爽李琼 | 丰满岳乱妇在线观看中字无码 | 巨爆乳无码视频在线观看 | 熟女少妇人妻中文字幕 | 国产av剧情md精品麻豆 | 内射老妇bbwx0c0ck | 自拍偷自拍亚洲精品10p | 久久国产自偷自偷免费一区调 | 少妇人妻偷人精品无码视频 | 成年美女黄网站色大免费全看 | 国产农村妇女高潮大叫 | www成人国产高清内射 | 又紧又大又爽精品一区二区 | 人妻人人添人妻人人爱 | 成人精品视频一区二区三区尤物 | 国产综合久久久久鬼色 | 国色天香社区在线视频 | 欧美放荡的少妇 | 少妇人妻偷人精品无码视频 | 国产午夜无码视频在线观看 | 成熟妇人a片免费看网站 | 伊人久久大香线焦av综合影院 | 精品久久久无码中文字幕 | 极品嫩模高潮叫床 | 18精品久久久无码午夜福利 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 欧美自拍另类欧美综合图片区 | 国产免费观看黄av片 | 美女毛片一区二区三区四区 | 欧美性猛交xxxx富婆 | 成人欧美一区二区三区 | 天天躁日日躁狠狠躁免费麻豆 | 国产亚洲精品久久久ai换 | 强奷人妻日本中文字幕 | 国产精品永久免费视频 | 国产精品怡红院永久免费 | 亚洲阿v天堂在线 | 国产真实夫妇视频 | 又大又硬又爽免费视频 | 人人妻人人澡人人爽欧美一区 | 久久精品中文字幕一区 | 在线播放无码字幕亚洲 | 久久久婷婷五月亚洲97号色 | 黑人粗大猛烈进出高潮视频 | 国产精品第一区揄拍无码 | 无码人中文字幕 | 国色天香社区在线视频 | 国产麻豆精品精东影业av网站 | 亚洲综合伊人久久大杳蕉 | 亚洲狠狠婷婷综合久久 | 国产av人人夜夜澡人人爽麻豆 | 麻豆av传媒蜜桃天美传媒 | 激情五月综合色婷婷一区二区 | 色噜噜亚洲男人的天堂 | 在线a亚洲视频播放在线观看 | 欧美人与牲动交xxxx | 大地资源网第二页免费观看 | 久9re热视频这里只有精品 | 成人无码精品一区二区三区 | 亚洲精品国产品国语在线观看 | 亚洲中文字幕av在天堂 | 人妻有码中文字幕在线 | 东京无码熟妇人妻av在线网址 | 国产极品视觉盛宴 | 性欧美熟妇videofreesex | 一本久久a久久精品vr综合 | 久久精品国产日本波多野结衣 | 国产成人无码区免费内射一片色欲 | 久久久精品欧美一区二区免费 | 内射爽无广熟女亚洲 | 精品无人国产偷自产在线 | 激情爆乳一区二区三区 | 国产9 9在线 | 中文 | 精品无码av一区二区三区 | 中文字幕av伊人av无码av | 玩弄人妻少妇500系列视频 | 蜜臀av在线观看 在线欧美精品一区二区三区 | 国产在线aaa片一区二区99 | 亚洲中文字幕无码中文字在线 | 国产深夜福利视频在线 | 亚拍精品一区二区三区探花 | 中文字幕无线码 | 人妻少妇精品无码专区二区 | 少妇人妻偷人精品无码视频 | 无码吃奶揉捏奶头高潮视频 | 18精品久久久无码午夜福利 | 噜噜噜亚洲色成人网站 | 国产精品久久久久久无码 | 国内精品人妻无码久久久影院蜜桃 | 欧美老妇交乱视频在线观看 | 精品无人国产偷自产在线 | 一本久道久久综合婷婷五月 | 九九久久精品国产免费看小说 | 成人免费视频在线观看 | 亚洲一区二区三区播放 | 日韩精品a片一区二区三区妖精 | 成人免费视频视频在线观看 免费 | 亚洲小说春色综合另类 | 亚洲国产精品一区二区第一页 | 野外少妇愉情中文字幕 | 亚洲一区二区三区播放 | 欧美国产亚洲日韩在线二区 | 亚洲精品一区国产 | 熟女少妇在线视频播放 | 精品无码国产一区二区三区av | 曰本女人与公拘交酡免费视频 | 日本护士毛茸茸高潮 | 十八禁真人啪啪免费网站 | 精品国精品国产自在久国产87 | 久久精品中文闷骚内射 | 亚洲综合无码久久精品综合 | 亚洲色欲久久久综合网东京热 | 红桃av一区二区三区在线无码av | 久久精品丝袜高跟鞋 | 日韩 欧美 动漫 国产 制服 | 亚洲阿v天堂在线 | 免费人成在线视频无码 | 久久精品中文字幕一区 | 亚洲精品久久久久久久久久久 | 亚洲国产精品无码一区二区三区 | 曰韩无码二三区中文字幕 | 激情人妻另类人妻伦 | 99riav国产精品视频 | 97久久超碰中文字幕 | 国产一区二区三区日韩精品 | 青青久在线视频免费观看 | 国产精品无码一区二区桃花视频 | 精品日本一区二区三区在线观看 | 亚洲中文字幕在线无码一区二区 | 久久久精品国产sm最大网站 | 亚洲春色在线视频 | 欧美日本精品一区二区三区 | 国产色视频一区二区三区 | 国产人妻人伦精品 | 亚洲日本一区二区三区在线 | 对白脏话肉麻粗话av | 久久综合给久久狠狠97色 | 成人片黄网站色大片免费观看 | 国内综合精品午夜久久资源 | 伊人久久大香线蕉亚洲 | 中文无码精品a∨在线观看不卡 | 国产欧美熟妇另类久久久 | 麻豆av传媒蜜桃天美传媒 | 国产精品办公室沙发 | 国产亚洲日韩欧美另类第八页 | 欧美丰满老熟妇xxxxx性 | 综合网日日天干夜夜久久 | 网友自拍区视频精品 | 伊人久久大香线蕉午夜 | 成人免费视频视频在线观看 免费 | 国产偷自视频区视频 | 在线观看欧美一区二区三区 | 免费看少妇作爱视频 | 精品久久综合1区2区3区激情 | 亚洲成在人网站无码天堂 | 久久天天躁狠狠躁夜夜免费观看 | 欧美 日韩 亚洲 在线 | 任你躁国产自任一区二区三区 | 成年美女黄网站色大免费视频 | 青青久在线视频免费观看 | 真人与拘做受免费视频一 | 免费国产成人高清在线观看网站 | 无码帝国www无码专区色综合 | 性生交大片免费看女人按摩摩 | 三级4级全黄60分钟 | 久久人人爽人人爽人人片ⅴ | 免费人成网站视频在线观看 | 国产精品怡红院永久免费 | 国产尤物精品视频 | 精品国精品国产自在久国产87 | 午夜福利试看120秒体验区 | 精品一二三区久久aaa片 | 国产精品无码永久免费888 | 无码精品人妻一区二区三区av | 国产性生大片免费观看性 | 国产 浪潮av性色四虎 | 免费观看激色视频网站 | 亚洲欧美色中文字幕在线 | 日日碰狠狠躁久久躁蜜桃 | 老头边吃奶边弄进去呻吟 | 久久国产精品萌白酱免费 | 亚洲第一网站男人都懂 | 男人的天堂2018无码 | 天堂а√在线中文在线 | 内射巨臀欧美在线视频 | 色综合久久久无码中文字幕 | 欧美老熟妇乱xxxxx | 国产激情无码一区二区 | 蜜臀av在线播放 久久综合激激的五月天 | 国产成人人人97超碰超爽8 | 97久久国产亚洲精品超碰热 | 76少妇精品导航 | 精品熟女少妇av免费观看 | 在线看片无码永久免费视频 | 夜先锋av资源网站 | 久久久www成人免费毛片 | 精品国产麻豆免费人成网站 | 欧洲熟妇色 欧美 | 成人性做爰aaa片免费看 | 日韩少妇内射免费播放 | 夫妻免费无码v看片 | 国产色精品久久人妻 | 国产情侣作爱视频免费观看 | 丰满少妇弄高潮了www | 日韩精品a片一区二区三区妖精 | 中文字幕人妻无码一区二区三区 | 在线看片无码永久免费视频 | 亚洲一区二区观看播放 | 欧美丰满老熟妇xxxxx性 | 大肉大捧一进一出好爽视频 | 99久久人妻精品免费一区 | 激情国产av做激情国产爱 | 双乳奶水饱满少妇呻吟 | 老熟妇乱子伦牲交视频 | 日本精品少妇一区二区三区 | 国产精品无码永久免费888 | 日本熟妇大屁股人妻 | 亚洲人成网站色7799 | 久久五月精品中文字幕 | 天天躁日日躁狠狠躁免费麻豆 | 国产精品嫩草久久久久 | 色综合视频一区二区三区 | 在线成人www免费观看视频 | 日本精品少妇一区二区三区 | 亚洲综合无码久久精品综合 | 国产亲子乱弄免费视频 | 久久精品中文字幕大胸 | 日本xxxx色视频在线观看免费 | 亚洲欧洲日本无在线码 | 欧美三级不卡在线观看 | 强奷人妻日本中文字幕 | av小次郎收藏 | 亚洲大尺度无码无码专区 | 欧美猛少妇色xxxxx | 欧美亚洲日韩国产人成在线播放 | 亚洲综合色区中文字幕 | 大地资源中文第3页 | 久久精品99久久香蕉国产色戒 | 免费观看又污又黄的网站 | 国产成人综合在线女婷五月99播放 | 国内揄拍国内精品少妇国语 | 嫩b人妻精品一区二区三区 | 人妻夜夜爽天天爽三区 | 四虎国产精品一区二区 | 在线成人www免费观看视频 | 亚洲国产一区二区三区在线观看 | 99精品视频在线观看免费 | 人妻少妇被猛烈进入中文字幕 | 国产亚洲tv在线观看 | 性啪啪chinese东北女人 | 精品久久久久久人妻无码中文字幕 | 娇妻被黑人粗大高潮白浆 | 精品国产一区av天美传媒 | 麻豆国产人妻欲求不满 | 强伦人妻一区二区三区视频18 | 亚无码乱人伦一区二区 | 红桃av一区二区三区在线无码av | 欧美大屁股xxxxhd黑色 | 亚洲爆乳精品无码一区二区三区 | www国产亚洲精品久久久日本 | 奇米影视7777久久精品 | 露脸叫床粗话东北少妇 | 亚洲国产成人a精品不卡在线 | 国産精品久久久久久久 | 狠狠躁日日躁夜夜躁2020 | 樱花草在线社区www | 成人精品视频一区二区 | 台湾无码一区二区 | 97久久国产亚洲精品超碰热 | 日韩精品一区二区av在线 | 国产无遮挡又黄又爽免费视频 | 黄网在线观看免费网站 | 少妇无套内谢久久久久 | 性欧美牲交在线视频 | 麻豆精产国品 | 搡女人真爽免费视频大全 | 国产三级精品三级男人的天堂 | 红桃av一区二区三区在线无码av | 国产电影无码午夜在线播放 | 成人试看120秒体验区 | 精品国产一区二区三区av 性色 | 丰满妇女强制高潮18xxxx | 大地资源网第二页免费观看 | 噜噜噜亚洲色成人网站 | 亚洲熟妇色xxxxx欧美老妇 | 免费观看又污又黄的网站 | 久久99国产综合精品 | 午夜精品一区二区三区在线观看 | 亚洲大尺度无码无码专区 | 中文无码成人免费视频在线观看 | 日日摸天天摸爽爽狠狠97 | 性生交大片免费看l | 免费无码av一区二区 | 精品乱子伦一区二区三区 | 日韩精品一区二区av在线 | 动漫av一区二区在线观看 | 亚洲精品无码国产 | 久久综合久久自在自线精品自 | 久久久国产一区二区三区 | 国产农村妇女高潮大叫 | 大肉大捧一进一出好爽视频 | 亚洲成av人片在线观看无码不卡 | 国产美女精品一区二区三区 | 婷婷五月综合激情中文字幕 | 中文字幕人妻丝袜二区 | 少妇的肉体aa片免费 | 97夜夜澡人人双人人人喊 | 亚洲の无码国产の无码步美 | 亚洲s码欧洲m码国产av | 一区二区三区乱码在线 | 欧洲 | 人妻插b视频一区二区三区 | 日产精品99久久久久久 | 亚洲欧美日韩国产精品一区二区 | 国产精品va在线观看无码 | 欧美怡红院免费全部视频 | 国产精品久久久久无码av色戒 | 国产深夜福利视频在线 | 人妻中文无码久热丝袜 | 中文字幕无线码免费人妻 | 欧美亚洲日韩国产人成在线播放 | 少女韩国电视剧在线观看完整 | 18禁止看的免费污网站 | 日韩精品一区二区av在线 | 激情人妻另类人妻伦 | 男女爱爱好爽视频免费看 | 青青青爽视频在线观看 | 大色综合色综合网站 | 成人一在线视频日韩国产 | 久久久久久av无码免费看大片 | 成年美女黄网站色大免费全看 | 高潮毛片无遮挡高清免费视频 | 男女猛烈xx00免费视频试看 | 性欧美牲交xxxxx视频 | av人摸人人人澡人人超碰下载 | 日本精品高清一区二区 | 久久久中文字幕日本无吗 | 狂野欧美性猛交免费视频 | 亚洲日韩精品欧美一区二区 | 真人与拘做受免费视频 | 在线观看国产午夜福利片 | 国产高清av在线播放 | 无码一区二区三区在线观看 | 丰满少妇高潮惨叫视频 | 图片小说视频一区二区 | 亚洲爆乳大丰满无码专区 | 久久99久久99精品中文字幕 | 欧美喷潮久久久xxxxx | 久久久精品欧美一区二区免费 | 人妻无码αv中文字幕久久琪琪布 | aⅴ亚洲 日韩 色 图网站 播放 | 国产精品99久久精品爆乳 | 成人综合网亚洲伊人 | 亚洲人成网站免费播放 | 全黄性性激高免费视频 | 日韩人妻无码中文字幕视频 | 麻豆md0077饥渴少妇 | 精品一二三区久久aaa片 | 亚洲の无码国产の无码步美 | 99re在线播放 | 女人被爽到呻吟gif动态图视看 | 国产亚洲精品久久久久久国模美 | 国产无遮挡又黄又爽免费视频 | 狠狠综合久久久久综合网 | 少妇邻居内射在线 | 人妻互换免费中文字幕 | 性欧美大战久久久久久久 | 久久综合网欧美色妞网 | 大胆欧美熟妇xx | 国产无套粉嫩白浆在线 | 丰满少妇女裸体bbw | 国产美女精品一区二区三区 | 亚洲爆乳无码专区 | 国产亚洲精品久久久ai换 | 亚洲s码欧洲m码国产av | 精品无人区无码乱码毛片国产 | 亚洲欧美综合区丁香五月小说 | 国产在线无码精品电影网 | 中文字幕日产无线码一区 | 精品一二三区久久aaa片 | 麻豆果冻传媒2021精品传媒一区下载 | 亚洲成a人一区二区三区 | 亚洲综合久久一区二区 | 亚洲国产精品久久久天堂 | 超碰97人人做人人爱少妇 | 国产午夜手机精彩视频 | 激情亚洲一区国产精品 | 亚洲热妇无码av在线播放 | 久久久精品456亚洲影院 | 成 人 网 站国产免费观看 | 日韩视频 中文字幕 视频一区 | 国产亚洲精品久久久ai换 | 中国女人内谢69xxxxxa片 | 国产精品无码一区二区桃花视频 | 久久97精品久久久久久久不卡 | 久久伊人色av天堂九九小黄鸭 | 国产偷国产偷精品高清尤物 | 国内精品九九久久久精品 | 学生妹亚洲一区二区 | 成 人 免费观看网站 | 丰满妇女强制高潮18xxxx | 精品无码一区二区三区的天堂 | 强开小婷嫩苞又嫩又紧视频 | 高清不卡一区二区三区 | 国产精品无码一区二区桃花视频 | 波多野结衣高清一区二区三区 | 国产又爽又猛又粗的视频a片 | 久久综合久久自在自线精品自 | 中文亚洲成a人片在线观看 | 精品乱码久久久久久久 | 久久久精品欧美一区二区免费 | 久久午夜无码鲁丝片秋霞 | 亚洲理论电影在线观看 | 2020久久香蕉国产线看观看 | 亚洲精品无码国产 | 日韩av无码一区二区三区不卡 | 丁香啪啪综合成人亚洲 | 国产成人久久精品流白浆 | 亚洲热妇无码av在线播放 | 国产偷国产偷精品高清尤物 | 欧美国产亚洲日韩在线二区 | 日韩精品乱码av一区二区 | 久久综合香蕉国产蜜臀av | 亚洲精品国偷拍自产在线麻豆 | 欧美变态另类xxxx | 好屌草这里只有精品 | 国产乱人伦偷精品视频 | 国产成人av免费观看 | 俺去俺来也在线www色官网 | 国产一区二区三区四区五区加勒比 | 国产电影无码午夜在线播放 | 激情人妻另类人妻伦 | 麻豆国产人妻欲求不满 | 午夜精品一区二区三区在线观看 | 国产小呦泬泬99精品 | 免费男性肉肉影院 | 99久久精品午夜一区二区 | 国内揄拍国内精品少妇国语 | 日韩无码专区 | 狂野欧美性猛交免费视频 | 精品久久久中文字幕人妻 | 亚洲无人区一区二区三区 | 在线天堂新版最新版在线8 | 国产综合色产在线精品 | 国产无遮挡吃胸膜奶免费看 | 日韩av激情在线观看 | 色欲人妻aaaaaaa无码 | 精品国产福利一区二区 | 成在人线av无码免观看麻豆 | 青青草原综合久久大伊人精品 | 无码av最新清无码专区吞精 | 欧美xxxx黑人又粗又长 | 扒开双腿疯狂进出爽爽爽视频 | 黑人玩弄人妻中文在线 | 欧美放荡的少妇 | 欧美日韩久久久精品a片 | 免费中文字幕日韩欧美 | 欧美 丝袜 自拍 制服 另类 | 亚洲 高清 成人 动漫 | 欧美精品国产综合久久 | 国产精品久久久久久久影院 | 亚洲中文字幕无码一久久区 | 日韩人妻少妇一区二区三区 | 97夜夜澡人人爽人人喊中国片 | 无码人妻丰满熟妇区五十路百度 | 在线播放免费人成毛片乱码 | 亚洲精品久久久久avwww潮水 | 成人一在线视频日韩国产 | 国产午夜福利100集发布 | 亚洲精品国产品国语在线观看 | 天下第一社区视频www日本 | 蜜桃无码一区二区三区 | 丰满少妇人妻久久久久久 | 久久 国产 尿 小便 嘘嘘 | 免费乱码人妻系列无码专区 | 亚洲一区二区三区偷拍女厕 | 国产va免费精品观看 | 最近免费中文字幕中文高清百度 | 亚洲国产成人a精品不卡在线 | 久精品国产欧美亚洲色aⅴ大片 | 国产suv精品一区二区五 | 人妻尝试又大又粗久久 | 亚洲精品欧美二区三区中文字幕 | 人妻aⅴ无码一区二区三区 | 动漫av网站免费观看 | 在线视频网站www色 | 日本一区二区三区免费高清 | 天堂亚洲2017在线观看 | 狠狠色噜噜狠狠狠7777奇米 | 欧美成人免费全部网站 | 国产精品亚洲五月天高清 | 国精产品一品二品国精品69xx | 欧美 亚洲 国产 另类 | 丁香啪啪综合成人亚洲 | 亚洲熟悉妇女xxx妇女av | 人人超人人超碰超国产 | 国产精品99久久精品爆乳 | 我要看www免费看插插视频 | 亚洲欧美日韩国产精品一区二区 | 粉嫩少妇内射浓精videos | 亚洲国产精品一区二区第一页 | 国产综合色产在线精品 | 亚洲乱码日产精品bd | 中文字幕人妻无码一夲道 | 美女张开腿让人桶 | 国产真实乱对白精彩久久 | 午夜免费福利小电影 | 日韩欧美中文字幕在线三区 | 装睡被陌生人摸出水好爽 | 伊人久久大香线焦av综合影院 | 无码任你躁久久久久久久 | 亚洲精品一区二区三区大桥未久 | 国产精品无套呻吟在线 | 国产超级va在线观看视频 | 性色欲网站人妻丰满中文久久不卡 | 日韩欧美中文字幕公布 | 久久99精品久久久久婷婷 | 奇米影视7777久久精品 | 亚洲精品国产品国语在线观看 | 少妇一晚三次一区二区三区 | 女人被爽到呻吟gif动态图视看 | 麻豆果冻传媒2021精品传媒一区下载 | 内射白嫩少妇超碰 | 欧美日韩在线亚洲综合国产人 | 日韩精品成人一区二区三区 | 免费国产成人高清在线观看网站 | 成人aaa片一区国产精品 | 国产人妖乱国产精品人妖 | 一区二区三区高清视频一 | 乌克兰少妇性做爰 | 国产精品久久久一区二区三区 | 亚洲熟悉妇女xxx妇女av | 亚洲一区二区三区在线观看网站 | 又粗又大又硬毛片免费看 | 亚洲色无码一区二区三区 | 99er热精品视频 | 国产精品人人爽人人做我的可爱 | 日韩av无码一区二区三区不卡 | 粉嫩少妇内射浓精videos | 色欲av亚洲一区无码少妇 | 波多野结衣高清一区二区三区 | 国产乱码精品一品二品 | 亚洲成a人片在线观看无码3d | 欧美日韩在线亚洲综合国产人 | 中文字幕无码日韩欧毛 | 日韩精品乱码av一区二区 | 精品厕所偷拍各类美女tp嘘嘘 | 日韩精品无码免费一区二区三区 | 久久人人爽人人爽人人片ⅴ | 国产超碰人人爽人人做人人添 | 内射白嫩少妇超碰 | 中文字幕无线码免费人妻 | 无码国内精品人妻少妇 | 男人和女人高潮免费网站 | 水蜜桃av无码 | 麻豆成人精品国产免费 | 久久精品视频在线看15 | 岛国片人妻三上悠亚 | 一本大道伊人av久久综合 | 牲欲强的熟妇农村老妇女视频 | 国产一精品一av一免费 | 99国产欧美久久久精品 | 国産精品久久久久久久 | 性生交大片免费看女人按摩摩 | 永久免费观看美女裸体的网站 | 亚洲色无码一区二区三区 | 精品人人妻人人澡人人爽人人 | www一区二区www免费 |