ios开发 方形到圆的动画_画个圆动画,的两种实现。iOS 动画由很浅,入浅,当然是 Swift...
方法一,使用 CAShapeLayer 和 UIBezierPath
加上 CABasicAnimation 有一個動畫屬性 strokeEnd
就算完
方法二,復雜一些。頻繁調用 CALayer 的 func draw(in ctx: CGContext) 也是可以的
通過定制 CALayer, 還要有一個使用該定制 CALayer 的 custom 視圖。使用 @NSManaged, 方便自定制的 CALayer 鍵值觀察 KVC
重寫 CALayer 的方法 action(forKey:), 指定需要的動畫
重寫 CALayer 的方法 needsDisplay(forKey:), 先指定刷新渲染,再出 action(forKey:) 的動畫
方法一的,具體實現
class CircleView: UIView {
let circleLayer: CAShapeLayer = {
// 形狀圖層,初始化與屬性配置
let circle = CAShapeLayer()
circle.fillColor = UIColor.clear.cgColor
circle.strokeColor = UIColor.red.cgColor
circle.lineWidth = 5.0
circle.strokeEnd = 0.0
return circle
}()
// 視圖創建,通過指定 frame
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
// 視圖創建,通過指定 storyboard
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
func setup(){
backgroundColor = UIColor.clear
// 添加上,要動畫的圖層
layer.addSublayer(circleLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
// 考慮到視圖的布局,如通過 auto layout,
// 需動畫圖層的布局,放在這里
let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(Double.pi * 2.0), clockwise: true)
circleLayer.path = circlePath.cgPath
}
// 動畫的方法
func animateCircle(duration t: TimeInterval) {
// 畫圓形,就是靠 `strokeEnd`
let animation = CABasicAnimation(keyPath: "strokeEnd")
// 指定動畫時長
animation.duration = t
// 動畫是,從沒圓,到滿圓
animation.fromValue = 0
animation.toValue = 1
// 指定動畫的時間函數,保持勻速
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
// 視圖具體的位置,與動畫結束的效果一致
circleLayer.strokeEnd = 1.0
// 開始動畫
circleLayer.add(animation, forKey: "animateCircle")
}
}
使用的代碼 : 很簡單
class ViewController: UIViewController {
// storyboard 布局
@IBOutlet weak var circleV: CircleView!
@IBAction func animateFrame(_ sender: UIButton) {
let diceRoll = CGFloat(Int(arc4random_uniform(7))*30)
let circleEdge = CGFloat(200)
// 直接指定 frame 布局
let circleView = CircleView(frame: CGRect(x: 50, y: diceRoll, width: circleEdge, height: circleEdge))
view.addSubview(circleView)
// 開始動畫
circleView.animateCircle(duration: 1.0)
}
@IBAction func animateAutolayout(_ sender: UIButton) {
// auto layout 布局
let circleView = CircleView(frame: CGRect.zero)
circleView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(circleView)
circleView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
circleView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
circleView.widthAnchor.constraint(equalToConstant: 250).isActive = true
circleView.heightAnchor.constraint(equalToConstant: 250).isActive = true
// 開始動畫
circleView.animateCircle(duration: 1.0)
}
@IBAction func animateStoryboard(_ sender: UIButton) {
// 開始動畫
circleV.animateCircle(duration: 1.0)
}
}
方法二的實現
核心類 UICircularRingLayer 的技術注意:
先要自定制一個基于 CAShapeLayer 的圖層
對 @NSManaged var val: CGFloat KVC,
觸發 override class func needsDisplay(forKey key: String) -> Bool ,
調用 setNeedsDisplay(),重新渲染,
接著觸發 override func action(forKey event: String) -> CAAction?, 指定動畫,
頻繁調用繪制方法 override func draw(in ctx: CGContext), 就是可見的動畫
@NSManaged 關鍵字,類似 Objective-C 里面的 @dynamic 關鍵字
@NSManaged 關鍵字,方便鍵值編碼
@NSManaged 通知編譯器,不要初始化,運行時保證有值
override class func needsDisplay(forKey key: String) -> Bool 返回 true
就是需要重新渲染,調用 setNeedsDisplay() 方法
下面的
override class func needsDisplay(forKey key: String) -> Bool {
if key == "val" {
return true
} else {
return super.needsDisplay(forKey: key)
}
}
相當于
override class func needsDisplay(forKey key: String) -> Bool {
if key == "val" {
return true
} else {
return false
}
}
override func action(forKey event: String) -> CAAction?, 返回協議對象 CAAction
CAAnimation 遵守 CAAction 協議,這里一般返回個 CAAnimation
一個 CALayer 圖層,可以有動態的動畫行為。
發起動畫時,可以設置該圖層的動畫屬性,操作關聯出來的具體動畫
下面的
override func action(forKey event: String) -> CAAction? {
if event == "val"{
// 實際動畫部分
let animation = CABasicAnimation(keyPath: "val")
// ...
return animation
} else {
return super.action(forKey: event)
}
}
相當于
override func action(forKey event: String) -> CAAction? {
if event == "val"{
// 實際動畫部分
let animation = CABasicAnimation(keyPath: "val")
// ...
return animation
} else {
return nil
}
}
方法二的,具體實現
/**
動畫起作用的樞紐,
負責處理繪制和動畫,
對于使用者隱藏,使用者操作外部的視圖類就好
*/
class UICircularRingLayer: CAShapeLayer {
// MARK: 屬性
@NSManaged var val: CGFloat
let ringWidth: CGFloat = 20
let startAngle = CGFloat(-90).rads
// MARK: 初始化
override init() {
super.init()
}
override init(layer: Any) {
// 確保使用姿勢
guard let layer = layer as? UICircularRingLayer else { fatalError("unable to copy layer") }
super.init(layer: layer)
}
required init?(coder aDecoder: NSCoder) { return nil }
// MARK: 視圖渲染部分
/**
重寫 draw(in 方法,畫圓環
*/
override func draw(in ctx: CGContext) {
super.draw(in: ctx)
UIGraphicsPushContext(ctx)
// 畫圓環
drawRing(in: ctx)
UIGraphicsPopContext()
}
// MARK: 動畫部分
/**
監聽 val 屬性的變化,重新渲染
*/
override class func needsDisplay(forKey key: String) -> Bool {
if key == "val" {
return true
} else {
return super.needsDisplay(forKey: key)
}
}
/**
監聽 val 屬性的變化,指定動畫行為
*/
override func action(forKey event: String) -> CAAction? {
if event == "val"{
// 實際動畫部分
let animation = CABasicAnimation(keyPath: "val")
animation.fromValue = presentation()?.value(forKey: "val")
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
animation.duration = 2
return animation
} else {
return super.action(forKey: event)
}
}
/**
畫圓,通過路徑布局。主要是指定 UIBezierPath 曲線的角度
*/
private func drawRing(in ctx: CGContext) {
let center: CGPoint = CGPoint(x: bounds.midX, y: bounds.midY)
let radiusIn: CGFloat = (min(bounds.width, bounds.height) - ringWidth)/2
// 開始畫
let innerPath: UIBezierPath = UIBezierPath(arcCenter: center,
radius: radiusIn,
startAngle: startAngle,
endAngle: toEndAngle,
clockwise: true)
// 具體路徑
ctx.setLineWidth(ringWidth)
ctx.setLineJoin(.round)
ctx.setLineCap(CGLineCap.round)
ctx.setStrokeColor(UIColor.red.cgColor)
ctx.addPath(innerPath.cgPath)
ctx.drawPath(using: .stroke)
}
// 本例子中,起始角度固定,終點角度通過 val 設置
var toEndAngle: CGFloat {
return (val * 360.0).rads + startAngle
}
}
輔助方法,用于角度轉弧度
extension CGFloat {
var rads: CGFloat { return self * CGFloat.pi / 180 }
}
觸發類
自定制 UIView,指定其圖層為,之前的定制圖層
@IBDesignable open class UICircularRing: UIView {
/**
將 UIView 自帶的 layer,強轉為上面的 UICircularRingLayer, 方便使用
*/
var ringLayer: UICircularRingLayer {
return layer as! UICircularRingLayer
}
/**
將 UIView 自帶的 layer,重寫為 UICircularRingLayer
*/
override open class var layerClass: AnyClass {
return UICircularRingLayer.self
}
/**
通過 frame 初始化,的設置
*/
override public init(frame: CGRect) {
super.init(frame: frame)
setup()
}
/**
通過 storyboard 初始化,的設置
*/
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
/**
初始化的配置
*/
func setup(){
// 設置光柵化
// 將光柵化后的內容緩存起來,方便復用
ringLayer.contentsScale = UIScreen.main.scale
ringLayer.shouldRasterize = true
ringLayer.rasterizationScale = UIScreen.main.scale * 2
ringLayer.masksToBounds = false
backgroundColor = UIColor.clear
ringLayer.backgroundColor = UIColor.clear.cgColor
ringLayer.val = 0
}
func startAnimation() {
ringLayer.val = 1
}
}
使用的代碼,很簡單
class ViewController: UIViewController {
let progressRing = UICircularRing(frame: CGRect(x: 100, y: 100, width: 250, height: 250))
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(progressRing)
}
@IBAction func animate(_ sender: UIButton) {
progressRing.startAnimation()
}
}
方法二,設置線條帽,為圓頭,比較方便
ctx.setLineCap(CGLineCap.round)
iOS 設置角度的坐標圖
總結
以上是生活随笔為你收集整理的ios开发 方形到圆的动画_画个圆动画,的两种实现。iOS 动画由很浅,入浅,当然是 Swift...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: lock交替打印_面试题Synchron
- 下一篇: quartus2管教锁定出不来_Quar