どうも、なぜだか本当に駐車場が見つからないみやびです。
どうなってるの、札幌中央区!?
さて、表記の件。
よくある円グラフにアニメーションをつけるってやつなのですが、
下記エントリーが非常に参考になった。
(というか、ほぼほぼパクり)
さすがQiitaさんです。
上記のものを、Swift4用に若干変えたのが下記だ。
import UIKit
struct CircleParamStruct {
let value: Float
let color: UIColor
init(setValue: Float, setColor: UIColor){
self.value = setValue
self.color = setColor
}
}
class CircleAnimationView: UIView {
private let params: Array<CircleParamStruct>
private var endAngle: CGFloat = CGFloat(Double.pi / 2.0) * -1
private let endAngleFirst: CGFloat = CGFloat(Double.pi / 2.0) * -1
private let maxAngle: CGFloat
private let allCount: CGFloat
private let holeFlg: Bool
private let holeWidth: CGFloat
private var animationTimer: Timer?
private var animationWait: CGFloat
private let animationFrame: Int
private let frameCountAll: Double
private var frameCountNow: Int = 0
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(frame: CGRect, params: Array<CircleParamStruct>, setHoleFlg: Bool = false, allCount: CGFloat = 0, setWait: CGFloat = 1.0, setHoleWidth: CGFloat = 0.0, setFps: Int = 60) {
self.params = params
self.holeWidth = setHoleWidth
self.holeFlg = setHoleFlg
self.allCount = allCount
self.animationWait = setWait
self.animationFrame = setFps
self.frameCountAll = Double(Float(setWait) / (1.0 / Float(setFps)))
if params.count == 1 && allCount != 0 {
self.maxAngle = CGFloat(Double.pi * 2) * CGFloat(params[0].value) / CGFloat(allCount)
} else {
self.maxAngle = CGFloat(Double.pi * 2)
}
super.init(frame: frame)
self.isOpaque = false
self.backgroundColor = UIColor.init(red: 0, green: 0, blue: 0, alpha: 0)
}
@objc private func update(){
let angle = CGFloat(Double.pi * 2.0 / self.frameCountAll)
self.endAngle = angle
if(self.endAngle > self.maxAngle) {
//終了
self.animationTimer?.invalidate()
} else {
self.setNeedsDisplay()
}
self.frameCountNow += 1
}
func startAnimatin(){
self.animationTimer = Timer.scheduledTimer(timeInterval: TimeInterval(1.0 / CGFloat(self.animationFrame)), target: self, selector: #selector(self.update), userInfo: nil, repeats: true)
}
override func draw(_ rect: CGRect) {
// Drawing code
let context: CGContext = UIGraphicsGetCurrentContext()!
var x: CGFloat = rect.origin.x
x += rect.size.width / 2
var y: CGFloat = rect.origin.y
y += rect.size.height / 2
var max: CGFloat = 0
if self.allCount == 0 {
for dic: CircleParamStruct in self.params {
let value = CGFloat(dic.value)
max += value
}
} else {
max = self.allCount
}
var startAngleInner: CGFloat = CGFloat(Double.pi / 2) * -1
var endAngleInner: CGFloat = 0
let radius: CGFloat = self.frame.size.width / 2
let radiusInner: CGFloat = self.frame.size.width / 2
for dic: CircleParamStruct in self.params {
let value = CGFloat(dic.value)
endAngleInner = startAngleInner + CGFloat(Double.pi * 2) * (value / max)
if(endAngleInner > self.endAngle) {
endAngleInner = self.endAngle
}
let color: UIColor = dic.color
context.move(to: CGPoint(x: x, y: y))
context.addArc(center: CGPoint(x: x, y: y), radius: radius, startAngle: startAngleInner, endAngle: endAngleInner, clockwise: false)
if self.holeFlg {
context.addArc(center: CGPoint(x: x, y: y), radius: radius - self.holeWidth, startAngle: endAngleInner, endAngle: startAngleInner, clockwise: true)
}
context.setFillColor(color.cgColor)
context.closePath()
context.fillPath()
startAngleInner = endAngleInner
}
}
}
エントリーとの相違点は、
パスを生成するさいのメソッドの実行の仕方と、
アニメーションの実行を、繰り返しのTimerを使うってのに変更してる点、
一個一個のデータの持ち方を、構造体(struct)に変更してる点、
それに伴って、イニシャライザをいじっている。
これを使用するには、下記のようにする。
import UIKit
class ViewController: UIViewController {
var graphView: CircleAnimationView!
override func viewDidLoad() {
super.viewDidLoad()
var params = Array<CircleParamStruct>()
params.append(CircleParamStruct(setValue: 2, setColor: UIColor.blue))
params.append(CircleParamStruct(setValue: 1, setColor: UIColor.red))
params.append(CircleParamStruct(setValue: 4, setColor: UIColor.yellow))
self.graphView = CircleAnimationView(frame: CGRect(x: 0, y: 0, width: 320, height: 320), params: params)
self.view.addSubview(graphView)
graphView.startAnimatin()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
これを、真ん中に穴を開ける場合は、イニシャライザを下記のようにする。
self.graphView = CircleAnimationView(frame: CGRect(x: 0, y: 0, width: 320, height: 320), params: params, setHoleFlg: true, allCount: 0, setWait: 1.0, setHoleWidth: 20)
イニシャライザめんどくさい感じなのはごめんなさない。
もっといいやり方あると思う。
さて、ここまでやって気づいた方もいるでしょうが、
このままだと、アニメーションにイージングをつけることができない。
タイマーで繰り返しやってるだけだからね。
ではどうすれば、イージングをつけるかというと、
回転の角度をアップデートする時に、その角度の数値に対して、
イージングの計算をさせればいいのだ。(当然のことながら、円のアニメーションは、回転の角度によって、アニメーションさせているため)
まず、イージング関数を格納した列挙と構造体を用意する。
下記のエントリーを参照に、計算式を入れていく。
import UIKit
// イージングの種類
public enum EaseType: String {
case Empty = "null",
easeIn = "easeIn",
easeOut = "easeOut",
easeInOut = "easeInOut",
easeInCubic = "easeInCubic",
easeOutCubic = "easeOutCubic",
easeInOutCubic = "easeInOutCubic",
easeInQuart = "easeInQuart",
easeOutQuart = "easeOutQuart",
easeInOutQuart = "easeInOutQuart",
easeInQuint = "easeInQuint",
easeOutQuint = "easeOutQuint",
easeInOutQuint = "easeInOutQuint"
}
// イージング適用の値を出す構造体、メソッド
struct AnimationEasingValue {
public static func valueFunc(easeType: EaseType, t: CGFloat, b: CGFloat, c: CGFloat, d: CGFloat) -> CGFloat {
var ret: CGFloat = 0.0
var ti = t
switch easeType {
case .easeIn:
ti /= d
ret = c*ti*ti + b
case .easeOut:
ti /= d
ret = -c*ti*(ti - 2.0) + b
case .easeInOut:
ti /= d / 2.0
if ti < 1 {
ret = c / 2.0 * ti * ti + b
} else {
ti = ti - 1
ret = -c / 2.0 * (ti*(ti - 2) - 1) + b
}
case .easeInCubic:
ti /= d
ret = c*ti*ti*ti + b
case .easeOutCubic:
ti /= d
ti = ti - 1
ret = c*(ti*ti*ti + 1) + b
case .easeInOutCubic:
ti /= d/2.0
if ti < 1 {
ret = c/2.0*ti*ti*ti + b
} else {
ti = ti - 2
ret = c/2.0 * (ti*ti*ti + 2) + b
}
case .easeInQuart:
ti /= d
ret = c*ti*ti*ti*ti + b
case .easeOutQuart:
ti /= d
ti = ti - 1
ret = -c*(ti*ti*ti*ti - 1) + b
case .easeInOutQuart:
ti /= d/2.0
if ti < 1 {
ret = c/2.0*ti*ti*ti*ti + b
} else {
ti = ti - 2
ret = -c/2.0 * (ti*ti*ti*ti - 2) + b
}
case .easeInQuint:
ti /= d
ret = c*ti*ti*ti*ti*ti + b
case .easeOutQuint:
ti /= d
ti = ti - 1
ret = c*(ti*ti*ti*ti*ti + 1) + b
case .easeInOutQuint:
ti /= d/2.0
if ti < 1 {
ret = c/2.0*ti*ti*ti*ti*ti + b
} else {
ti = ti - 2
ret = c/2.0 * (ti*ti*ti*ti*ti + 2) + b
}
default:
ti /= d
ret = c*ti*ti*ti + b
}
return ret
}
}
さすがに全部のイージングは用意していないが、
列挙型と、関数中身のswitchのcaseを増やしていけば、イージングの種類をなんぼでも作れる。
さて、先ほどの、CircleAnimationViewを変えていこう。
//let angle = CGFloat(Double.pi * 2.0 / self.frameCountAll) let angle = AnimationEasingValue.valueFunc(easeType: .easeOutQuint, t: CGFloat(self.frameCountNow), b: self.endAngleFirst, c: self.maxAngle, d: CGFloat(self.frameCountAll))
これで、円のアニメーションにもイージングをつけることができた。
これ、もちろん円だけでなくあらゆるもののアニメーションに応用できるので、
いろいろ使ってみてほしい。
UIView.animateメソッドでもイージングは効かせられるけど、なんか物足りないって方は、
Timerで、このイージングをつかってみるのもいいかもしれない。
ではまた。
コメントする