Créer une propriété animable personnalisée

Sur UIView vous pouvez modifier la couleur d'arrière-plan animée. Et sur un UISlideView Vous pouvez changer la valeur animée.

Pouvez-vous ajouter une propriété personnalisée à votre propre sous-classe UIView afin qu'elle puisse être animée?

Si j'ai un CGPath dans mon UIView Alors je peux en animer le dessin en changeant le pourcentage dessiné du chemin.

Puis-je encapsuler cette animation dans la sous-classe.

C'est-à-dire que j'ai un UIView avec un {[4] } qui crée un cercle.

Si le cercle n'est pas là, il représente 0%. Si le cercle est plein, il représente 100%. Je peux dessiner n'importe quelle valeur en changeant le pourcentage tiré du chemin. Je peux également animer le changement (dans la sous-classe UIView) en animant le pourcentage du CGPath et en redessinant le chemin.

Puis-je définir une propriété (c'est-à-dire un pourcentage) sur le UIView afin que je puisse coller le changement dans un bloc UIView animateWithDuration et qu'il anime le changement du pourcentage du chemin?

J'espère avoir expliqué ce que je ferais comme de bien faire.

Essentiellement, tout ce que je veux faire est quelque chose comme...

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

Et le chemin du cercle s'Anime au pourcentage donné.

21
demandé sur Fogmeister 2013-01-07 13:09:51

4 réponses

Si vous étendez CALayer et implémentez votre

- (void) drawInContext:(CGContextRef) context

Vous pouvez créer une propriété animable en remplaçant needsDisplayForKey (dans votre classe CALayer personnalisée) comme ceci:

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

Bien sûr, vous devez également avoir un @property appelé percentage. A partir de Maintenant, vous pouvez animer la propriété percentage en utilisant core animation. Je n'ai pas vérifié si cela fonctionne aussi en utilisant l'appel [UIView animateWithDuration...]. Cela pourrait fonctionner. Mais cela a fonctionné pour moi:

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

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

Oh, et pour utiliser yourCustomLayer avec myCircleView, ne ce:

[myCircleView.layer addSublayer:myCustomLayer];
23
répondu Tom van Zummeren 2013-01-07 10:07:10

Exemple Swift 3 complet:

Produit Final

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)

Il y a un grand article objc ici , qui va dans les détails sur la façon dont cela fonctionne

Ainsi Qu'un objc projet qui utilise les mêmes concepts ici:

Essentiellement action (pour layer:) sera appelée lorsqu'un objet est animé à partir d'un bloc d'animation, nous pouvons démarrer nos propres animations avec les mêmes propriétés (volées de la propriété backgroundColor) et animer le changement.

9
répondu David Rees 2017-07-07 02:18:38

Pour ceux qui ont besoin de plus de détails à ce sujet comme je l'ai fait:

Il y a un exemple cool d'Apple couvrant cette question.

Par exemple, grâce à cela, j'ai trouvé que vous n'avez pas réellement besoin d'ajouter votre couche personnalisée en tant que sous-couche (comme le suggère @Tom van zummeren). Au lieu de cela, il suffit d'ajouter une méthode de classe à votre classe de vue:

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

J'espère que ça aide quelqu'un.

3
répondu makaron 2015-09-27 20:08:01

Vous devrez implémenter la partie pourcentage vous-même. vous pouvez remplacer le code de dessin de calque pour dessiner votre cgpath accroding à la valeur de pourcentage définie. la caisse de la core animation guide de programmation et types d'animation et le calendrier guide de

0
répondu Swapnil Luktuke 2013-01-07 09:50:56