UIKit框架(四十三) —— CALayer的簡單實(shí)用示例(一)

版本記錄

版本號(hào) 時(shí)間
V1.0 2020.07.10 星期五

前言

iOS中有關(guān)視圖控件用戶能看到的都在UIKit框架里面钉跷,用戶交互也是通過UIKit進(jìn)行的宣蔚。感興趣的參考上面幾篇文章。
1. UIKit框架(一) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(一)
2. UIKit框架(二) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(二)
3. UIKit框架(三) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(一)
4. UIKit框架(四) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(二)
5. UIKit框架(五) —— 自定義控件:可重復(fù)使用的滑塊(一)
6. UIKit框架(六) —— 自定義控件:可重復(fù)使用的滑塊(二)
7. UIKit框架(七) —— 動(dòng)態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(一)
8. UIKit框架(八) —— 動(dòng)態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(二)
9. UIKit框架(九) —— UICollectionView的數(shù)據(jù)異步預(yù)加載(一)
10. UIKit框架(十) —— UICollectionView的數(shù)據(jù)異步預(yù)加載(二)
11. UIKit框架(十一) —— UICollectionView的重用迄本、選擇和重排序(一)
12. UIKit框架(十二) —— UICollectionView的重用硕淑、選擇和重排序(二)
13. UIKit框架(十三) —— 如何創(chuàng)建自己的側(cè)滑式面板導(dǎo)航(一)
14. UIKit框架(十四) —— 如何創(chuàng)建自己的側(cè)滑式面板導(dǎo)航(二)
15. UIKit框架(十五) —— 基于自定義UICollectionViewLayout布局的簡單示例(一)
16. UIKit框架(十六) —— 基于自定義UICollectionViewLayout布局的簡單示例(二)
17. UIKit框架(十七) —— 基于自定義UICollectionViewLayout布局的簡單示例(三)
18. UIKit框架(十八) —— 基于CALayer屬性的一種3D邊欄動(dòng)畫的實(shí)現(xiàn)(一)
19. UIKit框架(十九) —— 基于CALayer屬性的一種3D邊欄動(dòng)畫的實(shí)現(xiàn)(二)
20. UIKit框架(二十) —— 基于UILabel跑馬燈類似效果的實(shí)現(xiàn)(一)
21. UIKit框架(二十一) —— UIStackView的使用(一)
22. UIKit框架(二十二) —— 基于UIPresentationController的自定義viewController的轉(zhuǎn)場和展示(一)
23. UIKit框架(二十三) —— 基于UIPresentationController的自定義viewController的轉(zhuǎn)場和展示(二)
24. UIKit框架(二十四) —— 基于UICollectionViews和Drag-Drop在兩個(gè)APP間的使用示例 (一)
25. UIKit框架(二十五) —— 基于UICollectionViews和Drag-Drop在兩個(gè)APP間的使用示例 (二)
26. UIKit框架(二十六) —— UICollectionView的自定義布局 (一)
27. UIKit框架(二十七) —— UICollectionView的自定義布局 (二)
28. UIKit框架(二十八) —— 一個(gè)UISplitViewController的簡單實(shí)用示例 (一)
29. UIKit框架(二十九) —— 一個(gè)UISplitViewController的簡單實(shí)用示例 (二)
30. UIKit框架(三十) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的簡單示例(一)
31. UIKit框架(三十一) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的簡單示例(二)
32. UIKit框架(三十二) —— 替換Peek and Pop交互的基于iOS13的Context Menus(一)
33. UIKit框架(三十三) —— 替換Peek and Pop交互的基于iOS13的Context Menus(二)
34. UIKit框架(三十四) —— Accessibility的使用(一)
35. UIKit框架(三十五) —— Accessibility的使用(二)
36. UIKit框架(三十六) —— UICollectionView UICollectionViewDiffableDataSource的使用(一)
37. UIKit框架(三十七) —— UICollectionView UICollectionViewDiffableDataSource的使用(二)
38. UIKit框架(三十八) —— 基于CollectionView轉(zhuǎn)盤效果的實(shí)現(xiàn)(一)
39. UIKit框架(三十九) —— iOS 13中UISearchController 和 UISearchBar的新更改(一)
40. UIKit框架(四十) —— iOS 13中UISearchController 和 UISearchBar的新更改(二)
41. UIKit框架(四十一) —— 使用協(xié)議構(gòu)建自定義Collection(一)
42. UIKit框架(四十二) —— 使用協(xié)議構(gòu)建自定義Collection(二)

開始

首先看下主要內(nèi)容:

在本文中,您將了解CALayer及其工作原理嘉赎。 您將使用CALayer以獲得酷炫的效果置媳,例如形狀,漸變和粒子系統(tǒng)公条。本文內(nèi)容來自翻譯拇囊。

接著看一下寫作環(huán)境:

Swift 5, iOS 13, Xcode 11

下面就是正文了

您可能知道,在iOS應(yīng)用中看到的所有內(nèi)容都是視圖靶橱。有按鈕視圖寂拆,表格視圖,滑塊視圖抓韩,甚至包含其他視圖的父視圖纠永!

但是您可能不知道,iOS中的每個(gè)視圖都由另一個(gè)稱為圖層的類(具體來說就是CALayer)支持谒拴。

在本文中尝江,您將構(gòu)建Layer Player應(yīng)用程序。在此過程中英上,您將學(xué)到:

  • 什么是CALayer及其運(yùn)作方式炭序。
  • 如何使用CALayer功能來實(shí)現(xiàn)酷效果,例如形狀苍日,漸變甚至粒子系統(tǒng)惭聂。

CALayer具有多種屬性和方法可以修改。它具有幾個(gè)具有獨(dú)特屬性和方法的子類相恃。

Layer Player應(yīng)用程序演示了九種不同的CALayer功能辜纲。

入門項(xiàng)目并沒有做什么用。您將要編寫代碼以將此應(yīng)用程序變成功能齊全的CALayer指導(dǎo)!

在每個(gè)部分中耕腾,您都將添加必要的代碼以使神奇的圖層發(fā)揮作用见剩。 添加了每一行代碼后,請(qǐng)按照剛剛啟用的設(shè)置進(jìn)行操作扫俺。 這將使您對(duì)所探索的每種功能的功能有更深入的了解苍苞。

但是首先,來一些理論狼纬。


How does CALayer relate to UIView?

UIView處理許多事情羹呵,包括布局和觸摸事件。 但是疗琉,它不能直接控制圖形或動(dòng)畫担巩。 UIKit將該任務(wù)委托給Core Animation框架,從而可以使用CALayer没炒。 實(shí)際上涛癌,UIView只是CALayer的封裝。

每個(gè)UIView都有一個(gè)根CALayer送火,可以包含多個(gè)子層拳话。 當(dāng)您在UIView上設(shè)置bounds時(shí),該視圖又在其支持CALayer上設(shè)置bounds种吸。 如果在UIView上調(diào)用layoutIfNeeded()弃衍,則該調(diào)用將轉(zhuǎn)發(fā)到根CALayer

在深入研究Layer Player應(yīng)用程序之前坚俗,還應(yīng)該了解一些有關(guān)CALayer的知識(shí):

  • Layers can have sublayers - 層可以具有子層:就像視圖可以具有子視圖一樣镜盯,層也可以具有子層。 您可以將它們用于一些很酷的效果猖败!
  • Their properties are animatable - 它們的屬性是可設(shè)置動(dòng)畫的:更改圖層的屬性時(shí)速缆,可以使用CAAnimation為更改設(shè)置動(dòng)畫。
  • Layers are lightweight - 圖層重量輕:圖層的重量比視圖輕恩闻,因此可以幫助您獲得更好的性能艺糜。
  • They have tons of useful properties - 它們具有大量有用的屬性:您將在以下示例中進(jìn)行探索。

現(xiàn)在幢尚,您就可以開始使用CALayer創(chuàng)建一些自定義視圖了破停。


Customizing Views With CALayer

打開CALayerViewController.swift并將以下代碼添加到setupLayer()中:

//1
layer.frame = viewForLayer.bounds
layer.contents = UIImage(named: "star")?.cgImage

// 2
layer.contentsGravity = .center
layer.magnificationFilter = .linear

// 3
layer.cornerRadius = 100.0
layer.borderWidth = 12.0
layer.borderColor = UIColor.white.cgColor
layer.backgroundColor = swiftOrangeColor.cgColor

//4
layer.shadowOpacity = 0.75
layer.shadowOffset = CGSize(width: 0, height: 3)
layer.shadowRadius = 3.0
layer.isGeometryFlipped = false

在此代碼中,您將創(chuàng)建一個(gè)自定義視圖:

  • 1)設(shè)置圖層layer的邊界bounds尉剩,然后將圖像設(shè)置為圖層的內(nèi)容真慢。 注意底層CGImage的使用。
  • 2)然后理茎,將圖像居中放置在圖層中黑界。 您可以使用contentsGravity來更改大泄苕摇(如resizing, resizing aspect and resizing aspect fill)以及位置(中心,頂部园爷,右上角,右邊等)式撼。magnificationFilter控制放大圖像的行為童社。
  • 3)接下來,設(shè)置CALayer的背景顏色著隆,使其變圓并為其添加邊框扰楼。 請(qǐng)注意,您正在使用基礎(chǔ)CGColor對(duì)象更改圖層的顏色屬性美浦。
  • 4)最后弦赖,為圖層創(chuàng)建陰影。 當(dāng)isGeometryFlippedtrue時(shí)浦辨,位置幾何形狀和陰影將上下顛倒蹬竖。

構(gòu)建并運(yùn)行。 選擇CALayer流酬,然后檢查結(jié)果:

控件目前不執(zhí)行任何操作币厕。 因此,添加以下內(nèi)容以到prepare(for:sender:)

if segue.identifier == "DisplayLayerControls" {
  (segue.destination as? CALayerControlsViewController)?.layerViewController = self
}

這將連接嵌入式CALayerControlsViewController芽腾。 再次構(gòu)建并運(yùn)行旦装,然后檢查實(shí)際使用的圖層屬性。 試玩各種控件摊滔,以感受一下使用CALayer可以做什么阴绢!

1. Improving Performance With ShouldRasterize and DrawsAsynchronously

CALayer具有兩個(gè)其他可改善性能的屬性:shouldRasterizedrawsAsynchronously

默認(rèn)情況下艰躺,shouldRasterizefalse呻袭。設(shè)置為true時(shí),圖層的內(nèi)容僅渲染一次腺兴,從而提高了性能棒妨。非常適合在屏幕上動(dòng)起來但外觀不變的對(duì)象。

drawsAsynchronouslyshouldRasterize相反含长,默認(rèn)情況下也為false券腔。當(dāng)應(yīng)用需要重復(fù)重繪圖層內(nèi)容時(shí),將其設(shè)置為true可以提高性能拘泞。例如纷纫,當(dāng)您使用連續(xù)渲染動(dòng)畫粒子的發(fā)射器層時(shí),可能會(huì)發(fā)生這種情況陪腌。您將在本教程的后面部分中使用此功能辱魁。

注意:在設(shè)置shouldRasterizedrawsAsynchronously之前烟瞧,請(qǐng)先考慮其含義。更改設(shè)置并比較性能染簇。這將幫助您確定在給定情況下激活這些功能是否實(shí)際上會(huì)提高性能参滴。如果您濫用這些屬性,性能可能會(huì)降低锻弓!


Scrolling With CAScrollLayer

CAScrollLayer顯示可滾動(dòng)圖層的一部分砾赔。 這是非常基本的-它無法直接響應(yīng)用戶的觸摸青灼,甚至無法檢查可滾動(dòng)圖層的bounds暴心。 但這確實(shí)很酷,例如防止?jié)L動(dòng)超出bounds杂拨。

UIScrollView不使用CAScrollLayer來完成其工作专普。 而是直接更改圖層的bounds。 使用CAScrollLayer弹沽,您可以:

  • 將滾動(dòng)模式設(shè)置為水平或垂直(horizontal or vertical)檀夹。
  • 以編程方式滾動(dòng)到特定點(diǎn)point或區(qū)域area

構(gòu)建并運(yùn)行策橘,然后從菜單中選擇CAScrollLayer击胜。 您會(huì)看到一張圖片和兩個(gè)控制滾動(dòng)方向的開關(guān)。

現(xiàn)在是時(shí)候設(shè)置滾動(dòng)了役纹。

1. Setting up Scrolling

返回代碼并打開CAScrollLayerViewController.swift偶摔。 視圖中已經(jīng)有一個(gè)CAScrollLayer

將以下內(nèi)容添加到panRecognized(_ :)

var newPoint = scrollingView.bounds.origin
newPoint.x -= sender.translation(in: scrollingView).x
newPoint.y -= sender.translation(in: scrollingView).y
sender.setTranslation(.zero, in: scrollingView)
scrollingViewLayer.scroll(to: newPoint)
    
if sender.state == .ended {
  UIView.animate(withDuration: 0.3) {
    self.scrollingViewLayer.scroll(to: CGPoint.zero)
  }
}

發(fā)生平移手勢(shì)時(shí)促脉,您需要計(jì)算所需的相應(yīng)平移辰斋,然后在CAScrollLayer上調(diào)用scroll(to :)方法以相應(yīng)地移動(dòng)圖像。

scroll(to :)不會(huì)自動(dòng)設(shè)置動(dòng)畫瘸味,因此您可以使用UIView.animate(withDuration:animations :)對(duì)其進(jìn)行明確設(shè)置宫仗。

構(gòu)建并運(yùn)行,然后返回到CAScrollLayer示例旁仿。 現(xiàn)在藕夫,平移圖像時(shí),您將看到類似以下的內(nèi)容:

Layer Player還包括兩個(gè)控件枯冈,用于鎖定水平和垂直滾動(dòng)毅贮。 接下來,您將看一下這部分尘奏。

2. Locking Scrolling Directions

將以下代碼添加到scrollingSwitchChanged(_ :)

switch (horizontalScrollingSwitch.isOn, verticalScrollingSwitch.isOn) {
case (true, true):
  scrollingViewLayer.scrollMode = .both
case (true, false):
  scrollingViewLayer.scrollMode = .horizontally
case (false, true):
  scrollingViewLayer.scrollMode = .vertically
default:
  scrollingViewLayer.scrollMode = .none
}

在這里滩褥,您添加了一個(gè)簡單的switch塊。 它根據(jù)用戶界面中開關(guān)的值確定滾動(dòng)方向炫加。

現(xiàn)在構(gòu)建并運(yùn)行瑰煎。 返回應(yīng)用程序铺然,撥動(dòng)開關(guān),然后查看CAScrollLayer的行為酒甸。

以下是何時(shí)使用或不使用CAScrollLayer的一些經(jīng)驗(yàn)法則:

  • 如果您想要輕巧(lightweight)的東西并且只需要以編程方式滾動(dòng)魄健,請(qǐng)考慮使用CAScrollLayer
  • 如果希望用戶能夠滾動(dòng)插勤,最好使用UIScrollView沽瘦。 要了解更多信息,請(qǐng)查看我們的Scroll View School視頻教程系列饮六。
  • 如果要滾動(dòng)很大的圖像其垄,請(qǐng)考慮使用CATiledLayer苛蒲。

Rendering Text With CATextLayer

CATextLayer提供簡單但快速的純文本或?qū)傩宰址秩尽?與UILabel不同卤橄,CATextLayer不能具有分配的UIFont,只能具有CTFontCGFont臂外。

打開CATextLayerViewController.swift并將以下內(nèi)容添加到setUpTextLayer()的末尾:

// 1
textLayer.font = helveticaFont
textLayer.fontSize = Constants.baseFontSize

// 2
textLayer.foregroundColor = UIColor.darkGray.cgColor
textLayer.isWrapped = true
textLayer.alignmentMode = .left
textLayer.truncationMode = .end

// 3
textLayer.contentsScale = UIScreen.main.scale

這是您正在做的事情:

  • 1) 您設(shè)置文本圖層的字體窟扑。請(qǐng)注意,這是CTFont漏健,而不是UIFont嚎货。您可以使用CTFontCreateWithName(_:_:_ :)創(chuàng)建這些文件。 CATextLayer允許您像在此處一樣直接在圖層上設(shè)置字體大小蔫浆。
  • 2) 接下來殖属,設(shè)置文本顏色,換行瓦盛,對(duì)齊和截?cái)嗄J较聪浴K羞@些都可以在常規(guī)UILabelUITextView上獲得。
  • 3) 最后原环,將圖層的contentsScale設(shè)置為與屏幕的比例相匹配挠唆。默認(rèn)情況下,所有圖層類(不僅是CATextLayer)都以比例因子1進(jìn)行渲染嘱吗。將圖層附加到視圖會(huì)自動(dòng)將其contentScale設(shè)置為當(dāng)前屏幕的適當(dāng)比例因子玄组。但是,對(duì)于您手動(dòng)創(chuàng)建的任何圖層谒麦,必須顯式設(shè)置contentsScale俄讹。否則,其比例因子將為1绕德,從而使其在Retina顯示器上顯示為像素化颅悉。

構(gòu)建并運(yùn)行,然后從菜單中選擇CATextLayer迁匠。 Layer Player具有可以更改許多CATextLayer屬性的控件剩瓶。和他們一起玩驹溃,看看他們做什么:

接下來,您將使Layer Player能夠播放媒體文件延曙。


Playing Media With AVPlayerLayer

AVPlayerLayerAVFoundation增添了美好的一面豌鹤。 它擁有一個(gè)AVPlayer來播放AVPlayerItem類型的媒體文件。

1. Setting up Media Playback

打開AVPlayerLayerViewController.swift并將以下代碼添加到setUpPlayerLayer()中:

// 1
playerLayer.frame = viewForPlayerLayer.bounds

// 2
let url = Bundle.main.url(forResource: "colorfulStreak", withExtension: "m4v")!
let item = AVPlayerItem(asset: AVAsset(url: url))
let player = AVPlayer(playerItem: item)

// 3
player.actionAtItemEnd = .none

// 4
player.volume = 1.0
player.rate = 1.0


playerLayer.player = player

在此代碼中枝缔,您設(shè)置了播放器布疙。這是如何做:

  • 1) 首先,設(shè)置圖層的frame愿卸。
  • 2) 接下來灵临,使用AVPlayerItem創(chuàng)建一個(gè)player
  • 3) 您告訴播放器完成播放后什么也不做趴荸。其他選項(xiàng)包括暫腿甯龋或前進(jìn)到下一個(gè)資源(如果適用)。
  • 4) 最后发钝,設(shè)置播放器的音量和速率顿涣。

現(xiàn)在您可以播放媒體文件了,您將使用戶能夠更改他們的播放速度酝豪。

2. Changing Playback Speeds

rate設(shè)置視頻播放速度涛碑。 0表示暫停,1表示視頻以正常速度播放孵淘。

但是蒲障,設(shè)置速率rate也會(huì)指示以該速率開始播放。換句話說瘫证,調(diào)用pause()并將rate設(shè)置為0作用是一樣的揉阎,同樣調(diào)用play()并將rate設(shè)置為1作用也是一樣!

那么快進(jìn)痛悯,慢動(dòng)作或反向播放呢余黎?好吧,AVPlayer都可以滿足您载萌!

當(dāng)您將rate設(shè)置為高于1的任何值時(shí)惧财,播放將以該速度乘以常規(guī)速度開始。例如扭仁,將rate設(shè)置為2表示倍速垮衷。并將rate設(shè)置為負(fù)數(shù)會(huì)導(dǎo)致以該速度乘以正常速度反向播放。

但是乖坠,在以常規(guī)速度向前播放以外的任何速率進(jìn)行播放之前搀突,應(yīng)檢查AVPlayerItem中的相應(yīng)變量以驗(yàn)證是否可以該速率播放:

  • canPlayFastForward大于1的任何數(shù)字。
  • canPlaySlowForward的范圍為0到最大(但不包括1)之間的任何數(shù)字熊泵。
  • canPlayReverse-1仰迁。
  • canPlaySlowReverse的范圍是-1到最大(但不包括0)之間的任何數(shù)字甸昏。
  • canPlayFastReverse的值小于-1

大多數(shù)視頻可以以各種前進(jìn)速度播放徐许,但很少可以反向播放施蜜。

3. Updating the User Interface

現(xiàn)在,回到代碼雌隅。當(dāng)您點(diǎn)擊播放按鈕時(shí)翻默,它應(yīng)切換控件以播放AVAset并設(shè)置按鈕的標(biāo)題。

將以下內(nèi)容添加到playButtonTapped(_ :)

if player?.rate == 0 {
  player?.rate = rate
  updatePlayButtonTitle(isPlaying: true)
} else {
  player?.pause()
  updatePlayButtonTitle(isPlaying: false)
}

在此恰起,您可以根據(jù)初始速率切換播放器的狀態(tài)并更新播放按鈕的標(biāo)題修械。

最后,您將添加代碼检盼,以在播放器到達(dá)媒體文件的末尾時(shí)將播放光標(biāo)移回開頭肯污。

4. Resetting the Playback Cursor

viewDidLoad()中,您可以看到AVPlayerItemDidPlayToEndTimeNotification具有observer梯皿。 當(dāng)AVPlayerItem到達(dá)末尾時(shí)仇箱,將調(diào)用此通知县恕。

將以下代碼添加到playerDidReachEndNotificationHandler(_ :)

// 1
guard let playerItem = notification.object as? AVPlayerItem else { return }

// 2
playerItem.seek(to: .zero, completionHandler: nil)
    
// 3
if player?.actionAtItemEnd == .pause {
  player?.pause()
  updatePlayButtonTitle(isPlaying: false)
}

在此代碼中:

  • 1) 您驗(yàn)證通知對(duì)象是AVPlayerItem东羹。
  • 2) 您可以使用seek(to:completionHandler :)將播放器發(fā)送到所需位置吨述。 在這種情況下钾菊,您將播放器發(fā)送到CMTime.zero這是開始枚粘。
  • 3) 如果播放器的actionAtItemEnd設(shè)置為.pause苇羡,則可以暫停播放器谣光,并相應(yīng)地設(shè)置按鈕文本罩锐。

構(gòu)建并運(yùn)行侦高,然后從菜單中選擇AVPlayerLayer停巷。 更改控件上的值师坎,以查看每個(gè)控件如何更改圖層的行為恕酸。

下一步,您將看到使用CALayer制作炫酷漸變非常容易胯陋。


Color Blending With CAGradientLayer

CAGradientLayer使混合兩種或多種顏色變得容易蕊温,這使其特別適合于背景。您可以通過分配以下內(nèi)容進(jìn)行配置:

  • CGColors數(shù)組遏乔。
  • 一個(gè)開始點(diǎn)startPoint义矛,用于指定漸變圖層應(yīng)從何處開始。
  • 一個(gè)Endpoint盟萨,用于指定漸變層應(yīng)在何處結(jié)束凉翻。

請(qǐng)記住,startPointendPoint不是顯式點(diǎn)捻激。而是在單位坐標(biāo)空間中定義它們制轰,并在繪制它們時(shí)將它們映射到圖層的邊界前计。換一種說法:

  • x值為1表示該點(diǎn)位于圖層的右邊緣。
  • y值為1表示該點(diǎn)位于圖層的底部邊緣垃杖。

CAGradientLayer具有type屬性残炮,可用于更改漸變圖案。默認(rèn)(也是最常用的)是axial(線性)缩滨。這意味著势就,如果在startPointendPoint之間繪制一條線(A),則漸變將沿著垂直于A的假想線(B)發(fā)生脉漏,并且B上的所有點(diǎn)都將具有相同的顏色:

或者苞冯,您可以使用locations屬性控制漸變圖案。 location接受一個(gè)介于01之間的值的數(shù)組侧巨。這些值指定相對(duì)停止點(diǎn)舅锄,漸變層應(yīng)在其中使用colors數(shù)組中的下一個(gè)顏色。 如果未指定司忱,則默認(rèn)情況下停止位置的間隔均勻皇忿。

注意:如果設(shè)置locations,則其數(shù)量必須與colors的數(shù)量匹配坦仍,否則會(huì)發(fā)生不良情況鳍烁。 所以要當(dāng)心!

打開CAGradientLayerViewController.swift并將以下代碼添加到setUpGradientLayer()中:

gradientLayer.frame = viewForGradientLayer.bounds
gradientLayer.colors = colors
gradientLayer.locations = locations.map { NSNumber(value: $0) }
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)

在此代碼中繁扎,您為圖層分配了一個(gè)顏色數(shù)組幔荒,并設(shè)置了起點(diǎn)和終點(diǎn)。

構(gòu)建并運(yùn)行梳玫,從菜單中選擇CAGradientLayer爹梁,然后查看結(jié)果:

好豐富多彩! Layer Player為您提供了更改起點(diǎn)和終點(diǎn)提澎,顏色和位置的控件姚垃。 嘗試他們的樂趣,看看您能得到什么盼忌。

對(duì)于下一步积糯,您將向Layer Player添加加載動(dòng)畫。


Creating a Loading Animation with CAReplicatorLayer

CAReplicatorLayer將圖層復(fù)制指定的次數(shù)碴犬。 這使您可以創(chuàng)建一些令人驚訝的炫酷效果絮宁!

每個(gè)圖層副本可以具有自己的顏色和位置更改。 您可以延遲其繪制服协,以使整個(gè)復(fù)制器層具有動(dòng)畫效果绍昂。

構(gòu)建并運(yùn)行,然后從菜單中選擇CAReplicatorLayer。 您會(huì)看到幾個(gè)控件……但沒有什么可控制:

首先窘游,配置要復(fù)制的圖層唠椭。

1. Configuring Replicator Layers

返回Xcode并打開CAReplicatorLayerViewController.swift。 在// MARK: - Layer setup下有三種方法要填充忍饰。

首先贪嫂,將以下內(nèi)容添加到setUpReplicatorLayer()中:

// 1
replicatorLayer.frame = viewForReplicatorLayer.bounds

// 2
let count = instanceCountSlider.value
replicatorLayer.instanceCount = Int(count)
replicatorLayer.instanceDelay = 
  CFTimeInterval(instanceDelaySlider.value / count)

// 3
replicatorLayer.instanceColor = UIColor.white.cgColor
replicatorLayer.instanceRedOffset = offsetValueForSwitch(offsetRedSwitch)
replicatorLayer.instanceGreenOffset = offsetValueForSwitch(offsetGreenSwitch)
replicatorLayer.instanceBlueOffset = offsetValueForSwitch(offsetBlueSwitch)
replicatorLayer.instanceAlphaOffset = offsetValueForSwitch(offsetAlphaSwitch)

//4
let angle = Float.pi * 2.0 / count
replicatorLayer.instanceTransform = 
  CATransform3DMakeRotation(CGFloat(angle), 0.0, 0.0, 1.0)

//5
viewForReplicatorLayer.layer.addSublayer(replicatorLayer)

在此代碼中,配置復(fù)制圖層(replicator layer)的實(shí)例:

  • 1) 首先艾蓝,設(shè)置圖層的frame力崇。
  • 2) 您可以設(shè)置要?jiǎng)?chuàng)建的復(fù)制數(shù)量以及兩次創(chuàng)建之間的時(shí)間延遲。
  • 3) 您可以定義所有復(fù)制實(shí)例的基礎(chǔ)顏色以及要添加到每個(gè)顏色組件的增量值赢织。 每個(gè)默認(rèn)值均為0亮靴,可有效保留所有實(shí)例的顏色值。 但是于置,在這種情況下茧吊,實(shí)例顏色最初設(shè)置為白色。 這意味著紅色八毯,綠色和藍(lán)色已經(jīng)是1.0搓侄。 例如,如果將紅色設(shè)置為0话速,并將綠色和藍(lán)色設(shè)置為負(fù)數(shù)讶踪,則紅色將成為突出的顏色。 同樣尿孔,將alpha偏移量添加到每個(gè)連續(xù)復(fù)制實(shí)例的alpha中俊柔。
  • 4) 現(xiàn)在筹麸,旋轉(zhuǎn)每個(gè)實(shí)例以創(chuàng)建一個(gè)圓形效果活合。
  • 5) 最后,將圖層添加到視圖的圖層物赶。

2. Setting Position and Fade

接下來白指,為replicator layer創(chuàng)建一個(gè)實(shí)例層以供使用。 將以下內(nèi)容添加到setUpInstanceLayer

let layerWidth = CGFloat(layerSizeSlider.value)
let midX = viewForReplicatorLayer.bounds.midX - layerWidth / 2.0
instanceLayer.frame = CGRect(
  x: midX,
  y: 0.0,
  width: layerWidth,
  height: layerWidth * lengthMultiplier)
instanceLayer.backgroundColor = UIColor.white.cgColor
replicatorLayer.addSublayer(instanceLayer)

此代碼設(shè)置實(shí)例frame酵紫。 在這里告嘲,第一個(gè)實(shí)例將繪制在中心xviewForReplicatorLayerbounds的頂部。 然后奖地,您設(shè)置實(shí)例的顏色并將實(shí)例層添加到replicator layer橄唬。

現(xiàn)在用于淡入淡出動(dòng)畫。 將以下內(nèi)容添加到setUpLayerFadeAnimation中:

fadeAnimation.fromValue = 1.0
fadeAnimation.toValue = 0.0
fadeAnimation.repeatCount = Float(Int.max)

在這里参歹,您使用CABasicAnimationfadeAnimation將實(shí)例的不透明度從1(不透明)更改為0(透明)仰楚。

構(gòu)建并運(yùn)行并享受您的代碼現(xiàn)在所做的工作:

Layer Player包含用于操縱大多數(shù)這些屬性的控件。 更改值,看看它們?nèi)绾斡绊憚?dòng)畫僧界!

現(xiàn)在侨嘀,您已經(jīng)創(chuàng)建了一個(gè)有趣的動(dòng)畫圓,接下來將使用CALayer的另一個(gè)屬性繪制星星捂襟。


Drawing a Star With CAShapeLayer

CAShapeLayer使用可縮放的矢量路徑繪制線條咬腕,形狀和圖案-比使用圖片要快得多! 另一個(gè)好處是葬荷,您無需提供常規(guī)的@ 2x@ 3x尺寸的圖像涨共。

此外,您還可以使用各種屬性來自定義圖形宠漩。 例如煞赢,您可以調(diào)整線的粗細(xì),顏色哄孤,虛線并指定線的連接方式照筑。 而且,當(dāng)您的線條形成形狀時(shí)瘦陈,您可以更改其填充方式凝危。

現(xiàn)在該畫一個(gè)橙色的星星了! 首先晨逝,打開CAShapeLayerViewController.swift并將以下內(nèi)容添加到setUpShapeLayer()中:

// 1
shapeLayer.path = openPath.cgPath

// 2
shapeLayer.lineCap = .butt
shapeLayer.lineJoin = .miter
shapeLayer.miterLimit = 4.0

// 3
shapeLayer.lineWidth = CGFloat(lineWidthSlider.value)
shapeLayer.strokeColor = swiftOrangeColor.cgColor
shapeLayer.fillColor = UIColor.white.cgColor

// 4
shapeLayer.lineDashPattern = nil
shapeLayer.lineDashPhase = 0.0

viewForShapeLayer.layer.addSublayer(shapeLayer)

經(jīng)歷這個(gè):

  • 1) CAShapeLayer采用CGPath蛾默,它定義了如何在屏幕上繪制它。 您很快就會(huì)畫出這條路捉貌。
  • 2) lineJoinlineCap定義路徑中的線如何相交和結(jié)束支鸡。
  • 3) 設(shè)置路徑線的寬度和顏色。
  • 4) lineDashPatternlineDashPhase允許您繪制虛線(dashed lines)趁窃。 在這種情況下牧挣,您將繪制不帶dashes的簡單線條。

接下來醒陆,您將繪制路徑本身瀑构。 將以下內(nèi)容添加到setUpPath()中:

openPath.move(to: CGPoint(x: 30, y: 196))
    
openPath.addCurve(
  to: CGPoint(x: 112.0, y: 12.5),
  controlPoint1: CGPoint(x: 110.56, y: 13.79),
  controlPoint2: CGPoint(x: 112.07, y: 13.01))
    
openPath.addCurve(
  to: CGPoint(x: 194, y: 196),
  controlPoint1: CGPoint(x: 111.9, y: 11.81),
  controlPoint2: CGPoint(x: 194, y: 196))
    
openPath.addLine(to: CGPoint(x: 30.0, y: 85.68))
openPath.addLine(to: CGPoint(x: 194.0, y: 48.91))
openPath.addLine(to: CGPoint(x: 30, y: 196))

這將繪制您的星形。

構(gòu)建并運(yùn)行刨摩,然后選擇CAShapeLayer以查看結(jié)果:

Layer Player包含用于操縱許多CAShapeLayer屬性的控件寺晌。 玩這些控件來調(diào)整星星:

CALayer不僅可以讓您繪制2D圖形,還可以繪制3D形狀澡刹。 這就是您在下一節(jié)中要做的呻征。


Drawing a 3D Cube With CATransformLayer

CATransformLayer不會(huì)像其他圖層類一樣展平其子圖層層次結(jié)構(gòu)。 這使得繪制3D結(jié)構(gòu)非常方便罢浇。 CATransformLayer實(shí)際上是其子層的容器陆赋。 每個(gè)子層可以具有自己的transformsopacity更改边篮。 但是,它忽略對(duì)其他渲染圖層屬性(如邊框?qū)挾群皖伾┑母摹?/p>

在本部分中奏甫,您將構(gòu)建一個(gè)交互式3D立方體戈轿。 首先打開CATransformLayerViewController.swift并將以下代碼添加到buildCube()中:

// 1
transformLayer = CATransformLayer()

// 2
let redLayer = sideLayer(color: redColor)
redLayer.addSublayer(swipeMeTextLayer)
transformLayer.addSublayer(redLayer)
    
// 3
let orangeLayer = sideLayer(color: orangeColor)
var orangeTransform = CATransform3DMakeTranslation(
  sideLength / 2.0, 
  0.0,
  sideLength / -2.0)
orangeTransform = CATransform3DRotate(
  orangeTransform,
  degreesToRadians(90.0), 
  0.0, 
  1.0, 
  0.0)
orangeLayer.transform = orangeTransform
transformLayer.addSublayer(orangeLayer)

在這里,您創(chuàng)建cube的前兩個(gè)側(cè)面:

  • 1) 首先阵子,創(chuàng)建一個(gè)CATransformerLayer思杯。
  • 2) 接下來,添加一個(gè)CALayer來表示cube的紅色面挠进,并將其添加到transformLayer`色乾。
  • 3) 然后,添加cube的橙色面领突。 您可以通過設(shè)置transform屬性來定位它暖璧,該屬性接受CATransform3D`。

同樣君旦,添加以下代碼:

let yellowLayer = sideLayer(color: yellowColor)
yellowLayer.transform = CATransform3DMakeTranslation(0.0, 0.0, -sideLength)
transformLayer.addSublayer(yellowLayer)
        
let greenLayer = sideLayer(color: greenColor)
var greenTransform = CATransform3DMakeTranslation(
  sideLength / -2.0, 
  0.0,
  sideLength / -2.0)
greenTransform = CATransform3DRotate(
  greenTransform,
  degreesToRadians(90.0), 
  0.0, 
  1.0, 
  0.0)
greenLayer.transform = greenTransform
transformLayer.addSublayer(greenLayer)
        
let blueLayer = sideLayer(color: blueColor)
var blueTransform = CATransform3DMakeTranslation(
  0.0, 
  sideLength / -2.0,
  sideLength / -2.0)
blueTransform = CATransform3DRotate(
  blueTransform,
  degreesToRadians(90.0), 
  1.0, 
  0.0, 
  0.0)
blueLayer.transform = blueTransform
transformLayer.addSublayer(blueLayer)
        
let purpleLayer = sideLayer(color: purpleColor)
var purpleTransform = CATransform3DMakeTranslation(
  0.0, 
  sideLength / 2.0,
  sideLength / -2.0)
purpleTransform = CATransform3DRotate(
  purpleTransform,
  degreesToRadians(90.0), 
  1.0, 
  0.0, 
  0.0)
purpleLayer.transform = purpleTransform
transformLayer.addSublayer(purpleLayer)

在這里澎办,創(chuàng)建cube的其余四個(gè)面。

最后金砍,設(shè)置圖層的z錨點(diǎn)局蚀,然后通過添加以下內(nèi)容將其添加到屏幕上:

transformLayer.anchorPointZ = sideLength / -2.0
viewForTransformLayer.layer.addSublayer(transformLayer)

在這里,您可以配置anchorPointZ恕稠,以指定發(fā)生幾何操作的z軸上的錨點(diǎn)琅绅。

注意:要了解有關(guān)矩陣變換的更多信息,請(qǐng)查看以下練習(xí):

現(xiàn)在,您已經(jīng)創(chuàng)建了cube骆捧,是時(shí)候設(shè)置它了澎羞。

1. Rotating the Cube

要旋轉(zhuǎn)cube,您需要處理觸摸事件凑懂。 您無法直接點(diǎn)擊hit test變換圖層(transform layer)煤痕,因?yàn)樗鼪]有2D坐標(biāo)空間可用于映射接觸點(diǎn)。 但是接谨,可以對(duì)各個(gè)子層進(jìn)行測試。

該項(xiàng)目代碼已經(jīng)包括Bill DudneyTrackBall utility實(shí)用程序塘匣,該實(shí)用程序已移植到Swift脓豪。 這使基于用戶手勢(shì)的3D transform變得容易。 您可以在// MARK: - Touch Handlingcheck it out忌卤。

構(gòu)建并運(yùn)行扫夜。 從菜單中選擇CATransformLayer,您會(huì)看到一個(gè)紅色正方形:

滑動(dòng)cube并切換開關(guān)以查看它們?nèi)绾胃?code>cube:


Making a Shooting Star With CAEmitterLayer

在計(jì)算機(jī)圖形學(xué)中,粒子系統(tǒng)Particle Systems用于生成現(xiàn)實(shí)世界的現(xiàn)象笤闯,例如火堕阔,煙,火花颗味,煙花和爆炸超陆。

Core Animation中,CAEmitterLayer可用于渲染此類系統(tǒng)并控制作為CAEmitterCell實(shí)例的動(dòng)畫粒子浦马。 CAEmitterLayerCAEmitterCell都具有更改渲染速率时呀,大小,形狀晶默,顏色谨娜,速度,壽命等的屬性磺陡。

1. Preparing the Emitter Layer

發(fā)射層配置發(fā)射粒子的位置和形狀趴梢。

將以下內(nèi)容添加到CAEmitterLayerViewController.swift中的setUpEmitterLayer()中:

// 1
resetEmitterCells()
emitterLayer.frame = viewForEmitterLayer.bounds
viewForEmitterLayer.layer.addSublayer(emitterLayer)

// 2
emitterLayer.seed = UInt32(Date().timeIntervalSince1970)

// 3
emitterLayer.emitterPosition = CGPoint(
  x: viewForEmitterLayer.bounds.midX * 1.5, 
  y: viewForEmitterLayer.bounds.midY)

// 4
emitterLayer.renderMode = .additive

這是您所做的:

  • 1) 首先,重置圖層并將其添加到視圖中币他。
  • 2) 然后垢油,為該層的隨機(jī)數(shù)生成器提供種子。 反過來圆丹,這會(huì)隨機(jī)化層的發(fā)射器單元的某些屬性滩愁,例如速度。 有關(guān)更多信息辫封,請(qǐng)參見下一個(gè)代碼塊之后的以下注釋硝枉。
  • 3) 您設(shè)置發(fā)射器位置。
  • 4) 最后倦微,定義粒子單元如何渲染到圖層中妻味。

2. Setting up the Emitter Cell

接下來,設(shè)置發(fā)射器單元(emitter cell)欣福,該單元確定粒子的特定行為和外觀责球。

將以下內(nèi)容添加到setUpEmitterCell()中:

// 1
emitterCell.contents = UIImage(named: "smallStar")?.cgImage
    
// 2
emitterCell.velocity = 50.0
emitterCell.velocityRange = 500.0
    
// 3 
emitterCell.color = UIColor.black.cgColor

// 4
emitterCell.redRange = 1.0
emitterCell.greenRange = 1.0
emitterCell.blueRange = 1.0
emitterCell.alphaRange = 0.0
emitterCell.redSpeed = 0.0
emitterCell.greenSpeed = 0.0
emitterCell.blueSpeed = 0.0
emitterCell.alphaSpeed = -0.5
emitterCell.scaleSpeed = 0.1
    
// 5
let zeroDegreesInRadians = degreesToRadians(0.0)
emitterCell.spin = degreesToRadians(130.0)
emitterCell.spinRange = zeroDegreesInRadians
emitterCell.emissionLatitude = zeroDegreesInRadians
emitterCell.emissionLongitude = zeroDegreesInRadians
emitterCell.emissionRange = degreesToRadians(360.0)
    
// 6
emitterCell.lifetime = 1.0
emitterCell.birthRate = 250.0

// 7
emitterCell.xAcceleration = -800
emitterCell.yAcceleration = 1000

逐步進(jìn)行以下操作:

  • 1) 您可以通過將發(fā)射器單元的內(nèi)容設(shè)置為圖像來設(shè)置發(fā)射器單元。該圖像在Layer Player項(xiàng)目中可用拓劝。
  • 2) 然后雏逾,使用VelocityRange指定初始速度和最大方差。發(fā)射器層使用上述種子創(chuàng)建隨機(jī)數(shù)生成器郑临。隨機(jī)數(shù)生成器通過使用初始值加上和減去范圍值來隨機(jī)化范圍內(nèi)的值栖博。對(duì)于以Range結(jié)尾的所有屬性,都會(huì)發(fā)生這種隨機(jī)化厢洞。
  • 3) 您將顏色設(shè)置為黑色仇让。這樣可以使方差與默認(rèn)的白色不同典奉,從而導(dǎo)致粒子過亮。
  • 4) 接下來丧叽,使用與velocityRange相同的隨機(jī)化設(shè)置一系列顏色范圍卫玖。這次,隨機(jī)化指定每種顏色的隨機(jī)范圍踊淳。速度值決定了每種顏色在單元的整個(gè)生命周期內(nèi)變化的速度假瞬。
  • 5) 在這里,您可以指定如何在整個(gè)圓錐周圍分布cell嚣崭。您可以通過設(shè)置發(fā)射器單元的旋轉(zhuǎn)速度和發(fā)射范圍來實(shí)現(xiàn)笨触。 emissionRange定義以弧度指定的圓錐。 emissionRange確定如何在錐體周圍分布發(fā)射器單元雹舀。
  • 6) 您將cell的壽命設(shè)置為1秒芦劣。該屬性的默認(rèn)值為0,因此说榆,如果您未顯式設(shè)置它虚吟,則永遠(yuǎn)不會(huì)顯示cell!每秒的birthRate也是如此签财。 birthRate的默認(rèn)值為0串慰,因此必須將其設(shè)置為正數(shù)才能顯示cell
  • 7) 最后唱蒸,設(shè)置cell xy的加速度邦鲫。這些值會(huì)影響粒子發(fā)射的視角。

構(gòu)建并運(yùn)行神汹,然后選擇CAEmitterLayer庆捺。結(jié)果如下:

返回Xcode,找到prepare(for:sender :)并取消注釋該代碼以連接控件表屁魏。

現(xiàn)在滔以,再次構(gòu)建并運(yùn)行該應(yīng)用程序,并使用控件來調(diào)整所有上述屬性以及其他一些屬性:

恭喜你氓拼! 您已經(jīng)完成了精彩的CALayer之旅你画。 您已經(jīng)看到了9個(gè)有關(guān)如何使用CALayer及其許多子類的示例。

但是不要在這里停下來桃漾! 打開一個(gè)新項(xiàng)目坏匪,或使用現(xiàn)有項(xiàng)目之一,然后看看如何使用圖層呈队。 您可能可以實(shí)現(xiàn)更好的性能剥槐,或者可以添加一些新的動(dòng)畫來讓您的用戶以及您自己贊嘆不已!

后記

本篇主要講述了CALayer的簡單實(shí)用示例宪摧,感興趣的給個(gè)贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末粒竖,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子几于,更是在濱河造成了極大的恐慌蕊苗,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沿彭,死亡現(xiàn)場離奇詭異朽砰,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)喉刘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門瞧柔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人睦裳,你說我怎么就攤上這事造锅。” “怎么了廉邑?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵哥蔚,是天一觀的道長。 經(jīng)常有香客問我蛛蒙,道長糙箍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任牵祟,我火速辦了婚禮深夯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘诺苹。我一直安慰自己咕晋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布筝尾。 她就那樣靜靜地躺著捡需,像睡著了一般。 火紅的嫁衣襯著肌膚如雪筹淫。 梳的紋絲不亂的頭發(fā)上站辉,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音损姜,去河邊找鬼饰剥。 笑死,一個(gè)胖子當(dāng)著我的面吹牛摧阅,可吹牛的內(nèi)容都是我干的汰蓉。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼棒卷,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼顾孽!你這毒婦竟也來了祝钢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤若厚,失蹤者是張志新(化名)和其女友劉穎拦英,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體测秸,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疤估,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了霎冯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铃拇。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖沈撞,靈堂內(nèi)的尸體忽然破棺而出慷荔,到底是詐尸還是另有隱情,我是刑警寧澤关串,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布拧廊,位于F島的核電站,受9級(jí)特大地震影響晋修,放射性物質(zhì)發(fā)生泄漏吧碾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一墓卦、第九天 我趴在偏房一處隱蔽的房頂上張望倦春。 院中可真熱鬧,春花似錦落剪、人聲如沸睁本。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呢堰。三九已至,卻和暖如春凡泣,著一層夾襖步出監(jiān)牢的瞬間枉疼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國打工鞋拟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留骂维,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓贺纲,卻偏偏與公主長得像航闺,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容