简体   繁体   English

Swift中自定义属性的隐式CALayer动画

[英]Implicit CALayer animations for custom property in Swift

I've created a animatable Core Graphics drawing using PaintCode. 我使用PaintCode创建了一个可动画的Core Graphics绘图。 It's basically a circle meter (not unlike the Apple Watch rings), which basically fills up as a timer counts down. 它基本上是一个圆形表(与Apple Watch戒指不同),它基本上会在计时器倒计时时填满。 The meterLevel controls the fill level of the circle meter, from 0 to 100. Basically, if a timer is set to 10 seconds, I set the meterLevel every 1 seconds to 90, 80, 70, etc... meterLevel控制圆形仪表的填充水平,从0到100.基本上,如果计时器设置为10秒,我将meterLevel每1秒设置为90,80,70等...

This works good, however the animation is only drawn ever 1 second, and looks quite choppy. 这很好用,但是动画只画1秒钟,看起来很不稳定。 Instead, I'd like it be a smooth continuous filling meter. 相反,我希望它是一个平滑的连续灌装仪表。

Looking around it seemed like subclassing CALayer and creating an implicit animation for the meterLevel property might be the way to go. 环顾四周似乎是继承CALayer并为meterLevel属性创建一个隐式动画可能就是这样。 So here is what I have: 所以这就是我所拥有的:

import UIKit

class MeterControlView: UIView
{
var meterLevel: Int = 0 {
    didSet {
        self.layer.setValue(meterLevel, forKey: "meterLevel")
    }
}
var meterText: String = "00:00:00" {
    didSet {
        self.layer.setValue(meterText, forKey: "meterText")
    }
}


override class func layerClass() -> AnyClass {
    return MeterControlLayer.self
}

override func drawRect(rect: CGRect) {
    // Do nothing
    }
}

class MeterControlLayer: CALayer
{
@NSManaged
var meterLevel: Int
var meterText: String = "00:00:00"

override class func needsDisplayForKey(key: String) -> Bool {
    if (key == "meterLevel") {
        return true
    }
    return super.needsDisplayForKey(key)
}

override func actionForKey(key: String) -> CAAction? {
    if (key == "meterLevel") {
        let anim: CABasicAnimation = CABasicAnimation.init(keyPath: key)
        anim.fromValue = self.presentationLayer()?.meterLevel
        anim.duration = 1.0
        return anim
    } else {
        return super.actionForKey(key)
    }
}

override func drawInContext(ctx: CGContext) {
    super.drawInContext(ctx)
    UIGraphicsPushContext(ctx)
    XntervalStyleKit.drawMeterControl(frame: self.bounds, meterTime: meterText, meterLevelValue: CGFloat(meterLevel))
    UIGraphicsPopContext()
    }
}

This unfortunately, doesn't work exactly the way I would expect. 遗憾的是,这并不像我期望的那样完美。 The animation is still a bit choppy, though closer to what I want. 动画仍然有点不稳定,虽然更接近我想要的。

My question is more general though, is this the right way to go about accomplishing what I want to do? 我的问题更为笼统,这是完成我想做的事情的正确方法吗? I couldn't figure out the right way to set the layer properties meterLevel and meterText, without using setValueForKey:. 我无法找到设置图层属性meterLevel和meterText的正确方法,而不使用setValueForKey:。 Is that the right way to do this? 这是正确的方法吗?

Animation/Graphics is definitely new to me. 动画/图形对我来说绝对是新手。 I'm an embedded C software guy trying to get into iOS development as a hobby. 我是一个嵌入式C软件,试图将iOS开发作为一种业余爱好。

The animation is still a bit choppy, though closer to what I want. 动画仍然有点不稳定,虽然更接近我想要的。

Given this, it seems as if Core Animation is actually drawing your layer every frame (or trying to, anyway). 鉴于此,似乎Core Animation实际上每帧都在绘制您的图层(或者尝试,无论如何)。

Unfortunately, once you perform custom layer drawing each frame, your performance becomes main thread-bound: that is, normally, for properties that Core Animation can natively animate (such as bounds), the animation itself is rendered in the render server, which operates out-of-process from your application and has its own, high-priority render thread. 不幸的是,一旦你对每一帧执行自定义图层绘制,你的性能就会变成主线程:通常,对于Core Animation本身可以动画化的属性(例如边界),动画本身会在渲染服务器中呈现,从您的应用程序进行进程外处理,并拥有自己的高优先级渲染线程。 The main thread of your application is free to do whatever it wants during these types of animation without any interruption to the animation. 应用程序的主线程可以在这些动画类型中随意执行任何操作,而不会中断动画。

drawInContext(_:) , however, is called on the main thread of your application. 但是, drawInContext(_:)在应用程序的主线程上调用。 If you put a log statement or breakpoint there, is it called many times over the course of your animation duration? 如果你在那里放一个日志语句或断点,它是否会在动画持续时间内多次调用? If so, then the layer is properly animating. 如果是这样,那么图层就是正确的动画。 Your drawing operations within this function may be what's holding up the animation. 你在这个函数中的绘图操作可能是阻碍动画的。

Try setting drawsAsynchronously to true on your layer. 尝试在图层drawsAsynchronously设置为true This defers drawing commands to a background thread, and it can improve performance sometimes. 这将绘图命令推迟到后台线程,有时可以提高性能。 (Note that most, if not all, UIGraphics and Core Graphics functions are thread safe as of iOS 4, so background drawing is safe.) (请注意,大多数(如果不是全部)UIGraphics和Core Graphics功能在iOS 4中都是线程安全的,因此背景绘图是安全的。)

Additionally, depending on how complex your animation is, you may want to draw several intermediate representations in advance (in the background and in parallel, if possible). 此外,根据动画的复杂程度,您可能需要提前绘制几个中间表示(如果可能,在背景中并行绘制)。 Store these somewhere in memory if they aren't too large so you can simply display some of these bitmaps in your layer instead of drawing them just-in-time. 如果它们不是太大,请将它们存储在内存中,这样您只需在图层中显示一些这些位图,而不是及时绘制它们。

I wrote a UIView subclass called ConcentricProgressRingView which does something similar to what you're trying to do. 我写了一个名为ConcentricProgressRingViewUIView子类,它做了类似于你想要做的事情。

https://github.com/lionheart/ConcentricProgressRingView https://github.com/lionheart/ConcentricProgressRingView

Here's an example of what it looks like: 以下是它的外观示例:

Usage 用法

At the top of your UIViewController , import the module: UIViewController的顶部,导入模块:

import ConcentricProgressRingView

Then, in your viewDidLoad : 然后,在viewDidLoad

let rings = [
    ProgressRing(color: UIColor(.RGB(232, 11, 45)), backgroundColor: UIColor(.RGB(34, 3, 11))),
    ProgressRing(color: UIColor(.RGB(137, 242, 0)), backgroundColor: UIColor(.RGB(22, 33, 0))),
    ProgressRing(color: UIColor(.RGB(0, 200, 222)), backgroundColor: UIColor(.RGB(0, 30, 28)))
]
let progressRingView = try! ConcentricProgressRingView(center: view.center, radius: radius, margin: margin, rings: rings, defaultColor: UIColor.clearColor(), defaultWidth: 18)
view.addSubview(progressRingView)

Once you've instantiated your ConcentricProgressRingView instance, animate a specific ring to a percentage with setProgress . 实例化ConcentricProgressRingView实例后,使用setProgress将特定环设置为百分比。

ring.arcs[1].setProgress(0.5, duration: 2)

How it works 这个怎么运作

I'm not exactly sure what I'm doing differently from your example code in the question, but I'm creating a CABasicAnimation and setting a few parameters on it to tweak the animation behavior. 我不确定我在做什么与你的问题中的示例代码有什么不同,但我正在创建一个CABasicAnimation并在其上设置一些参数来调整动画行为。 The code is open source, so check it out. 代码是开源的,所以请查看。 Hope this helps! 希望这可以帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM