這篇文章通過使用CAShapeLayer和UIBezierPath來畫出一個動態顯示剩餘流量的小動畫。
最終實現的效果如下:
動態效果圖:
1. CAShapeLayer
實際中,能夠用CALayer完成的任務是比較少的,如果使用這個基礎圖層就能實現絕大部分的功能,我們們就沒有必要再開啟一個CAShapeLayer了嘛。
1.1 CAShapeLayer的優點
那CAShapeLayer到底有啥子優點嘛!
- CAShapeLayer作為繼承自CALayer的子類,當然可使用CALayer的所有屬性。也就是說,爹有的它都有了。
- CAShapeLayer是一個通過向量圖形而不是點陣圖來繪製的圖層子類。指定諸如顏色和線寬等屬性,用path來定義想要繪製的圖形,最後CAShapeLayer就自動渲染出來了。也就是說,CAShapeLayer不需要像普通CALayer一樣建立一個寄宿圖形。而且是向量圖形噢!!所以無論有多大,都不會佔用太多的記憶體。
- CAShapeLayer使用了硬體加速,繪製同一圖形會比用CoreGraphics快很多。
1.2 基本屬性
屬性名 | 作用 |
---|---|
path | 影像的繪製路徑,path不支援隱式動畫 |
fillColor | 填充path的顏色,或無填充。預設為不透明黑色。 |
fillRule | 填充path的規則。選項是非零和偶奇。預設為非零。 |
lineCap | 線端點型別 |
lineDashPattern | 線性模版 |
lineDashPhase | 線型模版的起點 |
lineJoin | 線連線型別 |
lineWidth | 線寬 |
miterLimit | 最大斜接長度。 |
strokeColor | 描邊顏色 |
strokeStart | 描邊的起點 |
strokeEnd | 描邊的終點 |
1.3 屬性解讀
能看到這裡,說明您已經不是一個沒有任何基礎的小白了。所以特別基礎的屬性就沒必要解釋一遍了。下面只是一個不常用或者立即起來稍微費點勁的屬性。
lineDashPattern
: 這是一個NSNumber的陣列,索引從1開始記,奇數位數值表示實線長度,偶數位數值表示空白長度。系統會按照數值自動重複設定虛線。miterLimit
:最大斜接長度。斜接長度指的是在兩條線交匯處和外交之間的距離。只有lineJoin屬性為kCALineJoinMiter時miterLimit才有效。邊角的角度越小,斜接長度就會越大。為了避免斜接長度過長,我們可以使用miterLimit屬性。如果斜接長度超過miterLimit的值,邊角會以lineJoin的“bevel”即kCALineJoinBevel型別來顯示strokeStart
&strokeEnd
: 描邊的起始點位置。範圍為0~1.
1.3.1 lineDashPattern畫虛線
basicLayer.lineDashPattern = [5,2,10,7]複製程式碼
這句話的意思是說這個虛線由四部分組成:
- 第一段實線長度為5
- 畫完長度為5畫素的實線之後,空2畫素
- 空完2畫素之後,再畫10畫素的實線
- 畫完長度為10畫素的實線之後,空7畫素
然後重複這個陣列中的數值,一直不停的繪畫。
1.3.2 strokeStart & strokeEnd
strokeStart它表示描線開始的地方佔總路徑的百分比。預設值是0。
strokeEnd表示繪製結束的地方站總路徑的百分比。預設值是1,如果小於等於strokeStart 則繪製不出任何內容。
手畫一張圖,解釋一下啥意思:
2. 實戰:繪製一個鏤空圖層動畫
做好後的效果如下:
fileprivate func hollowLayer(){
// 建立空心的layer
let hollowLayer = CAShapeLayer()
hollowLayer.bounds = CGRect(x: 0, y: 0, width: 100, height: 100)
view.layer.addSublayer(hollowLayer)
hollowLayer.position = view.center
// 最外面待圓角的方形path
let squarePath = UIBezierPath.init(roundedRect: CGRect(x: 0, y: 0, width: 100, height: 100), cornerRadius: 5)
// 中間鏤空的圓形path
let hollowPath = UIBezierPath.init(ovalIn: CGRect(x: 10, y: 10, width: 80, height: 80))
squarePath.append(hollowPath)
hollowLayer.path = squarePath.cgPath
hollowLayer.fillColor = UIColor.lightGray.cgColor
// 設定路徑的填充模式為兩個圖形的非交集
hollowLayer.fillRule = kCAFillRuleEvenOdd
// 建立進度layer
let processSectorLayer = CAShapeLayer()
view.layer.addSublayer(processSectorLayer)
processSectorLayer.bounds = CGRect(x: 0, y: 0, width: 70, height: 70)
processSectorLayer.position = view.center
// 進度的path
let processSectorPath = UIBezierPath.init(arcCenter: CGPoint.init(x: 35, y: 35), radius: 17.5, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
processSectorLayer.path = processSectorPath.cgPath
processSectorLayer.lineWidth = 35
// 進度的起點和結束位置,設定進度條修改這個值和結束數值就可以了
processSectorLayer.strokeStart = 0.5
processSectorLayer.strokeEnd = 0.75
processSectorLayer.strokeColor = UIColor.lightGray.cgColor
processSectorLayer.fillColor = UIColor.clear.cgColor
}複製程式碼
3. 使用CAShapeLayer繪畫動態流量圖
有了上面對於CAShapeLayer 的基礎訓練,繪製一個動態的流量圖就不是什麼困難的事情了。
實現後的效果如下:
3.1 實現思路
1,建立一個view,用來展示進度圓環。
2,在進度的view上面新增一個layer,用來展示進度圓環底部灰色的圓環。
3,在灰色的圓環上面,新增一個layer,用來顯示實際的進度。
4,建立一個定時器,定時器用來更新時時進度。
3.2 程式碼實現
在文章裡面我們們只PO出來一些關鍵的程式碼,如果想檢視原始檔,可以自行下載原始碼哈。
3.2.1 懶載入進度圓環的shapeLayer
// 進度條layer
lazy var circleProgressLayer: CAShapeLayer = {
let circleProgressLayer = CAShapeLayer()
let circleBounds = CGRect(x: 0, y: 0, width: 250, height: 250)
circleProgressLayer.bounds = circleBounds
circleProgressLayer.position = CGPoint(x: circleBounds.width / 2, y: circleBounds.height / 2)
let circleProgressPath = UIBezierPath.init(arcCenter: CGPoint(x: circleBounds.width / 2, y: circleBounds.height / 2), radius: circleBounds.height / 2, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
circleProgressLayer.strokeStart = 0
circleProgressLayer.strokeEnd = 1
circleProgressLayer.path = circleProgressPath.cgPath
circleProgressLayer.lineWidth = 10
circleProgressLayer.strokeColor = UIColor.init(colorLiteralRed: 0, green: 151, blue: 255, alpha: 1).cgColor
circleProgressLayer.fillColor = UIColor.clear.cgColor
return circleProgressLayer
}()複製程式碼
3.2.2 新增定時器
//開啟定時器
timer = Timer.scheduledTimer(timeInterval: 0.04, target: self, selector: #selector(progressShowNumber), userInfo: nil, repeats: true)複製程式碼
3.2.3 定時器的呼叫事件
// 定時器呼叫的方法
@objc private func progressShowNumber(){
if progressValue > expectValue - 1 && progressValue < expectValue {
timer.invalidate()
circleProgressLayer.strokeEnd = expectValue / 100
progressLabel.text = "\(expectValue)%"
return
}
if progressValue > expectValue {
timer.invalidate()
return
}
//更新進度文字和進度條的strokeEnd
circleProgressLayer.strokeEnd = CGFloat(progressValue) / 100
progressLabel.text = "\(progressValue)%"
progressValue += 1
}複製程式碼
這兩天人正好在San Francisco,抽空去San Jose蘋果的大本營溜達溜達。到時候給大家放照哈。
原始碼可以在這裡下載。git.oschina.net/atypical/mu…
-----------------------華麗分割線,iOS動畫系列全集連結-------------------------------------------------
第一篇:iOS動畫系列之一:通過實戰學習CALayer和透視的原理。做一個帶時分秒指標的時鐘動畫(上)
第二篇:iOS動畫系列之二:通過實戰學習CALayer和透視的原理。做一個帶時分秒指標的時鐘動畫。包含了OC和Swift兩種原始碼(下)
第三篇:iOS動畫系列之三:Core Animation。介紹了Core Animation的常用屬性和方法。
第四篇:CABasic Animation。iOS動畫系列之四:基礎動畫之平移篇
第五篇:CABasic Animation。iOS動畫系列之五:基礎動畫之縮放篇&旋轉篇
第六篇:iOS動畫系列之六:利用CABasic Animation完成帶動畫特效的登入介面
第七篇:iOS動畫系列之七:實現類似Twitter的啟動動畫
第八篇:iOS動畫系列之八:使用CAShapeLayer繪畫動態流量圖
第九篇:iOS動畫系列之九:實現點讚的動畫及播放起伏指示器
第十篇:實戰系列:繪製雲霄飛車場景