[英]Animate an expanding drawing in iOS UIView Swift
我想使用CGContext在UIView中創建一個自定義動態繪圖,這樣當我在其上執行UIView動畫時,繪圖將在動畫/插值期間呈現。 我嘗試使用自定義CALayer並覆蓋其繪制方法,但失敗了。 我嘗試了子類化UIView繪圖(_ in:CGRect),但這只是第一次繪制。 這就是我想要做的:
class RadioWaveView : UIView {
override func draw(_ in:CGRect) {
// draw something, for example an ellipse based on frame size, something like this:
// let context = CGGraphicsCurrentContext()
// context.addEllipse(self.frame)
// Unfortunately this method is called only once, not during animation
}
}
在viewDidAppear()
:
UIView.animate(withDuration: 5, delay: 1, options: [.repeat], animations: {
self.myRadioWaveView.frame = CGRect(x:100,y:100,width:200,heigh:300)
}, completion: { flag in
})
動畫是有效的,因為我設置了myRadioWaveView
的背景顏色,並在屏幕上看到它的擴展。 在draw
中放置斷點表明它只執行一次。
編輯:本質上,我問這個問題:我們如何創建自定義UIView並使用UIView.animate()來動畫該視圖的渲染? 讓我們保留UIView.animate()方法,我們如何設置擴展圓(或UIView中的任何其他自定義繪圖)的動畫?
編輯:這是我創建的自定義類,只繪制一個三角形。 當我為其邊界設置動畫時,三角形會與視圖的基礎圖像縮放,但在動畫期間不會調用繪制方法。 我不想那樣; 我希望draw()方法在動畫的每一幀重繪圖形。
open class TriangleView : UIView {
override open func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()!
let path = CGMutablePath()
path.move(to: CGPoint.zero)
path.addLine(to: CGPoint(x:self.frame.width,y:0))
path.addLine(to: CGPoint(x:self.frame.width/2,y:self.frame.height))
path.addLine(to: CGPoint.zero)
path.closeSubpath()
context.beginPath()
context.setStrokeColor(UIColor.white.cgColor)
context.setLineWidth(3)
context.addPath(path)
context.closePath()
context.strokePath()
}
}
我一直在處理你的問題,這種方法是使用Using CABasicAnimation
和CAShapeLayer
我定義了一個自定義的UIView
類來完成工作,我將進一步改進@IBDesignable
和@IBInspectables
import UIKit
class RadioWaveAnimationView: UIView {
var animatableLayer : CAShapeLayer?
override func awakeFromNib() {
super.awakeFromNib()
self.layer.cornerRadius = self.bounds.height/2
self.animatableLayer = CAShapeLayer()
self.animatableLayer?.fillColor = self.backgroundColor?.cgColor
self.animatableLayer?.path = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).cgPath
self.animatableLayer?.frame = self.bounds
self.animatableLayer?.cornerRadius = self.bounds.height/2
self.animatableLayer?.masksToBounds = true
self.layer.addSublayer(self.animatableLayer!)
self.startAnimation()
}
func startAnimation()
{
let layerAnimation = CABasicAnimation(keyPath: "transform.scale")
layerAnimation.fromValue = 1
layerAnimation.toValue = 3
layerAnimation.isAdditive = false
let layerAnimation2 = CABasicAnimation(keyPath: "opacity")
layerAnimation2.fromValue = 1
layerAnimation2.toValue = 0
layerAnimation2.isAdditive = false
let groupAnimation = CAAnimationGroup()
groupAnimation.animations = [layerAnimation,layerAnimation2]
groupAnimation.duration = CFTimeInterval(2)
groupAnimation.fillMode = kCAFillModeForwards
groupAnimation.isRemovedOnCompletion = true
groupAnimation.repeatCount = .infinity
self.animatableLayer?.add(groupAnimation, forKey: "growingAnimation")
}
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
}
結果
希望這可以幫助
使用這種最簡單的方法
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var animatableView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//Circle
//animatableView.layer.cornerRadius = animatableView.bounds.size.height/2
//animatableView.layer.masksToBounds = true
//triangle
self.applyTriangle(givenView: animatableView)
UIView.animate(withDuration: 2, delay: 1, options: [.repeat], animations: {
self.animatableView.transform = CGAffineTransform(scaleX: 2, y: 2)
}) { (finished) in
self.animatableView.transform = CGAffineTransform.identity
}
}
func applyTriangle(givenView: UIView){
let bezierPath = UIBezierPath()
bezierPath.move(to: CGPoint(x: givenView.bounds.width / 2, y: givenView.bounds.width))
bezierPath.addLine(to: CGPoint(x: 0, y: 0))
bezierPath.addLine(to: CGPoint(x: givenView.bounds.width, y: 0))
bezierPath.close()
let shapeLayer = CAShapeLayer(layer: givenView.layer)
shapeLayer.path = bezierPath.cgPath
shapeLayer.frame = givenView.bounds
shapeLayer.masksToBounds = true
givenView.layer.mask = shapeLayer
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
結果
希望這對你有所幫助
一個中有兩個答案。首先,如果你真的希望每次傳遞都調用drawRect
,你應該使用CADisplayLink
並手動設置視圖框架的動畫。 第二:如果您不想這樣做但仍希望它順利繪制,則將contentMode設置為scaleAspectFit。 這將使其與UIView.animateWithDuration
順利UIView.animateWithDuration
。 但是,它不會像CADisplayLink
解決方案那樣在每個周期調用drawRect
。
使用CADisplayLink
每幀更新邊界。 添加needsDisplayOnBoundsChange = true
以便在更改邊界時重繪視圖。
http://i.imgur.com/JjarYsq.gif
例:
//
// ViewController.swift
// StackOverflow
//
// Created by Brandon T on 2017-07-22.
// Copyright © 2017 XIO. All rights reserved.
//
import UIKit
class RadioWave : UIView {
private var dl: CADisplayLink?
private var startTime: CFTimeInterval!
private var fromFrame: CGRect!
private var toFrame: CGRect!
private var duration: CFTimeInterval!
private var completion: (() -> Void)?
override init(frame: CGRect) {
super.init(frame: frame)
self.isOpaque = false
self.layer.needsDisplayOnBoundsChange = true;
self.fromFrame = frame;
self.toFrame = frame
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func animateToFrame(frame: CGRect, duration: CFTimeInterval, completion:(() -> Void)? = nil) {
self.dl?.remove(from: .current, forMode: .commonModes)
self.dl?.invalidate()
self.dl = CADisplayLink(target: self, selector: #selector(onUpdate(dl:)))
self.completion = completion
self.fromFrame = self.frame
self.toFrame = frame
self.duration = duration
self.startTime = CACurrentMediaTime()
self.dl?.add(to: .current, forMode: .commonModes)
}
override func draw(_ rect: CGRect) {
let ctx = UIGraphicsGetCurrentContext()
ctx?.clear(rect)
ctx?.addEllipse(in: rect)
ctx?.clip()
ctx?.setFillColor(UIColor.blue.cgColor)
ctx?.fill(rect)
}
@objc
func onUpdate(dl: CADisplayLink) {
let dt = CGFloat((dl.timestamp - self.startTime) / self.duration)
if (dt > 1.0) {
self.frame = self.toFrame
self.dl?.remove(from: .current, forMode: .commonModes)
self.dl?.invalidate()
self.dl = nil
completion?()
completion = nil
return;
}
var frame: CGRect! = self.toFrame;
frame.origin.x = (self.toFrame.origin.x - self.fromFrame.origin.x) * dt + fromFrame.origin.x
frame.origin.y = (self.toFrame.origin.y - self.fromFrame.origin.y) * dt + fromFrame.origin.y
frame.size.width = (self.toFrame.size.width - self.fromFrame.size.width) * dt + fromFrame.size.width
frame.size.height = (self.toFrame.size.height - self.fromFrame.size.height) * dt + fromFrame.size.height
self.frame = frame
}
}
class ViewController: UIViewController {
var radioWave: RadioWave!
override func viewDidLoad() {
super.viewDidLoad()
self.radioWave = RadioWave(frame: CGRect(x: self.view.center.x - 10.0, y: self.view.center.y - 10.0, width: 20.0, height: 20.0))
self.view.addSubview(self.radioWave)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
let smallFrame = CGRect(x: self.view.center.x - 10.0, y: self.view.center.y - 10.0, width: 20.0, height: 20.0)
let largeFrame = CGRect(x: self.view.center.x - 100.0, y: self.view.center.y - 100.0, width: 200.0, height: 200.0)
self.radioWave.animateToFrame(frame: largeFrame, duration: 2.0, completion: {
self.radioWave.animateToFrame(frame: smallFrame, duration: 2.0)
})
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
通常只有在邊界發生變化時才會重新繪制視圖。 iOS上的動畫實際上只是內插視圖的快照(表示層)。 這樣可以提高性能,而不是重繪並在每個動畫步驟中布置視圖。
使用[UIView animateWithDuration]
圓圈動畫
class RadioWave : UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.isOpaque = false
self.layer.needsDisplayOnBoundsChange = true
self.contentMode = .scaleAspectFit
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
let ctx = UIGraphicsGetCurrentContext()
ctx?.clear(rect)
ctx?.addEllipse(in: rect)
ctx?.clip()
ctx?.setFillColor(UIColor.blue.cgColor)
ctx?.fill(rect)
}
}
class ViewController: UIViewController {
var radioWave: RadioWave!
override func viewDidLoad() {
super.viewDidLoad()
self.radioWave = RadioWave(frame: CGRect(x: self.view.center.x - 10.0, y: self.view.center.y - 10.0, width: 20.0, height: 20.0))
self.view.addSubview(self.radioWave)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
let largeFrame = CGRect(x: self.view.center.x - 100.0, y: self.view.center.y - 100.0, width: 200.0, height: 200.0)
self.radioWave.setNeedsDisplay()
UIView.animate(withDuration: 2.0, delay: 0.0, options: [.layoutSubviews, .autoreverse, .repeat], animations: {
self.radioWave.frame = largeFrame
}, completion: { (completed) in
})
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
以上使用contentMode scaleAspectFit
,它允許動畫正確縮放而不會模糊! 這是我所知道的唯一方法,在動畫時畫出光滑的圓圈。 它不會在每個動畫的開頭和結尾之前調用drawRect。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.