-
Notifications
You must be signed in to change notification settings - Fork 0
/
AsunAnimationSwitch.swift
250 lines (201 loc) · 10.7 KB
/
AsunAnimationSwitch.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
//
// AsunSwitch.Swift
// AsunAnimationSwitch
//
// Created by Asun on 2018/8/14.
// Copyright © 2018年 Asun. All rights reserved.
//
import UIKit
public class AsunAnimationSwitch: UIControl {
public var Basic:AsunBasicSet = AsunBasicSet()
private var trailCircle: CAShapeLayer = CAShapeLayer()
private var circle: CAShapeLayer = CAShapeLayer()
private var checkmark: CAShapeLayer = CAShapeLayer()
private var checkmarkMidPoint: CGPoint = CGPoint.zero
private var selected_internal: Bool = false
public override var isSelected: Bool {
get {
return selected_internal
}
set {
super.isSelected = newValue
//todo
self.setSelected(isSelected: newValue, animated: false)
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupViews()
}
public override func awakeFromNib() {
setupViews()
}
public override func layoutSubviews() {
configure()
}
public func setupViews() {
self.backgroundColor = UIColor.clear
configureShapeLayer(shapeLayer: trailCircle)
configureShapeLayer(shapeLayer: circle)
configureShapeLayer(shapeLayer: checkmark)
self.isSelected = false
self.addTarget(self, action: #selector(onTouchUpInside), for: UIControlEvents.touchUpInside)
}
public override func layoutSublayers(of layer: CALayer) {
super.layoutSublayers(of: layer)
if layer == self.layer {
var offset: CGPoint = CGPoint.zero
let radius = fmin(self.bounds.width, self.bounds.height)/2
offset.x = (self.bounds.width - radius * 2) / 2.0
offset.y = (self.bounds.height - radius * 2) / 2.0
CATransaction.begin()
CATransaction.setDisableActions(true)
let ovalRect = CGRect(x: offset.x, y: offset.y, width: radius*2, height: radius*2)
let circlePath = UIBezierPath(ovalIn: ovalRect)
trailCircle.path = circlePath.cgPath
circle.transform = CATransform3DIdentity
circle.frame = self.bounds
circle.path = UIBezierPath(ovalIn: ovalRect).cgPath
circle.transform = CATransform3DMakeRotation(CGFloat(212 * Double.pi / 180), 0, 0, 1)
let origin = CGPoint(x: offset.x + radius, y: offset.y + radius)
var checkStartPoint = CGPoint.zero
checkStartPoint.x = origin.x + radius * CGFloat(cos(212 * Double.pi / 180))
checkStartPoint.y = origin.y + radius * CGFloat(sin(212 * Double.pi / 180))
let checkmarkPath = UIBezierPath()
checkmarkPath.move(to: checkStartPoint)
self.checkmarkMidPoint = CGPoint(x: offset.x + radius * 0.9, y: offset.y + radius * 1.4)
checkmarkPath.addLine(to: self.checkmarkMidPoint)
var checkEndPoint = CGPoint.zero
checkEndPoint.x = origin.x + radius * CGFloat(cos(320 * Double.pi / 180))
checkEndPoint.y = origin.y + radius * CGFloat(sin(320 * Double.pi / 180))
checkmarkPath.addLine(to: checkEndPoint)
checkmark.frame = self.bounds
checkmark.path = checkmarkPath.cgPath
CATransaction.commit()
}
}
private func configure() {
self.circle.lineWidth = Basic.lineWidth
self.checkmark.lineWidth = Basic.lineWidth
self.trailCircle.lineWidth = Basic.lineWidth
self.circle.strokeColor = Basic.strokeColor.cgColor
self.checkmark.strokeColor = Basic.strokeColor.cgColor
self.trailCircle.strokeColor = Basic.trailStrokeColor.cgColor
}
@objc func onTouchUpInside(sender: AnyObject) {
self.setSelected(isSelected: !self.isSelected, animated: true)
self.sendActions(for: UIControlEvents.valueChanged)
}
public func setSelected(isSelected: Bool, animated: Bool) {
self.selected_internal = isSelected
checkmark.removeAllAnimations()
circle.removeAllAnimations()
trailCircle.removeAllAnimations()
self.resetValues(animated: animated)
if animated {
self.addAnimationsForSelected(selected: selected_internal)
}
}
private func configureShapeLayer(shapeLayer: CAShapeLayer) {
shapeLayer.lineJoin = kCALineJoinRound
shapeLayer.lineCap = kCALineCapRound
shapeLayer.lineWidth = Basic.lineWidth
shapeLayer.fillColor = UIColor.clear.cgColor
self.layer.addSublayer(shapeLayer)
}
private func resetValues(animated: Bool) {
CATransaction.begin()
CATransaction.setDisableActions(true)
if (selected_internal && animated) || (selected_internal == false && animated == false) {
checkmark.strokeEnd = 0.0
checkmark.strokeStart = 0.0
trailCircle.opacity = 0.0
circle.strokeStart = 0.0
circle.strokeEnd = 1.0
} else {
checkmark.strokeEnd = Basic.finalStrokeEndForCheckmark
checkmark.strokeStart = Basic.finalStrokeStartForCheckmark
trailCircle.opacity = 1.0
circle.strokeStart = 0.0
circle.strokeEnd = 0.0
}
CATransaction.commit()
}
private func addAnimationsForSelected(selected: Bool) {
let circleAnimationDuration = Basic.animationDuration * 0.5
let checkmarkEndDuration = Basic.animationDuration * 0.8
let checkmarkStartDuration = checkmarkEndDuration - circleAnimationDuration
let checkmarkBounceDuration = Basic.animationDuration - checkmarkEndDuration
let checkmarkAnimationGroup = CAAnimationGroup()
checkmarkAnimationGroup.isRemovedOnCompletion = false
checkmarkAnimationGroup.fillMode = kCAFillModeForwards
checkmarkAnimationGroup.duration = Basic.animationDuration
checkmarkAnimationGroup.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
let checkmarkStrokeEnd = CAKeyframeAnimation(keyPath: "strokeEnd")
checkmarkStrokeEnd.duration = checkmarkEndDuration + checkmarkBounceDuration
checkmarkStrokeEnd.isRemovedOnCompletion = false
checkmarkStrokeEnd.fillMode = kCAFillModeForwards
checkmarkStrokeEnd.calculationMode = kCAAnimationPaced
if selected {
checkmarkStrokeEnd.values = [NSNumber(value: 0.0), NSNumber(value: Float(Basic.finalStrokeEndForCheckmark + Basic.checkmarkBounceAmount)), NSNumber(value: Float(Basic.finalStrokeEndForCheckmark))]
checkmarkStrokeEnd.keyTimes = [NSNumber(value: 0.0), NSNumber(value: checkmarkEndDuration), NSNumber(value: checkmarkEndDuration + checkmarkBounceDuration)]
} else {
checkmarkStrokeEnd.values = [ NSNumber(value: Float(Basic.finalStrokeEndForCheckmark)), NSNumber(value: Float(Basic.finalStrokeEndForCheckmark + Basic.checkmarkBounceAmount)), NSNumber(value: -0.1)]
checkmarkStrokeEnd.keyTimes = [NSNumber(value: 0.0), NSNumber(value: checkmarkBounceDuration), NSNumber(value: checkmarkEndDuration + checkmarkBounceDuration)]
}
let checkmarkStrokeStart = CAKeyframeAnimation(keyPath: "strokeStart")
checkmarkStrokeStart.duration = checkmarkStartDuration + checkmarkBounceDuration
checkmarkStrokeStart.isRemovedOnCompletion = false
checkmarkStrokeStart.fillMode = kCAFillModeForwards
checkmarkStrokeStart.calculationMode = kCAAnimationPaced
if selected {
checkmarkStrokeStart.values = [ NSNumber(value: 0.0), NSNumber(value: Float(Basic.finalStrokeStartForCheckmark + Basic.checkmarkBounceAmount)), NSNumber(value: Float(Basic.finalStrokeStartForCheckmark))]
checkmarkStrokeStart.keyTimes = [NSNumber(value: 0.0), NSNumber(value: checkmarkStartDuration), NSNumber(value: checkmarkStartDuration + checkmarkBounceDuration)]
} else {
checkmarkStrokeStart.values = [NSNumber(value: Float(Basic.finalStrokeStartForCheckmark)), NSNumber(value: Float(Basic.finalStrokeStartForCheckmark + Basic.checkmarkBounceAmount)), NSNumber(value: 0.0)]
checkmarkStrokeStart.keyTimes = [NSNumber(value: 0.0), NSNumber(value: checkmarkBounceDuration), NSNumber(value: checkmarkStartDuration + checkmarkBounceDuration)]
}
if selected {
checkmarkStrokeStart.beginTime = circleAnimationDuration
}
checkmarkAnimationGroup.animations = [checkmarkStrokeEnd, checkmarkStrokeStart]
checkmark.add(checkmarkAnimationGroup, forKey: "checkmarkAnimation")
let circleAnimationGroup = CAAnimationGroup()
circleAnimationGroup.duration = Basic.animationDuration
circleAnimationGroup.isRemovedOnCompletion = false
circleAnimationGroup.fillMode = kCAFillModeForwards
circleAnimationGroup.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
let circleStrokeEnd = CABasicAnimation(keyPath: "strokeEnd")
circleStrokeEnd.duration = circleAnimationDuration
if selected {
circleStrokeEnd.beginTime = 0.0
circleStrokeEnd.fromValue = NSNumber(value: 1.0)
circleStrokeEnd.toValue = NSNumber(value: -0.1)
} else {
circleStrokeEnd.beginTime = Basic.animationDuration - circleAnimationDuration
circleStrokeEnd.fromValue = NSNumber(value: 0.0)
circleStrokeEnd.toValue = NSNumber(value: 1.0)
}
circleStrokeEnd.isRemovedOnCompletion = false
circleStrokeEnd.fillMode = kCAFillModeForwards
circleAnimationGroup.animations = [circleStrokeEnd]
circle.add(circleAnimationGroup, forKey: "circleStrokeEnd")
let trailCircleColor = CABasicAnimation(keyPath: "opacity")
trailCircleColor.duration = Basic.animationDuration
if selected {
trailCircleColor.fromValue = NSNumber(value: 0.0)
trailCircleColor.toValue = NSNumber(value: 1.0)
} else {
trailCircleColor.fromValue = NSNumber(value: 1.0)
trailCircleColor.toValue = NSNumber(value: 0.0)
}
trailCircleColor.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
trailCircleColor.fillMode = kCAFillModeForwards
trailCircleColor.isRemovedOnCompletion = false
trailCircle.add(trailCircleColor, forKey: "trailCircleColor")
}
}