CoreGraphic框架解析 (十八) —— 如何制作Glossy效果的按鈕(一)

版本記錄

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

前言

quartz是一個(gè)通用的術(shù)語(yǔ)蒿叠,用于描述在iOSMAC OS X 中整個(gè)媒體層用到的多種技術(shù) 包括圖形咪鲜、動(dòng)畫(huà)、音頻栈顷、適配。Quart 2D 是一組二維繪圖和渲染API嵌巷,Core Graphic會(huì)使用到這組API萄凤,Quartz Core專(zhuān)指Core Animation用到的動(dòng)畫(huà)相關(guān)的庫(kù)、API和類(lèi)搪哪。CoreGraphicsUIKit下的主要繪圖系統(tǒng)靡努,頻繁的用于繪制自定義視圖。Core Graphics是高度集成于UIView和其他UIKit部分的晓折。Core Graphics數(shù)據(jù)結(jié)構(gòu)和函數(shù)可以通過(guò)前綴CG來(lái)識(shí)別惑朦。在app中很多時(shí)候繪圖等操作我們要利用CoreGraphic框架,它能繪制字符串漓概、圖形漾月、漸變色等等,是一個(gè)很強(qiáng)大的工具胃珍。感興趣的可以看我另外幾篇梁肿。
1. CoreGraphic框架解析(一)—— 基本概覽
2. CoreGraphic框架解析(二)—— 基本使用
3. CoreGraphic框架解析(三)—— 類(lèi)波浪線(xiàn)的實(shí)現(xiàn)
4. CoreGraphic框架解析(四)—— 基本架構(gòu)補(bǔ)充
5. CoreGraphic框架解析 (五)—— 基于CoreGraphic的一個(gè)簡(jiǎn)單繪制示例 (一)
6. CoreGraphic框架解析 (六)—— 基于CoreGraphic的一個(gè)簡(jiǎn)單繪制示例 (二)
7. CoreGraphic框架解析 (七)—— 基于CoreGraphic的一個(gè)簡(jiǎn)單繪制示例 (三)
8. CoreGraphic框架解析 (八)—— 基于CoreGraphic的一個(gè)簡(jiǎn)單繪制示例 (四)
9. CoreGraphic框架解析 (九)—— 一個(gè)簡(jiǎn)單小游戲 (一)
10. CoreGraphic框架解析 (十)—— 一個(gè)簡(jiǎn)單小游戲 (二)
11. CoreGraphic框架解析 (十一)—— 一個(gè)簡(jiǎn)單小游戲 (三)
12. CoreGraphic框架解析 (十二)—— Shadows 和 Gloss (一)
13. CoreGraphic框架解析 (十三)—— Shadows 和 Gloss (二)
14. CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一)
15. CoreGraphic框架解析 (十五)—— Arcs 和 Paths (二)
16. CoreGraphic框架解析 (十六)—— Lines, Rectangles 和 Gradients (一)
17. CoreGraphic框架解析 (十七)—— Lines, Rectangles 和 Gradients (二)

開(kāi)始

首先看下寫(xiě)作環(huán)境

Swift 4.2, iOS 12, Xcode 10

Core Graphics是iOS開(kāi)發(fā)人員容易避免的框架之一蜓陌。 它有點(diǎn)模糊,語(yǔ)法不是超現(xiàn)代的栈雳,Apple并沒(méi)有像WWDC那樣給予它盡可能多的愛(ài)护奈! 另外缔莲,只需使用圖像即可輕松避免使用它哥纫。

但是,Core Graphics是一個(gè)非常強(qiáng)大的工具痴奏! 你可以擺脫平面設(shè)計(jì)師的束縛蛀骇,使用強(qiáng)大的CG劍自己創(chuàng)造出令人驚嘆的UI美。

在本教程中读拆,您將學(xué)習(xí)如何僅使用Core Graphics創(chuàng)建可自定義擅憔,可重復(fù)使用的光澤按鈕。 難道你沒(méi)有聽(tīng)說(shuō)過(guò)skeuomorphism的風(fēng)格嗎檐晕?

在此過(guò)程中暑诸,您將學(xué)習(xí)如何繪制圓角矩形,如何輕松地為Core Graphics繪圖著色以及如何創(chuàng)建漸變和光澤效果辟灰。

自定義UIButton有很多選項(xiàng)个榕,從完整的自定義UIButton類(lèi)到較小的擴(kuò)展。 但是在這次討論中缺少的是一個(gè)詳細(xì)的核心圖形教程芥喇,用于自定義按鈕西采,從頭到尾。 這很簡(jiǎn)單继控;你可以使用它來(lái)獲得你想要的應(yīng)用程序的確切外觀械馆。

現(xiàn)在是時(shí)候開(kāi)始吧!

打開(kāi)啟動(dòng)項(xiàng)目:CoolButton Starter武通。

項(xiàng)目結(jié)構(gòu)非常簡(jiǎn)單霹崎,僅包含選擇Single View App Xcode模板時(shí)創(chuàng)建的文件。 唯一的兩個(gè)major exceptions是:

  • Assets.xcassets目錄中的幾個(gè)圖像冶忱。
  • 您可以在Main.storyboard中找到ViewController的預(yù)先設(shè)計(jì)的UI尾菇。

現(xiàn)在轉(zhuǎn)到File ? New ? File…,選擇iOS?CocoaTouch Class朗和,然后單擊Next错沽。

在下一個(gè)菜單中,輸入CoolButton作為類(lèi)名眶拉。 在子類(lèi)字段中千埃,鍵入UIButton。 對(duì)于語(yǔ)言忆植,請(qǐng)選擇Swift放可。 現(xiàn)在單擊Next谒臼,然后單擊Create

打開(kāi)CoolButton.swift并用以下內(nèi)容替換類(lèi)定義:

class CoolButton: UIButton {
  var hue: CGFloat {
    didSet {
      setNeedsDisplay()
    }
  }
  
  var saturation: CGFloat {
    didSet {
      setNeedsDisplay()
    }
  }
  
  var brightness: CGFloat {
    didSet {
      setNeedsDisplay()
    }
  }
}

在這里耀里,您將創(chuàng)建三個(gè)屬性蜈缤,用于自定義顏色的色調(diào),飽和度和亮度(hue, saturation and brightness)冯挎。

設(shè)置屬性后底哥,將觸發(fā)對(duì)setNeedsDisplay的調(diào)用,以強(qiáng)制UIButton在用戶(hù)更改顏色時(shí)重繪按鈕房官。

現(xiàn)在趾徽,將以下代碼粘貼到CoolButton的底部,就在最后一個(gè)花括號(hào)之前:

required init?(coder aDecoder: NSCoder) {
  self.hue = 0.5
  self.saturation = 0.5
  self.brightness = 0.5
  
  super.init(coder: aDecoder)
  
  self.isOpaque = false
  self.backgroundColor = .clear
}

override func draw(_ rect: CGRect) {
  guard let context = UIGraphicsGetCurrentContext() else {
    return
  }

  let color = UIColor(hue: hue, 
    saturation: saturation, 
    brightness: brightness, 
    alpha: 1.0)

  context.setFillColor(color.cgColor)
  context.fill(bounds)
}

在這里翰守,您初始化變量并使用預(yù)先配置的顏色填充按鈕孵奶,以確保一切從一開(kāi)始就正常工作。


Configuring the Button’s UI

打開(kāi)Main.storyboard以配置UI蜡峰。 有一個(gè)視圖控制器包含三個(gè)滑塊 - 一個(gè)用于色調(diào)了袁,一個(gè)用于飽和度,一個(gè)用于亮度湿颅。

您錯(cuò)過(guò)了UIButton载绿,因此請(qǐng)將其添加到屏幕頂部。 轉(zhuǎn)到Object Library肖爵,鍵入UIButton卢鹦,然后將其拖到屏幕中。

一些自動(dòng)布局的時(shí)間到了劝堪! 按住Ctrl鍵并從按鈕拖動(dòng)到視圖冀自,然后在“安全區(qū)域”中選擇Center Horizontally

按住Ctrl鍵并從按鈕拖動(dòng)到hue label秒啦,然后選擇Vertical Spacing熬粗。

現(xiàn)在,將按鈕的大小調(diào)整為您喜歡的大小余境。 按住Ctrl鍵并從按鈕向左或向右拖動(dòng)驻呐,然后選擇Width

按住control鍵芳来,從按鈕向上或向下拖動(dòng)并選擇Height含末。

注意:如果通過(guò)拖動(dòng)設(shè)置寬度和高度約束有困難,請(qǐng)選擇按鈕并單擊畫(huà)布右下角的Add New Contraints按鈕即舌。 它看起來(lái)有點(diǎn)像星球大戰(zhàn)中的領(lǐng)帶戰(zhàn)士佣盒。

接下來(lái),通過(guò)雙擊并按Delete鍵刪除顯示Button的文本顽聂。

仍然選中該按鈕肥惭,轉(zhuǎn)到屏幕右側(cè)的Inspectors側(cè)欄盯仪,然后單擊Identity inspector。 在Custom Class ? Class中蜜葱,輸入CoolButton以使您的按鈕成為CoolButton類(lèi)的實(shí)例全景。

按鈕就位后,您就可以開(kāi)始將UI連接到代碼了牵囤!


Making Your Button Functional

打開(kāi)ViewController.swift并使用以下內(nèi)容替換類(lèi)定義:

class ViewController: UIViewController {
  @IBOutlet weak var coolButton: CoolButton!
  @IBAction func hueValueChanged(_ sender: Any) { }
  @IBAction func saturationValueChanged(_ sender: Any) { }
  @IBAction func brightnessValueChanged(_ sender: Any) { }
}

在這里爸黄,您將聲明對(duì)剛剛在故事板中創(chuàng)建的按鈕的引用。 您還要聲明配置滑塊的值更改時(shí)發(fā)生的回調(diào)奔浅。

現(xiàn)在馆纳,再次打開(kāi)Main.storyboard并在頂部欄中選擇Assistant Editor以并排顯示ViewController.swiftMain.storyboard诗良。

按住control鍵從View Controller Scene ? ViewController將按鈕拖動(dòng)到左側(cè)并將其連接到coolButton outlet汹桦。

類(lèi)似的,按住control鍵從View Controller Scene ? ViewController拖出slider鉴裹,并選擇appropriate value-changed回調(diào)舞骆。

接下來(lái),切換到ViewController.swift并實(shí)現(xiàn)滑塊的值更改回調(diào):

@IBAction func hueValueChanged(_ sender: Any) {
  guard let slider = sender as? UISlider else { return }
  coolButton.hue = CGFloat(slider.value)
}

@IBAction func saturationValueChanged(_ sender: Any) {
  guard let slider = sender as? UISlider else { return }
  coolButton.saturation = CGFloat(slider.value)
}

@IBAction func brightnessValueChanged(_ sender: Any) {
  guard let slider = sender as? UISlider else { return }
  coolButton.brightness = CGFloat(slider.value)
}

默認(rèn)情況下径荔,UISliders的范圍為0.0到1.0督禽。 這非常適合您的色調(diào),飽和度和亮度值总处,范圍也從0.0到1.0狈惫,因此您可以直接設(shè)置它們。

經(jīng)過(guò)所有這些工作鹦马,它終于建立并運(yùn)行了胧谈! 如果一切正常,您可以使用滑塊來(lái)填充各種顏色的按鈕:


Drawing Rounded Rectangles

確實(shí)荸频,您可以輕松創(chuàng)建方形按鈕菱肖,但按鈕樣式比芝加哥的天氣變化更快!

事實(shí)上旭从,由于我們最初發(fā)布本教程稳强,方形按鈕和圓角矩形按鈕在按鈕選美中的第一位來(lái)回翻轉(zhuǎn),所以知道如何制作這兩個(gè)版本是個(gè)好主意和悦。

您還可以說(shuō)退疫,通過(guò)簡(jiǎn)單地改變UIViewcorner radius來(lái)創(chuàng)建圓角矩形非常容易,但那里的樂(lè)趣在哪里鸽素?用這種方式做得更加滿(mǎn)足褒繁,或者說(shuō)是瘋狂。

制作圓角矩形的一種方法是使用CGContextAddArc API繪制弧付鹿。使用該API澜汤,您可以在每個(gè)角落繪制弧線(xiàn)并繪制線(xiàn)條以連接它們蚜迅。但這很麻煩,需要很多幾何形狀俊抵。

幸運(yùn)的是谁不,有一種更簡(jiǎn)單的方法!你不需要做太多的數(shù)學(xué)運(yùn)算徽诲,它可以很好地繪制圓角矩形刹帕。這是CGContextAddArcToPoint API

1. Using the CGContextAddArcToPoint API

CGContextAddArcToPoint API允許您通過(guò)指定兩條切線(xiàn)和一條半徑來(lái)描述要繪制的弧谎替。 Quartz2D Programming Guide中的下圖顯示了它的工作原理:

當(dāng)您使用矩形時(shí)偷溺,您知道要繪制的每個(gè)弧的切線(xiàn) - 它們只是矩形的邊緣! 您可以根據(jù)矩形的圓角來(lái)指定半徑 - 圓弧越大钱贯,圓角越圓挫掏。

關(guān)于這個(gè)函數(shù)的另一個(gè)好處是,如果路徑中的當(dāng)前點(diǎn)沒(méi)有設(shè)置為告訴弧開(kāi)始繪制的位置秩命,它將從當(dāng)前點(diǎn)到路徑的開(kāi)頭繪制一條線(xiàn)尉共。 因此,您可以將其用作快捷方式弃锐,只需幾次調(diào)用即可繪制圓角矩形袄友。

2. Drawing Your Arcs

由于您要在此Core Graphics教程中創(chuàng)建一堆圓角矩形,并且希望代碼盡可能可重用霹菊,因此請(qǐng)為所有繪圖方法創(chuàng)建單獨(dú)的文件剧蚣。

轉(zhuǎn)到File ? New ? File…邢隧,然后選擇iOS ? Swift File巡通。 按Next,將其命名為Drawing卿操,然后單擊Create柳洋。

現(xiàn)在待诅,使用以下內(nèi)容替換import Foundation

import UIKit
import CoreGraphics

extension UIView {
  func createRoundedRectPath(for rect: CGRect, radius: CGFloat) -> CGMutablePath {
    let path = CGMutablePath()
    
    // 1
    let midTopPoint = CGPoint(x: rect.midX, y: rect.minY)
    path.move(to: midTopPoint)
    
    // 2
    let topRightPoint = CGPoint(x: rect.maxX, y: rect.minY)
    let bottomRightPoint = CGPoint(x: rect.maxX, y: rect.maxY)
    let bottomLeftPoint = CGPoint(x: rect.minX, y: rect.maxY)
    let topLeftPoint = CGPoint(x: rect.minX, y: rect.minY)
    
    // 3
    path.addArc(tangent1End: topRightPoint, 
      tangent2End: bottomRightPoint, 
      radius: radius)

    path.addArc(tangent1End: bottomRightPoint, 
      tangent2End: bottomLeftPoint, 
      radius: radius)

    path.addArc(tangent1End: bottomLeftPoint,
      tangent2End: topLeftPoint, 
      radius: radius)

    path.addArc(tangent1End: topLeftPoint,
      tangent2End: topRightPoint, 
      radius: radius)

    // 4
    path.closeSubpath()
    
    return path
  }
}

上面的代碼為UIView類(lèi)型的所有內(nèi)容創(chuàng)建了一個(gè)全局?jǐn)U展,因此您不僅可以將它用于UIButton熊镣。

該代碼還指示createRoundedRectPath(for:radius :)按以下順序繪制圓角矩形:

以下是代碼中發(fā)生的情況的細(xì)分:

  • 1) 您移動(dòng)到頂線(xiàn)部分的中心卑雁。
  • 2) 您將每個(gè)角聲明為局部常量。
  • 3) 您將每個(gè)弧添加到路徑:
    • i) 首先绪囱,在右上角添加一個(gè)圓弧测蹲。 在繪制弧之前,CGPathAddArcToPoint將從矩形中間的當(dāng)前位置到弧的開(kāi)始繪制一條線(xiàn)鬼吵。
    • ii) 同樣扣甲,您可以為右下角和連接線(xiàn)添加弧。
    • iii) 然后為左下角和連接線(xiàn)添加弧。
    • iv) 最后琉挖,為左上角和連接線(xiàn)添加弧启泣。
  • 4) 最后,使用closeSubpath()連接弧的終點(diǎn)和起點(diǎn)示辈。

3. Making Your Button Rounded

好的寥茫,現(xiàn)在是時(shí)候讓這個(gè)方法起作用了! 打開(kāi)CoolButton.swift并用以下內(nèi)容替換draw(_ :)

override func draw(_ rect: CGRect) {
  guard let context = UIGraphicsGetCurrentContext() else {
    return
  }
  
  // 1
  let outerColor = UIColor(
    hue: hue, saturation: saturation, brightness: brightness, alpha: 1.0)
  let shadowColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 0.5)
  
  // 2
  let outerMargin: CGFloat = 5.0
  let outerRect = rect.insetBy(dx: outerMargin, dy: outerMargin)
  // 3
  let outerPath = createRoundedRectPath(for: outerRect, radius: 6.0)
  
  // 4
  if state != .highlighted {
    context.saveGState()
    context.setFillColor(outerColor.cgColor)
    context.setShadow(offset: CGSize(width: 0, height: 2), 
      blur: 3.0, color: shadowColor.cgColor)
    context.addPath(outerPath)
    context.fillPath()
    context.restoreGState()
  }
}

下面進(jìn)行細(xì)分:

  • 1) 您定義了兩種顏色矾麻。
  • 2) 然后你使用insetBy(dx:dy :)得到一個(gè)稍小的矩形(每邊5個(gè)像素)纱耻,你將繪制圓角矩形。 你把它變小了险耀,這樣你就有空間在外面畫(huà)一個(gè)陰影弄喘。
  • 3) 接下來(lái),您調(diào)用剛剛編寫(xiě)的函數(shù)createRoundedRectPath(for:radius :)甩牺,為圓角矩形創(chuàng)建路徑蘑志。
  • 4) 最后,設(shè)置填充顏色和陰影柴灯,添加上下文的路徑卖漫,并調(diào)用fillPath()以使用當(dāng)前顏色填充它。

注意:如果您的按鈕當(dāng)前未突出顯示赠群,您只想運(yùn)行代碼;例如旱幼,它沒(méi)有被點(diǎn)擊查描。

構(gòu)建并運(yùn)行應(yīng)用程序;如果一切正常柏卤,您應(yīng)該看到以下內(nèi)容:


Adding a Gradient

好吧冬三,按鈕開(kāi)始看起來(lái)很不錯(cuò),但你可以做得更好缘缚! 添加漸變?cè)趺礃樱?/p>

將以下函數(shù)添加到Drawing.swift勾笆,以使其普遍適用于任何UIView

func drawLinearGradient(
  context: CGContext, rect: CGRect, startColor: CGColor, endColor: CGColor) {
  // 1
  let colorSpace = CGColorSpaceCreateDeviceRGB()
  
  // 2
  let colorLocations: [CGFloat] = [0.0, 1.0]
  
  // 3
  let colors: CFArray = [startColor, endColor] as CFArray
  
  // 4
  let gradient = CGGradient(
    colorsSpace: colorSpace, colors: colors, locations: colorLocations)!

  // More to come...
}

看起來(lái)并不多,但這個(gè)函數(shù)做了很多桥滨!

  • 1) 您需要的第一件事是獲得一個(gè)用于繪制漸變的顏色空間窝爪。

注意:您可以使用色彩空間做很多事情,但99%的時(shí)間您只需要一個(gè)標(biāo)準(zhǔn)的設(shè)備相關(guān)RGB色彩空間齐媒。因此蒲每,只需使用函數(shù)CGColorSpaceCreateDeviceRGB()來(lái)獲取所需的引用。

  • 2) 接下來(lái)喻括,設(shè)置一個(gè)數(shù)組邀杏,跟蹤漸變范圍內(nèi)每種顏色的位置。值0表示漸變的開(kāi)始唬血,1表示漸變的結(jié)束望蜡。您只有兩種顏色唤崭,并且您希望第一種顏色位于開(kāi)頭,第二種顏色位于結(jié)尾脖律,因此您傳入0和1浩姥。

注意:如果需要,可以在漸變中使用三種或更多顏色状您,并且可以設(shè)置漸變中每種顏色的開(kāi)始位置勒叠。這對(duì)某些效果很有用。

  • 3) 之后膏孟,使用傳遞給函數(shù)的顏色創(chuàng)建一個(gè)數(shù)組眯分。為方便起見(jiàn),您在此處使用普通舊數(shù)組柒桑,但您需要將其轉(zhuǎn)換為CFArray弊决,因?yàn)檫@就是API所需要的。

  • 4) 然后使用CGGradient(colorsSpace:colors:locations :)創(chuàng)建漸變魁淳,傳入顏色空間飘诗,顏色數(shù)組和先前創(chuàng)建的位置。

你現(xiàn)在有一個(gè)漸變引用界逛,但它實(shí)際上還沒(méi)有繪制任何東西昆稿。它只是指向稍后實(shí)際繪制時(shí)使用的信息的指針。

通過(guò)在drawLinearGradient(context:rect:startColor:endColor :)的末尾添加以下內(nèi)容來(lái)完成該函數(shù):

// 5
let startPoint = CGPoint(x: rect.midX, y: rect.minY)
let endPoint = CGPoint(x: rect.midX, y: rect.maxY)

context.saveGState()

// 6
context.addRect(rect)
// 7
context.clip()

// 8
context.drawLinearGradient(
  gradient, start: startPoint, end: endPoint, options: [])

context.restoreGState()
  • 5) 您要做的第一件事是計(jì)算要繪制漸變的起點(diǎn)和終點(diǎn)息拜。您只需將其設(shè)置為矩形的top middlebottom middle的直線(xiàn)溉潭。

其余的代碼可以幫助您在提供的矩形中繪制漸變,關(guān)鍵函數(shù)是drawLinearGradient(_:start:end:options :)少欺。

1. Constraining a Gradient to a Sub-area

然而喳瓣,關(guān)于該函數(shù)的奇怪之處在于它用漸變填充整個(gè)繪圖區(qū)域 - 沒(méi)有辦法將其設(shè)置為僅用漸變填充子區(qū)域!

好吧赞别,沒(méi)有裁剪(clipping)畏陕,那就是!

裁剪是Core Graphics的一個(gè)很棒的功能仿滔,可以將繪圖限制為任意形狀惠毁。您所要做的就是將形狀添加到上下文中,但是不要像通常那樣填充它堤撵,而是調(diào)用clip()∪侍郑現(xiàn)在,您已將所有未來(lái)的繪圖限制在該區(qū)域实昨!

這就是你在這里做的:

  • 6) 您將矩形添加到上下文中洞豁。
  • 7) 剪輯到該區(qū)域。
  • 8) 然后調(diào)用drawLinearGradient(_:start:end:options :),傳入之前設(shè)置的所有變量丈挟。

那么關(guān)于saveGState()/ restoreGState()的這些東西究竟是什么呢刁卜?

好吧,Core Graphics是一個(gè)狀態(tài)機(jī)曙咽。您可以配置所需的一組狀態(tài)蛔趴,例如顏色和線(xiàn)條粗細(xì),然后執(zhí)行操作以實(shí)際繪制它們例朱。這意味著一旦你設(shè)置了某些東西孝情,它就會(huì)一直保持這種狀態(tài),直到你改回來(lái)洒嗤。

好吧箫荡,你剛剛修剪到一個(gè)區(qū)域,所以除非你做了一些事情渔隶,否則你再也無(wú)法在那個(gè)區(qū)域之外畫(huà)畫(huà)了羔挡!

這就是saveGState()/ restoreGState()可以幫到的地方。

通過(guò)這些间唉,您可以將上下文的當(dāng)前設(shè)置保存到堆棧中绞灼,然后在完成后再將其pop回到原來(lái)的位置。

就是這樣呈野,現(xiàn)在嘗試一下吧低矮!

打開(kāi)CoolButton.swift并將其添加到draw(_ :)的底部:

// Outer Path Gradient:
// 1
let outerTop = UIColor(hue: hue, saturation: saturation, 
  brightness: brightness, alpha: 1.0)
let outerBottom = UIColor(hue: hue, saturation: saturation, 
  brightness: brightness * 0.8, alpha: 1.0)

// 2
context.saveGState()
context.addPath(outerPath)
context.clip()
drawLinearGradient(context: context, rect: outerRect, 
  startColor: outerTop.cgColor, endColor: outerBottom.cgColor)
context.restoreGState()

建立并運(yùn)行;你應(yīng)該看到這樣的東西:

  • 1) 首先际跪,定義頂部和底部顏色商佛。
  • 2) 然后,通過(guò)在堆棧上保存當(dāng)前圖形狀態(tài)姆打,添加路徑,剪切它肠虽,繪制漸變并再次恢復(fù)狀態(tài)來(lái)繪制漸變幔戏。

很好,你的按鈕看起來(lái)很漂亮税课! 一些額外的pizazz怎么樣闲延?!


Adding a Gloss Effect

現(xiàn)在是時(shí)候讓這個(gè)按鈕閃亮了韩玩,因?yàn)?code>skeuomorphism應(yīng)該永遠(yuǎn)不會(huì)過(guò)時(shí)垒玲!

當(dāng)您為Core Graphics中的按鈕添加光澤(gloss)效果時(shí),事情會(huì)變得非常復(fù)雜找颓。 如果你感覺(jué)很難合愈,請(qǐng)看看 Matt GallagherMichael Heyeck關(guān)于此事的一些偉大作品。

但是對(duì)于我可憐的眼睛,你可以通過(guò)應(yīng)用漸變alpha蒙版獲得一個(gè)相當(dāng)好看的光澤效果近似佛析,這更容易理解和編碼益老。 所以你要去這么做。

這是你可以全面應(yīng)用于UIView的東西寸莫,所以將以下函數(shù)添加到Drawing.swift中的UIView擴(kuò)展:

func drawGlossAndGradient(
  context: CGContext, rect: CGRect, startColor: CGColor, endColor: CGColor) {

  // 1
  drawLinearGradient(
    context: context, rect: rect, startColor: startColor, endColor: endColor)
  
  let glossColor1 = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.35)
  let glossColor2 = UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 0.1)
  
  let topHalf = CGRect(origin: rect.origin, 
    size: CGSize(width: rect.width, height: rect.height/2))
  
  drawLinearGradient(context: context, rect: topHalf, 
    startColor: glossColor1.cgColor, endColor: glossColor2.cgColor)
}

此函數(shù)基本上是從開(kāi)始到結(jié)束顏色在矩形上繪制漸變捺萌,然后在上半部分添加光澤。 下面進(jìn)行細(xì)分:

  • 1) 要繪制漸變膘茎,請(qǐng)調(diào)用之前編寫(xiě)的函數(shù)桃纯。
  • 2) 要繪制光澤,然后在其上繪制另一個(gè)漸變披坏,從很透明(白色态坦,0.35 alpha)到非常透明(白色,0.1 alpha)刮萌。

看看它的外觀驮配。 回到CoolButton.swift并在draw(_ :)中做一個(gè)小改動(dòng)。 替換此行着茸,這是draw(_ :)中的倒數(shù)第二行:

drawLinearGradient(context: context, rect: outerRect, 
  startColor: outerTop.cgColor, endColor: outerBottom.cgColor)

使用

drawGlossAndGradient(context: context, rect: outerRect, 
  startColor: outerTop.cgColor, endColor: outerBottom.cgColor)

如果您無(wú)法發(fā)現(xiàn)差異壮锻,您只需將drawLinearGradient(context:rect:startColor:endColor :)更改為drawGlossAndGradient(context:rect:startColor:endColor :),即Drawing.swift中新添加的方法涮阔。

構(gòu)建并運(yùn)行猜绣,您的按鈕現(xiàn)在應(yīng)如下所示:

噢,有光澤敬特!


Styling the Button

現(xiàn)在為超細(xì)掰邢,挑剔的細(xì)節(jié)。 如果您正在制作3D按鈕伟阔,那么您可以全力以赴辣之。 要做到這一點(diǎn),你需要一個(gè)斜角皱炉。

要?jiǎng)?chuàng)建斜角類(lèi)型效果怀估,請(qǐng)?zhí)砑右粭l內(nèi)部路徑,該路徑的漸變略微不同于外部路徑合搅。 將它添加到CoolButton.swift中的draw(_ :)底部:

// 1: Inner Colors
let innerTop = UIColor(
  hue: hue, saturation: saturation, brightness: brightness * 0.9, alpha: 1.0)
let innerBottom = UIColor(
  hue: hue, saturation: saturation, brightness: brightness * 0.7, alpha: 1.0)

// 2: Inner Path
let innerMargin: CGFloat = 3.0
let innerRect = outerRect.insetBy(dx: innerMargin, dy: innerMargin)
let innerPath = createRoundedRectPath(for: innerRect, radius: 6.0)

// 3: Draw Inner Path Gloss and Gradient
context.saveGState()
context.addPath(innerPath)
context.clip()
drawGlossAndGradient(context: context, 
  rect: innerRect, startColor: innerTop.cgColor, endColor: innerBottom.cgColor)
context.restoreGState()

在這里多搀,您使用insetBy(dx:dy :)再次縮小矩形,然后獲得一個(gè)圓角矩形并在其上運(yùn)行漸變灾部。 構(gòu)建并運(yùn)行康铭,您將看到一個(gè)微妙的改進(jìn):


Highlighting the Button

你的按鈕看起來(lái)很酷,但它不像一個(gè)按鈕赌髓。 沒(méi)有指示用戶(hù)是否按下了按鈕从藤。

要處理此問(wèn)題催跪,您需要覆蓋觸摸事件以告訴您的按鈕重新顯示自身,因?yàn)樵谟脩?hù)選擇它之后可能需要更新呛哟。

將以下內(nèi)容添加到CoolButton.swift

@objc func hesitateUpdate() {
  setNeedsDisplay()
}

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
  super.touchesBegan(touches, with: event)
  setNeedsDisplay()
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
  super.touchesMoved(touches, with: event)
  setNeedsDisplay()
}

override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
  super.touchesCancelled(touches, with: event)
  setNeedsDisplay()
  
  perform(#selector(hesitateUpdate), with: nil, afterDelay: 0.1)
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
  super.touchesEnded(touches, with: event)
  setNeedsDisplay()
  
  perform(#selector(hesitateUpdate), with: nil, afterDelay: 0.1)
}

構(gòu)建并運(yùn)行項(xiàng)目叠荠,當(dāng)你點(diǎn)擊按鈕時(shí),你會(huì)發(fā)現(xiàn)存在差異 - 突出顯示和斜角消失扫责。

但是你可以通過(guò)draw(_:)來(lái)改善效果:

當(dāng)用戶(hù)按下按鈕時(shí)榛鼎,整個(gè)按鈕應(yīng)該變暗。

您可以通過(guò)為名為actualBrightness的亮度創(chuàng)建臨時(shí)變量鳖孤,然后根據(jù)按鈕的狀態(tài)適當(dāng)調(diào)整它來(lái)實(shí)現(xiàn)此目的:

var actualBrightness = brightness

if state == .highlighted {
  actualBrightness -= 0.1
}

然后者娱,在draw(_ :)內(nèi),用actualBrightness替換所有亮度實(shí)例苏揣。

總而言之黄鳍,draw(_ :)函數(shù)現(xiàn)在看起來(lái)像這樣。 它有點(diǎn)長(zhǎng)平匈,但重復(fù)是值得的:

override func draw(_ rect: CGRect) {
  guard let context = UIGraphicsGetCurrentContext() else {
    return
  }
  
  var actualBrightness = brightness
  
  if state == .highlighted {
    actualBrightness -= 0.1
  }
  
  let outerColor = UIColor(
    hue: hue, saturation: saturation, brightness: actualBrightness, alpha: 1.0)
  let shadowColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 0.5)
  
  let outerMargin: CGFloat = 5.0
  let outerRect = rect.insetBy(dx: outerMargin, dy: outerMargin)
  let outerPath = createRoundedRectPath(for: outerRect, radius: 6.0)
  
  if state != .highlighted {
    context.saveGState()
    context.setFillColor(outerColor.cgColor)
    context.setShadow(
      offset: CGSize(width: 0, height: 2), blur: 3.0, color: shadowColor.cgColor)
    context.addPath(outerPath)
    context.fillPath()
    context.restoreGState()
  }
  
  // Outer Path Gloss & Gradient
  let outerTop = UIColor(hue: hue, saturation: saturation, 
    brightness: actualBrightness, alpha: 1.0)
  let outerBottom = UIColor(hue: hue, saturation: saturation, 
    brightness: actualBrightness * 0.8, alpha: 1.0)
  
  context.saveGState()
  context.addPath(outerPath)
  context.clip()
  drawGlossAndGradient(context: context, rect: outerRect, 
    startColor: outerTop.cgColor, endColor: outerBottom.cgColor)
  context.restoreGState()
  
  // Inner Path Gloss & Gradient
  let innerTop = UIColor(hue: hue, saturation: saturation, 
    brightness: actualBrightness * 0.9, alpha: 1.0)
  let innerBottom = UIColor(hue: hue, saturation: saturation, 
    brightness: actualBrightness * 0.7, alpha: 1.0)

  let innerMargin: CGFloat = 3.0
  let innerRect = outerRect.insetBy(dx: innerMargin, dy: innerMargin)
  let innerPath = createRoundedRectPath(for: innerRect, radius: 6.0)
  
  context.saveGState()
  context.addPath(innerPath)
  context.clip()
  drawGlossAndGradient(context: context, rect: innerRect, 
    startColor: innerTop.cgColor, endColor: innerBottom.cgColor)
  context.restoreGState()
}

構(gòu)建并運(yùn)行框沟;點(diǎn)擊它時(shí)按鈕應(yīng)該看起來(lái)很棒!

后記

本篇主要講述了如何制作Glossy效果的按鈕增炭,感興趣的給個(gè)贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末忍燥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子隙姿,更是在濱河造成了極大的恐慌梅垄,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件输玷,死亡現(xiàn)場(chǎng)離奇詭異队丝,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)欲鹏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)机久,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人赔嚎,你說(shuō)我怎么就攤上這事吞加。” “怎么了尽狠?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)叶圃。 經(jīng)常有香客問(wèn)我袄膏,道長(zhǎng),這世上最難降的妖魔是什么掺冠? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任沉馆,我火速辦了婚禮码党,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘斥黑。我一直安慰自己揖盘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布锌奴。 她就那樣靜靜地躺著兽狭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鹿蜀。 梳的紋絲不亂的頭發(fā)上箕慧,一...
    開(kāi)封第一講書(shū)人閱讀 51,754評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音茴恰,去河邊找鬼颠焦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛往枣,可吹牛的內(nèi)容都是我干的伐庭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼分冈,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼圾另!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起丈秩,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤盯捌,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蘑秽,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體饺著,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年肠牲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了幼衰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缀雳,死狀恐怖渡嚣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肥印,我是刑警寧澤识椰,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站深碱,受9級(jí)特大地震影響腹鹉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜敷硅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一功咒、第九天 我趴在偏房一處隱蔽的房頂上張望愉阎。 院中可真熱鬧,春花似錦力奋、人聲如沸榜旦。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)溅呢。三九已至,卻和暖如春滨彻,著一層夾襖步出監(jiān)牢的瞬間藕届,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工亭饵, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留休偶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓辜羊,卻偏偏與公主長(zhǎng)得像踏兜,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子八秃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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