ios开发 方形到圆的动画_画个圆动画,的两种实现。iOS 动画由很浅,入浅,当然是 Swift...

本文介绍了两种在iOS中实现方形到圆形动画的方法:一种使用CAShapeLayer和UIBezierPath结合CABasicAnimation;另一种是自定义CALayer,频繁调用draw(in:)方法并实现动画效果。详细讲解了两种方法的具体实现步骤和关键代码。
摘要由CSDN通过智能技术生成

方法一,使用 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 设置角度的坐标图

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值