创建自定义的可动画属性

时间:2013-01-07 09:09:51

标签: iphone ios uiview core-animation

UIView上,您可以更改backgroundColour动画。在UISlideView上,您可以更改动画值。

您可以将自定义属性添加到自己的UIView子类中,以便对其进行动画处理吗?

如果我CGPath中有UIView,那么我可以通过更改路径绘制的百分比来设置它的绘图。

我可以将该动画封装到子类中吗。

即。我有一个UIViewCGPath创建了一个圆圈。

如果圆圈不存在则代表0%。如果圆圈已满,则表示100%。我可以通过改变路径绘制的百分比来绘制任何值。我还可以通过设置CGPath的百分比并重新绘制路径来动画更改(在UIView子类中)。

我可以在UIView上设置一些属性(即百分比),这样我就可以将更改粘贴到UIView animateWithDuration块中,并对路径百分比的变化进行动画处理吗?

我希望我已经解释了我想做的事情。

基本上,我想做的就是......

[UIView animateWithDuration:1.0
 animations:^{
     myCircleView.percentage = 0.7;
 }
 completion:nil];

并且圆圈路径以给定百分比为动画。

5 个答案:

答案 0 :(得分:23)

如果您扩展CALayer并实施自定义

- (void) drawInContext:(CGContextRef) context

您可以通过覆盖needsDisplayForKey(在自定义CALayer类中)来制作可动画的属性,如下所示:

+ (BOOL) needsDisplayForKey:(NSString *) key {
    if ([key isEqualToString:@"percentage"]) {
        return YES;
    }
    return [super needsDisplayForKey:key];
}

当然,您还需要@property名为percentage。从现在开始,您可以使用核心动画为百分比属性设置动画。我也没有使用[UIView animateWithDuration...]调用检查它是否有效。它可能会奏效。但这对我有用:

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"percentage"];
animation.duration = 1.0;
animation.fromValue = [NSNumber numberWithDouble:0];
animation.toValue = [NSNumber numberWithDouble:100];

[myCustomLayer addAnimation:animation forKey:@"animatePercentage"];

哦,要将yourCustomLayermyCircleView一起使用,请执行以下操作:

[myCircleView.layer addSublayer:myCustomLayer];

答案 1 :(得分:12)

完成Swift 3示例:

Final product

public class CircularProgressView: UIView {

    public dynamic var progress: CGFloat = 0 {
        didSet {
            progressLayer.progress = progress
        }
    }

    fileprivate var progressLayer: CircularProgressLayer {
        return layer as! CircularProgressLayer
    }

    override public class var layerClass: AnyClass {
        return CircularProgressLayer.self
    }


    override public func action(for layer: CALayer, forKey event: String) -> CAAction? {
        if event == #keyPath(CircularProgressLayer.progress),
            let action = action(for: layer, forKey: #keyPath(backgroundColor)) as? CAAnimation {

            let animation = CABasicAnimation()
            animation.keyPath = #keyPath(CircularProgressLayer.progress)
            animation.fromValue = progressLayer.progress
            animation.toValue = progress
            animation.beginTime = action.beginTime
            animation.duration = action.duration
            animation.speed = action.speed
            animation.timeOffset = action.timeOffset
            animation.repeatCount = action.repeatCount
            animation.repeatDuration = action.repeatDuration
            animation.autoreverses = action.autoreverses
            animation.fillMode = action.fillMode
            animation.timingFunction = action.timingFunction
            animation.delegate = action.delegate
            self.layer.add(animation, forKey: #keyPath(CircularProgressLayer.progress))
        }
        return super.action(for: layer, forKey: event)
    }

}



/*
* Concepts taken from:
* https://stackoverflow.com/a/37470079
*/
fileprivate class CircularProgressLayer: CALayer {
    @NSManaged var progress: CGFloat
    let startAngle: CGFloat = 1.5 * .pi
    let twoPi: CGFloat = 2 * .pi
    let halfPi: CGFloat = .pi / 2


    override class func needsDisplay(forKey key: String) -> Bool {
        if key == #keyPath(progress) {
            return true
        }
        return super.needsDisplay(forKey: key)
    }

    override func draw(in ctx: CGContext) {
        super.draw(in: ctx)

        UIGraphicsPushContext(ctx)

        //Light Grey
        UIColor.lightGray.setStroke()

        let center = CGPoint(x: bounds.midX, y: bounds.midY)
        let strokeWidth: CGFloat = 4
        let radius = (bounds.size.width / 2) - strokeWidth
        let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: twoPi, clockwise: true)
        path.lineWidth = strokeWidth
        path.stroke()


        //Red
        UIColor.red.setStroke()

        let endAngle = (twoPi * progress) - halfPi
        let pathProgress = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle , clockwise: true)
        pathProgress.lineWidth = strokeWidth
        pathProgress.lineCapStyle = .round
        pathProgress.stroke()

        UIGraphicsPopContext()
    }
}

let circularProgress = CircularProgressView(frame: CGRect(x: 0, y: 0, width: 80, height: 80))
UIView.animate(withDuration: 2, delay: 0, options: .curveEaseInOut, animations: {
    circularProgress.progress = 0.76
}, completion: nil)

有一篇很棒的objc文章here,详细介绍了这项工作的原理

以及使用相同概念的here

的objc项目

当从动画块动画对象时,将调用基本动作(对于图层:),我们可以使用相同的属性(从backgroundColor属性中窃取)开始我们自己的动画,并为更改设置动画。

答案 2 :(得分:3)

对于那些需要更多细节的人,就像我一样:

这个问题有一个很酷的example from Apple

E.g。多亏了它,我发现你实际上并不需要将自定义图层添加为子图层(如 @Tom van Zummeren 建议的那样)。相反,它足以为您的View类添加一个类方法:

+ (Class)layerClass
{
    return [CustomLayer class];
}

希望它对某人有帮助。

答案 3 :(得分:0)

您必须自己实施百分比部分。您可以覆盖图层绘图代码以绘制您的cgpath accroding到设置的百分比值。结帐core animation programming guideanimation types and timing guide

答案 4 :(得分:0)

@David Rees answer使我步入正轨,但是有一个问题。就我而言 动画开始后,动画完成总是返回false。

namespace Amazon.Lambda.APIGatewayEvents
{
    public class APIGatewayProxyRequest
    {
        public APIGatewayProxyRequest();

        public string Resource { get; set; }
        public string Path { get; set; }
        public string HttpMethod { get; set; }
        public IDictionary<string, string> Headers { get; set; }
        public IDictionary<string, IList<string>> MultiValueHeaders { get; set; }
        public IDictionary<string, string> QueryStringParameters { get; set; }
        public IDictionary<string, IList<string>> MultiValueQueryStringParameters { get; set; }
        public IDictionary<string, string> PathParameters { get; set; }
        public IDictionary<string, string> StageVariables { get; set; }
        public ProxyRequestContext RequestContext { get; set; }
        public string Body { get; set; }
        public bool IsBase64Encoded { get; set; }

        <...SNIP...>
    }
}

这是它为我工作的方式-动画动作是在CALayer内部处理的。

我还提供了一个小示例,说明如何使图层具有诸如“颜色”之类的其他属性。 在这种情况下,如果没有初始化程序复制这些值,则更改颜色将仅对非动画视图起作用。在动画过程中,使用“默认设置”将可见。

UIView.animate(withDuration: 2, delay: 0, options: .curveEaseInOut, animations: {
    circularProgress.progress = 0.76
}, completion: { finished in
   // finished - always false
})

以不同方式处理动画和复制图层属性的方法在本文中找到: https://medium.com/better-programming/make-apis-like-apple-animatable-view-properties-in-swift-4349b2244cea