Quartz 2D CGGradient與CGShading實現漸變的繪製
Quartz 提供了兩種不透明的資料型別來建立漸變CGShading和 CGGradient,你可以使用其中任何一個來建立軸向或徑向漸變。
軸向漸變:沿著一個軸方向線性漸變
徑向漸變:一個點為原型,指定半徑的範圍內輻射漸變
我們用兩個圖來理解一下這兩個概念:
1、軸向漸變(軸:沿著左上角 到 右下角)
2、徑向漸變(以中心點為圓心,向半徑為50的四周方向輻射漸變)
CGShading與CGGradient之間的關係,以及使用的不同。
CGGradient是CGShading的子集,CGGradient封裝了對CGShading的漸變實現函式,因此CGGradient使用起來會比CGShading
下面介紹一下兩者的不同:
CGGradient | CGShading |
使用同一個CGGradient例項可以同時繪製軸向漸變和徑向漸變 | 在繪製軸向漸變和徑向漸變時,需要分別例項化不同的物件 |
在繪製時建立漸變的幾何形狀 | 在建立物件時建立漸變的幾何形狀 |
Quartz自動完成計算漸變中每個點的顏色變化 | 你需要提供一個回撥函式,來計算漸變中每個點的顏色 |
使用簡單,定義兩個點和對應的顏色即可 | 需要在回撥函式中計算,使用比較麻煩 |
我們先來介紹一下CGGradient的使用,它使用起來很簡單,我們通過它來實現上面介紹的軸向漸變和徑向漸變的例子
例項化方法:
//引數 // 1、顏色空間 // 2、指定漸變中的顏色(需要與locations個數對應,每組顏色值為rgba(根據顏色空間決定),例如:1,0,0,1,表示一個紅色,透明度為1) // 3、指定漸變的過度位置(值範圍:[0,1],0:幾何開始的位置;1:幾何結束的位置;0.5:幾何中間的位置) init?(colorSpace space: CGColorSpace, colorComponents components: UnsafePointer<CGFloat>, locations: UnsafePointer<CGFloat>?, count: Int)// 引數 // 1、顏色空間 // 2、指定漸變中的顏色(需要與locations個數對應,每組顏色值為:CGColor) // 3、指定漸變的過度位置(值範圍:[0,1],0:幾何開始的位置;1:幾何結束的位置;0.5:幾何中間的位置) init?(colorsSpace space: CGColorSpace?, colors: CFArray, locations: UnsafePointer<CGFloat>?)
具體實現程式碼:
override func draw(_ rect: CGRect) { guard let context = UIGraphicsGetCurrentContext() else { return } // 初始化顏色空間,有兩種方法,一般採用第一種(下面兩個顏色空間是一樣的) // 方法1: let space = CGColorSpaceCreateDeviceRGB() // 方法2: // let space = CGColorSpace(name: CGColorSpace.sRGB)! // 設定漸變的過度位置,範圍:[0, 1] var locations: [CGFloat] = [0, 0.5, 1] // 設定過度的顏色元件集合,因為我們的顏色空間是RGB,因此每組的顏色值為rgba,即:[r, g, b, a] // 注意:每組的顏色是在陣列中連續的,即:[r,g,b,a, r,g,b,a, r,g,b,a] var colors: [CGFloat] = [1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1] // 初始化漸變物件CGGradient guard let gradient = CGGradient(colorSpace: space, colorComponents: &colors, locations: &locations, count: locations.count) else { print("gradient create fail") return } // 繪製軸向漸變 // 引數: // 1、漸變物件例項 // 2、漸變的幾何開始點座標 // 3、漸變的幾何結束點座標 // 4、繪製選項,包括: // (1)drawsBeforeStartLocation:在幾何開始點的位置之前的空間部分(超出邊界不繪製),用第一個顏色值擴散顏色 // (2)drawsAfterEndLocation:在幾何結束點的位置之後的空間部分(超出邊界不繪製),用最後一個顏色值擴散顏色 context.drawLinearGradient(gradient, start: .init(x: 0, y: 0), end: .init(x: bounds.width, y: bounds.height), options: .drawsBeforeStartLocation) // 繪製徑向漸變 // 引數: // 1、漸變物件例項 // 2、漸變開始的圓心點座標 // 3、漸變開始的圓半徑(輻射半徑) // 4、漸變結束的圓心點座標 // 5、漸變結束的圓半徑(輻射半徑) // 6、繪製選項,跟上面的一樣 // context.drawRadialGradient(gradient, // startCenter: .init(x:bounds.width/2, y:bounds.height/2), // startRadius: 10, // endCenter: .init(x: bounds.width/2, y: bounds.height/2), // endRadius: 50, // options: .drawsBeforeStartLocation) }
程式碼中註釋介紹的已經很詳細了,這裡我給大家著重介紹一下options引數。我們看下圖便一目瞭然:
紅色區域:是漸變開始點繪製之前的顏色填充部分。
藍色區域:是漸變結束點繪製之後的顏色填充部分。
options選項決定在漸變的開始點之前 還是 結束點之後 的區域內進行顏色填,填充的顏色色值為:
1、開始點之前繪製(drawsBeforeStartLocation):填充色為顏色陣列中第一個顏色值
2、結束點之後繪製(drawsAfterEndLocation):填充色為顏色陣列中最後一個顏色值
下面是兩種options的軸向漸變的繪圖結果:(徑向漸變也是如此)
drawsBeforeStartLocation:(開始點前採用紅色填充,結束點後無填充)
drawsAfterEndLocation:(開始點前無填充,結束點後採用藍色填充)
接下來介紹CGShading的使用,它在繪製漸變時用起來比CGGradient要複雜,因為需要我們自己來計算漸變過程中每個點的顏色。使用時涉及到以下幾個關鍵物件:
CGFunctionCallbacks:用於提供計算漸變過程中每個點的顏色的回撥函式,這就是我們實現漸變的關鍵點。
CGFunction:為上面的回撥函式設定初始值,以及相關值的範圍的物件。
CGShading:定義漸變的規則,例如:顏色空間、從幾何區域的哪裡開始漸變到哪裡結束漸變等。
實現軸向漸變與徑向漸變的例項化方法不一樣,下面是對應這兩種實現的例項化方法:
// 初始化軸向漸變物件 init?(axialSpace space: CGColorSpace, start: CGPoint, end: CGPoint, function: CGFunction, extendStart: Bool, extendEnd: Bool) // 初始化徑向漸變物件 init?(radialSpace space: CGColorSpace, start: CGPoint, startRadius: CGFloat, end: CGPoint, endRadius: CGFloat, function: CGFunction, extendStart: Bool, extendEnd: Bool)
下面是一個利用CGShading實現軸向漸變的具體實現的demo,程式碼中註釋寫的很詳細,大家可以參考。
override func draw(_ rect: CGRect) { guard let context = UIGraphicsGetCurrentContext() else { return } // 初始化一個回撥函式,用於計算每個點的顏色值 // 引數: // 1、CGFunction初始化傳入的第一個引數info(指標型別) // 2、輸入值 // 3、輸出值 // 4、釋放info指標的回撥函式 var callback = CGFunctionCallbacks(version: 0) { info, inVal, outVal in let numberOfComponents: Int if let info = info { // 注意這裡不能用takeRetainedValue()取值,否則會提前釋放info的記憶體,導致下次訪問info時出錯 numberOfComponents = Unmanaged<NSNumber>.fromOpaque(info).takeUnretainedValue().intValue }else{ numberOfComponents = 0 } // 漸變開始的顏色值 let startColor: [CGFloat] = [1, 0, 0, 1] // 漸變結束的顏色值 let endColor: [CGFloat] = [0, 1, 0, 1] let aInVal = inVal.pointee // 獲取每次迭代的輸入值,下面指定了輸入值範圍在[0, 1],因此aInVal的值範圍在[0, 1] var out = outVal // 下面指定了輸出值包含的分量個數為numberOfComponents(即:out中應返回numberOfComponents個元素) for i in 0..<numberOfComponents { // 注意:下面指定了輸出值中每個分量(每個元素)的值範圍[0, 1],因此這裡的計算結果不要超出這個範圍 if aInVal < 0.5 { // 計算漸變開始點到中間點區間的顏色分量值 out.pointee = startColor[i] * (0.8 - aInVal) }else{ // 計算漸變中間點到結束點區間的顏色分量值 out.pointee = endColor[i] * (aInVal - 0.2) } out = out.successor() } } releaseInfo: { info in guard let info = info else { return } // 釋放info指標記憶體 Unmanaged<NSNumber>.fromOpaque(info).release() } // 初始化顏色空間 let space = CGColorSpaceCreateDeviceRGB() // 獲取通道顏色分量的個數,這裡要加一個alpha let numberOfComponents = space.numberOfComponents + 1 // rgb + a // 指定上面callback中輸入值的遞增迭代次數,這裡設定1,即:輸入值會從domain的範圍內,迭代一次(0, 0.0.00167, ..., 1)終止 // 如果設定為2,會迭代兩次:第一次(0, 0.0.00167, ..., 1),第二次(0, 0.0.00167, ..., 1) let domainDimension = 1 // 輸入值的變化範圍,這裡指定從0到1,即輸入值會根據漸變的開始點到結束點的變化,從0開始遞增到1,如(0, 0.0.00167, ..., 1) // 如果設定為[1, 10],則輸入值會從1開始遞增到10 let domain: [CGFloat] = [0, 1] // 注意:這裡的範圍必須是遞增的 // 指定上面callback中輸出值包含的分量個數,如果為4,那麼上面callback的輸出值out指標應該包含4個分量值(即:rgba)。 // 可以理解為輸出值陣列的size let rangeDimension = numberOfComponents // 輸出值每個分量的範圍,即r值的範圍[0, 1],g值的範圍[0, 1], b值的範圍[0, 1], a值的範圍[0, 1] // 可以理解為輸出值陣列中每個元素值的取值範圍 let range: [CGFloat] = [0, 1, 0, 1, 0, 1, 0, 1] // 陣列下標0和1代表第一個值的範圍,一次類推 guard let function = CGFunction(info: Unmanaged<NSNumber>.passRetained(NSNumber(integerLiteral: numberOfComponents)).toOpaque(), domainDimension: domainDimension, domain: domain, rangeDimension: rangeDimension, range: range, callbacks: &callback) else { print("function create fail") return } // 漸變的開始點座標 let start = CGPoint(x: bounds.width/4, y: bounds.height/4) // 漸變的結束點座標 let end = CGPoint(x: bounds.width * 3 / 4, y: bounds.height * 3 / 4) // 指定漸變開始點之前的區域是否執行填充顏色,相當於使用CGGradient繪製漸變時,呼叫drawLinearGradient方法時設定的:drawsBeforeStartLocation let extendStart = true // 指定漸變結束點之後的區域是否執行填充顏色,相當於使用CGGradient繪製漸變時,呼叫drawLinearGradient方法時設定的:drawsAfterEndLocation let extendEnd = true // 初始化一個軸向漸變例項CGShading guard let shading = CGShading(axialSpace: space, start: start, end: end, function: function, extendStart: extendStart, extendEnd: extendEnd) else { print("shading create fail") return } // 繪製漸變 context.drawShading(shading) }
程式碼中註釋介紹的已經很詳細了,這裡就不詳細說明了,下面是實現的效果:
徑向漸變的實現也是類似的,只不過是CGShading的例項化方法不一樣。下面是採用CGShading實現的徑向漸變的效果:
要得到你必須要付出,要付出你還要學會堅持,如果你真的覺得很難,那你就放棄,但是你放棄了就不要抱怨,我覺得人生就是這樣,世界真的是平等的,每個人都要通過自己的努力,去決定自己生活的樣子。