每個開發者在設計程式介面時都會用上不同的顏色組合和圖像,務求製作出賣相更吸引的作品。在顏色配搭上,單色使用有時候不免顯得單調,而漸層 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類別提供兩種屬性,可以指定漸層發生的方向:
startPointendPoint
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下載。