2013-01-07 13 views
19

Na UIView możesz zmienić animowany kolor tła. I na UISlideView możesz zmienić wartość animowaną.Utwórz niestandardową animowalną właściwość

Czy można dodać niestandardową właściwość do własnej podklasy UIView, aby mogła być animowana?

Jeśli mam w moim UIView, mogę animować rysunek poprzez zmianę procentu narysowanego ścieżki.

Czy mogę zamknąć tę animację w podklasie.

tj. Mam z CGPath, który tworzy okrąg.

Jeśli koła nie ma, oznacza to 0%. Jeśli koło jest pełne, reprezentuje 100%. Mogę narysować dowolną wartość, zmieniając procent narysowany ścieżki. Mogę również animować zmianę (w obrębie podklasy UIView), animując procent wartości CGPath i ponownie rysując ścieżkę.

Czy mogę ustawić pewną właściwość (tj. Wartość procentową) na UIView, aby można było umieścić zmianę w bloku UIView animateWithDuration i animować zmianę procentowej ścieżki?

Mam nadzieję, że wyjaśniłem, co chciałbym robić dobrze.

Zasadniczo, wszystko co chcę zrobić, to coś jak ...

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

i ścieżka koło animować do podanej wartości procentowej.

Odpowiedz

22

Jeśli przedłużyć CALayer i wdrożyć niestandardowe

- (void) drawInContext:(CGContextRef) context 

Można dokonać właściwość animatable nadrzędnymi needsDisplayForKey (w niestandardowej klasy CALayer) tak:

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

Oczywiście, musisz również mieć @property o nazwie percentage. Od teraz możesz animować wartość procentową za pomocą animacji rdzenia. Nie sprawdziłem, czy to działa, korzystając z połączenia [UIView animateWithDuration...]. To może zadziałać. Ale ten pracował dla mnie:

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

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

No i używać yourCustomLayer z myCircleView, to zrobić:

[myCircleView.layer addSublayer:myCustomLayer]; 
+0

Dzięki za odpowiedź. Przyjrzę się dziś wieczorem. – Fogmeister

+0

@Fogmeister Cześć, trochę się spóźniłem na imprezę. Miał nadzieję zapytać: w jaki sposób wdrożyć '- (void) kontekst drawInContext: (CGContextRef)? Czy jest to miejsce, w którym zostanie narysowana nowa własność? Z góry dziękuję. – Unheilig

+0

@Tom w twojej próbce ręcznie ustawiłeś czas trwania na 1 ... jak mógłbyś określić czas trwania/krzywą/opóźnienie, ect ... dostarczony przez blok UIViewAnimation? – Georg

0

będziesz musiał zaimplementować część procentową samodzielnie. możesz przesłonić kod rysowania warstwy, aby narysować ścieżkę cgpath zgodnie z ustawioną wartością procentową. kasie core animation programming guide i animation types and timing guide

+0

Dzięki za odpowiedź. Przyjrzę się dziś wieczorem. – Fogmeister

+0

dlaczego downhtote? to też po tak długim czasie? – lukya

+0

Brak pomysłu. Nie ma nic wspólnego ze mną. – Fogmeister

3

dla tych, którzy potrzebuje więcej szczegółów na ten temat jak ja:

istnieje fajne example from Apple obejmujące to pytanie.

E.g. dzięki niemu stwierdziłem, że nie musisz dodawać warstwy niestandardowej jako podwarstwy (tak jak sugeruje to @ Tom van Zummeren).Zamiast tego wystarczy dodać klasę do swojej klasy View:

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

Mam nadzieję, że to pomoże komuś.

7

Kompletna Swift 3 Przykład:

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) 

Istnieje wielka artykuł objc here, który przechodzi do szczegółów o tym, jak to działa

Jak również objc projektu, który używa tych samych pojęć: here:

Zasadniczo akcja (dla warstwy :) zostanie wywołana, gdy obiekt jest animowany z bloku animacji, możemy uruchomić nasze własne animacje z tymi samymi właściwościami (skradzione z właściwości backgroundColor) i animować zmiany.

Powiązane problemy