每個開發者在設計程式介面時都會用上不同的顏色組合和圖像,務求製作出賣相更吸引的作品。在顏色配搭上,單色使用有時候不免顯得單調,而漸層 gradient 的使用或許可以帶來意想不到的效果。我曾經好幾次在設計時運用了漸層顏色,感覺這個題目值得跟大家討論一下,就是這樣這篇文章便「誕生」了。它在技術上使用簡單,開發者們一定會喜歡這個簡單又好看的功能。
那麼,怎樣才能既快速又輕鬆地設定漸層?這裡為大家提供三個方案。第一個,也是不太建議使用的方法,就是使用包含漸變效果的圖像。原因是缺乏靈活性,視覺上雖然不一定有分別,但對於開發者而言就不能隨意調整漸變度,若有更改就得重新上載圖像。第二個方案是使用 Core Graphics 技術,它比較適合進階開發者,對相關知識(例如圖形上下文、顏色空間等等)有一定程度的認知要求,對菜鳥來說恐怕會顯得艱深難明(除非你想越級挑戰咯)。最後來到第三個方案,一個既簡單又快捷的途徑:使用 CAGradientLayer
物件。
每一個視圖物件都包含了 CALayer
類別,而作為子類別的 CAGradientLayer
正正就是為了製作漸層效果而生。只需要簡單的四行程式碼便能應用,還有幾個屬性可為效果作出微調,而且絕大部份都能動畫化。下文會作詳細的分享,現在我們先看看layer
視圖,正是CAGradientLayer
類別實際上處理漸層效果的地方。使用 CAGradientLayer
的不足之處是暫不支援 radial
漸層效果,不過線性漸層已經足夠應付一般使用。
接著下來我會更詳細地說明每一個微調漸層效果的屬性。我將會使用兩種比較色彩鮮明的顏色來作出舉例,而只取用兩種顏色是因為要簡單明顯地展示效果。然而,只要學懂了技巧,要用上兩種以上的顏色作漸層也是可以的。
製作漸層
製作漸層是一個快捷簡單的工作,當中已包括一些指定的動作。事實上,你只需要設定少量屬性便能用上漸層效果。然而,微調漸層效果卻是更花時間的一部分,不同的值都會帶來不同的效果,總得花上些心神去尋找出最合適的配搭值,製作出更完美的效果。
我們將會逐步製作和討論漸層效果,但我們先需要一支程式來作範例。打開 Xcode 並建立一個新程式,確認選擇 Single View Application 範例。
現在我們需要建立一個新的專案,在專案導覽器點擊 ViewController.swift
檔案。在類別開首宣告以下屬性:
var gradientLayer: CAGradientLayer!
gradientLayer
屬性將會是我們的測試物件。在這個小小的實作中,我們將會通過以下簡單步驟在目標視圖中製作一個漸層:
- 初始化
CAGradientLayer
物件 (即是本範例的gradientLayer
)。 - 為漸層設定框架。
- 設定漸層顏色。
- 在目標視圖層增加漸層子層。
事實上,開始以上步驟前,需要配置的屬性還有很多,這個我們接著下來會再作研究。現在,我們先集中在這四個步驟。務求簡單的闡述,我們將會使用預設視圖 viewController
類別為目標視圖,並為它製作漸層顏色。
在 ViewController
類別建立一個新方法,為 gradientLayer
屬性作出初始化以及設定一些預設值:
func createGradientLayer() { gradientLayer = CAGradientLayer() gradientLayer.frame = self.view.bounds gradientLayer.colors = [UIColor.redColor().CGColor, UIColor.yellowColor().CGColor] self.view.layer.addSublayer(gradientLayer) }
上列的程式碼,首先把 gradientLayer
物件作出初始化。然後為它設定框架,把它的邊界設定與視圖控制器濶度相等。接下來,我們要指定漸層效果選用的顏色,最後在預設視圖層增加漸層子層。
使用以下程式碼呼叫 viewWillAppear(_:)
方法:
override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) createGradientLayer() }
現在試一下執行程式,可以得到以下的效果:
只是用了四行程式碼便得到這樣的效果,不錯吧!讓我們再深入一點。
漸層顏色
儘管前面的程式碼十分簡單易用,但實際上是包含了一個重要的屬性:colors
屬性。首先,不用我多說也知道,沒有使用它來設定顏色,根本就不會顯示漸層。第二,這個屬性得用上顏色的 array
(說實在,其實是 AnyObject
物件)而非 UIColor
物件;顏色必須是 CGColor
物件。在上述例子我只是使用了兩種顏色,但實際運用上是沒有限制,絕對是可以用上多色漸層,如下:
gradientLayer.colors = [UIColor.redColor().CGColor, UIColor.orangeColor().CGColor, UIColor.blueColor().CGColor, UIColor.magentaColor().CGColor, UIColor.yellowColor().CGColor]
執行程式可以得到這個效果:
使用 colors
屬性的好處是它能動畫化,意思是可以調節漸層顏色而製造動態效果。來試試建立和使用一個顏色陣列集。我們將會為每一種顏色製作顏色集(顏色陣列),並在點擊觀看圖像時應用上,而每種顏色之間將以動態方式來轉換。
首先,在 ViewController
類別,gradientLayer
屬性之下宣告這兩個新屬性:
var colorSets = [[CGColor]]() var currentColorSet: Int!
colorSets
陣列的元素其實是CGColor
物件。currentColorSet
指定了應用在漸層效果的顏色。
現在就製作顏色集。以下的程式碼只是舉例,你可以隨意選用自己喜歡的顏色:
func createColorSets() { colorSets.append([UIColor.redColor().CGColor, UIColor.yellowColor().CGColor]) colorSets.append([UIColor.greenColor().CGColor, UIColor.magentaColor().CGColor]) colorSets.append([UIColor.grayColor().CGColor, UIColor.lightGrayColor().CGColor]) currentColorSet = 0 }
在以上的方法,除了為陣列設定用上的顏色,並將他們附加到colorSets
陣列中,我們還需要為currentColorSet
屬性設定初始值。
在viewDidLoad()
方法作出呼叫,讓程式執行以下程式碼:
override func viewDidLoad() { super.viewDidLoad() createColorSets() }
我們還需要為 createGradientLayer()
方法作出少量改動。找出以下程式碼:
gradientLayer.colors = [UIColor.redColor().CGColor, UIColor.orangeColor().CGColor, UIColor.blueColor().CGColor, UIColor.magentaColor().CGColor, UIColor.yellowColor().CGColor]
並以下列程式碼取代之:
gradientLayer.colors = colorSets[currentColorSet]
現在通過在currentColorSet
屬性裡指定的顏色將會被使用,而非預設的顏色。
我說過會簡單地通過點擊視圖去觸發顏色之間的過渡動畫。意思是我們需要增加一個點擊手勢辨識,前往viewDidLoad()
作出處理:
override func viewDidLoad() { ... let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.handleTapGesture(_:))) self.view.addGestureRecognizer(tapGestureRecognizer) }
當視圖收到點擊手勢便呼叫handleTapGesture(_:)
方法。現在還不行,我們將會建立它。要注意CABasicAnimation
將會在以下程式碼中被使用,所以它可以把colors
屬性動畫化。我先假設你對CABasicAnimation
類別有一定程式的認知,否則你便需要上網先行做點功課。在這裡我會跳過某些屬性,務求以最簡單的方式去展示動畫效果。
func handleTapGesture(gestureRecognizer: UITapGestureRecognizer) { if currentColorSet < colorSets.count - 1 { currentColorSet! += 1 } else { currentColorSet = 0 } let colorChangeAnimation = CABasicAnimation(keyPath: "colors") colorChangeAnimation.duration = 2.0 colorChangeAnimation.toValue = colorSets[currentColorSet] colorChangeAnimation.fillMode = kCAFillModeForwards colorChangeAnimation.removedOnCompletion = false gradientLayer.addAnimation(colorChangeAnimation, forKey: "colorChange") }
起初我們決定了顏色的順序。當來到colorSets
陣列中最後一種顏色時,我們將會回到最初(currentColorSet = 0
),其餘我們則透過遞增currentColorSet
屬性來作出轉變效果。
接下來的程式碼都是和動畫有關。最重要的是duration
屬性,意思是動畫所花的時間,以及toValue
設定colors
(建立在CABasicAnimation
類別的初始化規定)期望的最終值。另外兩個屬性能使動畫變化保留在層中,而不是恢復到原來的顏色。但,這不是永久的。我們需要在動畫完成時,明確地設置新的漸層顏色。這可以通過覆蓋下面的方法時,在 CABasicAnimation
完成時呼叫出來:
override func animationDidStop(anim: CAAnimation, finished flag: Bool) { if flag { gradientLayer.colors = colorSets[currentColorSet] } }
以及增加handleTapGesture(_:)
方法,讓上述方法成為實際效果:
func handleTapGesture(gestureRecognizer: UITapGestureRecognizer) { ... // Add this line to make the ViewController class the delegate of the animation object. colorChangeAnimation.delegate = self gradientLayer.addAnimation(colorChangeAnimation, forKey: "colorChange") }
就是這樣。現在可以隨意調節動畫過渡的時間。我故意設定了兩秒時間,這樣漸層顏色的改變效果會更明顯:
顏色位置
知道如何設定或改變漸層效果的顏色是漸層效果的基礎知識,但並不足以完全掌握它的功能。明白如何修改層的顏色覆蓋區域和覆蓋顏色的默認佈局作用也很大。
看看我們之前所建立的漸層效果,你會發現每一種顏色是默認佔據一半區域:
透過更改CAGradientLayer
類別提供的locations
屬性可以作出修改。這個屬性跟據NSNumber
物件作值,而每一個數字決定了每種顏色的起始位置。而且,很重要的,這些數字限制在0.0至1.0之間。
來看看例子便更清楚。到createGradientLayer()
方法,並加入以下程式碼:
gradientLayer.locations = [0.0, 0.35]
重新執行程式便得到以下的漸層效果:
第二種顏色起始的位置是基於我們設定在locations
陣列的第二個值。我們也可以把第二種顏色設定為佔據層的65%區域(1.0-0.35 = 0.65)。作為一個預防措施,必須確認之前的一種顏色的位置不能大過後來的顏色,否則就會出現重叠,如下圖:
若然你想試試以上的效果,可以把數值設定為 [0.5, 0.35]。
再深入一點,我們將為視圖加入一個點擊手勢識別器。這次我們需要兩根手指去進行點擊。在viewDidLoad()
加入以下程式碼:
override func viewDidLoad() { ... let twoFingerTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(ViewController.handleTwoFingerTapGesture(_:))) twoFingerTapGestureRecognizer.numberOfTouchesRequired = 2 self.view.addGestureRecognizer(twoFingerTapGestureRecognizer) }
在handleTwoFingerTapGesture(_:)
方法中,我們為兩種顏色的位置建立了隨機值。然而,我們必須確定第一個位置值比第二個小。除此之外,新位置將會同時發送到控制台。如下:
func handleTwoFingerTapGesture(gestureRecognizer: UITapGestureRecognizer) { let secondColorLocation = arc4random_uniform(100) let firstColorLocation = arc4random_uniform(secondColorLocation - 1) gradientLayer.locations = [NSNumber(double: Double(firstColorLocation)/100.0), NSNumber(double: Double(secondColorLocation)/100.0)] print(gradientLayer.locations!) }
當兩根手指同時點擊程式,將會顯示以下的效果:
要注意,locations
屬性並沒有預設值,所以要小心使用時引發潛在的問題導致程式意外彈出。
漸層方向
現在已經明白如果處理漸層的colors
屬性,接著我們就了解一下漸層效果director
。初學者們可以先看看這個截圖:
圖中一看便清楚知道漸層效果是由上而下,而這是所有漸層的default
顏色方向。同樣,我們可以作出微調,讓漸層做出不同方向的效果。
CAGradientLayer
類別提供兩種屬性,可以指定漸層發生的方向:
startPoint
endPoint
CGPoint
值可以分配到以上任何一個的屬性,而x
和y
兩個值必須介乎0.0至1.0之間。實際上,startPoint
描述了第一種顏色的起始坐標,而endPoint
描述了最後一種顏色的結束坐標。然而,這裡有一個重要的細節:坐標存在於運作系統坐標空間之內。
什麼意思?
看看下列數字可有助理解:
在iOS,zero
點(起始點)坐落於螢幕的左上角 (x = 0.0,y = 0.0),完結點在左下角 (x = 1.0, y = 1.0)。任何的坐標的點,即是x
和y
必須介乎0.0至1.0之間。
以上的坐標與其他操作系統的處理不一樣。在Mac的Textedit舉個例子吧:
這裡的起始點是左下角,而完結點在右上角,與iOS的坐標空間有所不同。
在預設情況下,當endPoint
相等於 (0.5, 1.0),startPoint
則相等於 (0.5, 0.0) 點。要注意x
值是保持不變,但y
值就由0.0(頂)開始並在1.0(底)完結,這樣便能設定方向。如果你想看到更大的分別,可以到createGradientLayer()
方法:
func createGradientLayer() { ... gradientLayer.startPoint = CGPointMake(0.0, 0.5) gradientLayer.endPoint = CGPointMake(1.0, 0.5) }
這個例子中x
值由0.0換成1.0,而y
則保持不變。這樣會得出向右的漸層效果:
要完全明白漸層方向,可以對上列的程式碼作出修改,把x
和y
的設定值調整至0.0-1.0之間的任何一個數值。現在,我們將會為範例增添一些漸層方向的新功能:為視圖增加Pan手勢,依照手指在螢幕上移動的手勢去改變漸層方向。以下的方向都能夠用上:
- 朝上
- 朝下
- 朝右
- 朝左
- 由左上至右下
- 由右上至左下
- 由左下至右上
- 由右下至左上
回到專案,在enum
建立代表方向的指令:
enum PanDirections: Int { case Right case Left case Bottom case Top case TopLeftToBottomRight case TopRightToBottomLeft case BottomLeftToTopRight case BottomRightToTopLeft }
然後,在ViewController
類別宣告新屬性去指引漸層方向:
var panDirection: PanDirections!
panDirection
屬性跟據手指移動動作得到相應的值。我們將會處理一個兩個步驟的工作:最初我們將決定所需方向及分配至以上屬性。然後,當pan手勢完成時,從相應的startPoint
和endPoint
屬性值決定漸層方向。
在開始之前,我們需要建立一個新的手勢辨識器物件,並將它加入視圖中。因此,來到viewDidLoad()
方法,加入以下程式碼:
override func viewDidLoad() { ... let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(ViewController.handlePanGestureRecognizer(_:))) self.view.addGestureRecognizer(panGestureRecognizer) }
在實作handlePanGestureRecognizer(_:)
,我們將會使用手勢辨識器屬性velocity。當 velocity 任何方向點大於300.0(x
或y
),方向都會被考慮在內。邏輯上很容易理解:首要條件是檢查水平軸上的velocity,然後才檢查縱軸上的velocity,便能取得方向。可以看看以下程式碼和註釋:
func handlePanGestureRecognizer(gestureRecognizer: UIPanGestureRecognizer) { let velocity = gestureRecognizer.velocityInView(self.view) if gestureRecognizer.state == UIGestureRecognizerState.Changed { if velocity.x > 300.0 { // 方向朝右的例子 // 以下是垂直移動手勢的具體實例 if velocity.y > 300.0 { // 由左上移動至右下 panDirection = PanDirections.TopLeftToBottomRight } else if velocity.y < -300.0 { // 由左下移動至右上 panDirection = PanDirections.BottomLeftToTopRight } else { // 朝右移動 panDirection = PanDirections.Right } } else if velocity.x < -300.0 { // 方向朝左的例子 // 以下是垂直移動手勢的具體實例 if velocity.y > 300.0 { // 由右上移動至左下 panDirection = PanDirections.TopRightToBottomLeft } else if velocity.y < -300.0 { // 由右下移動至左上 panDirection = PanDirections.BottomRightToTopLeft } else { // 朝左移動 panDirection = PanDirections.Left } } else { // 垂直移動例子(朝上或朝下) if velocity.y > 300.0 { // 朝下移動 panDirection = PanDirections.Bottom } else if velocity.y < -300.0 { // 朝上移動 panDirection = PanDirections.Top } else { // 沒有移動 panDirection = nil } } } else if gestureRecognizer.state == UIGestureRecognizerState.Ended { changeGradientDirection() } }
這裡有兩點需要注意(進一步決定pan手勢方向)::
- 若果沒有其他條件被滿足,
panDirection
將會是nil。 - 從
Changed
手勢狀態決定方向。當手勢完成時,changeGradientDirection()
方法會被呼叫出來,panDIrection()
屬性的值決定新的方向。
以下的方法相當簡單,只需要在漸層上為startPoint
及endPoint
屬性設定適當的值。看看x
和y
如何從手勢方向取得值:
func changeGradientDirection() { if panDirection != nil { switch panDirection.rawValue { case PanDirections.Right.rawValue: gradientLayer.startPoint = CGPointMake(0.0, 0.5) gradientLayer.endPoint = CGPointMake(1.0, 0.5) case PanDirections.Left.rawValue: gradientLayer.startPoint = CGPointMake(1.0, 0.5) gradientLayer.endPoint = CGPointMake(0.0, 0.5) case PanDirections.Bottom.rawValue: gradientLayer.startPoint = CGPointMake(0.5, 0.0) gradientLayer.endPoint = CGPointMake(0.5, 1.0) case PanDirections.Top.rawValue: gradientLayer.startPoint = CGPointMake(0.5, 1.0) gradientLayer.endPoint = CGPointMake(0.5, 0.0) case PanDirections.TopLeftToBottomRight.rawValue: gradientLayer.startPoint = CGPointMake(0.0, 0.0) gradientLayer.endPoint = CGPointMake(1.0, 1.0) case PanDirections.TopRightToBottomLeft.rawValue: gradientLayer.startPoint = CGPointMake(1.0, 0.0) gradientLayer.endPoint = CGPointMake(0.0, 1.0) case PanDirections.BottomLeftToTopRight.rawValue: gradientLayer.startPoint = CGPointMake(0.0, 1.0) gradientLayer.endPoint = CGPointMake(1.0, 0.0) default: gradientLayer.startPoint = CGPointMake(1.0, 1.0) gradientLayer.endPoint = CGPointMake(0.0, 0.0) } } }
假若panDiretion
是nil,就不會有任何動作。
現在執行一下程式並依照支援的手勢方向移動指頭,漸層會隨著你的移動改變方向。
結語
來到最後,希望我能夠讓大家了解CAGradientLayer
使用相當方便,以程式編寫去製作漸層效果。透過適當組合屬性的值讓漸層有更多的變化,得到更好的漸層效果。而動畫效果更是一大賣點。這個題目相對是顯淺易明,我相信你也能夠做得到並開始嘗試在作品中添加漸層顏色。
完整的範例專案,請到GitHub.com下載。