2013-01-07 49 views
19

UIView पर आप पृष्ठभूमि को एनिमेटेड बदल सकते हैं। और UISlideView पर आप एनिमेटेड मान बदल सकते हैं।एक कस्टम एनिमेटेबल प्रॉपर्टी बनाएं

क्या आप अपनी खुद की UIView सबक्लास में कस्टम प्रॉपर्टी जोड़ सकते हैं ताकि इसे एनिमेटेड किया जा सके?

यदि मेरे पास CGPath है तो मेरे UIView के भीतर मैं पथ के खींचे गए प्रतिशत को बदलकर इसे चित्रित कर सकता हूं।

क्या मैं उस एनीमेशन को सबक्लास में समाहित कर सकता हूं।

यानी मेरे पास UIView है CGPath जो एक सर्कल बनाता है।

यदि सर्कल नहीं है तो यह 0% का प्रतिनिधित्व करता है। यदि सर्कल भर गया है तो यह 100% का प्रतिनिधित्व करता है। मैं पथ के प्रतिशत को बदलकर कोई मूल्य खींच सकता हूं। मैं CGPath के प्रतिशत को एनिमेट करके और पथ को दोबारा बदलकर परिवर्तन (UIView सबक्लास के भीतर) को एनिमेट कर सकता हूं।

क्या मैं UIView पर कुछ संपत्ति (यानी प्रतिशत) सेट कर सकता हूं ताकि मैं परिवर्तन को UIView animateWithDuration ब्लॉक में बदल सकूं और यह पथ के प्रतिशत में परिवर्तन को एनिमेट कर सकता है?

मुझे उम्मीद है कि मैंने समझाया है कि मैं क्या करना चाहता हूं।

अनिवार्य रूप से, सब मैं क्या करना चाहते हैं कुछ की तरह है ...

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

और चक्र पथ दिया प्रतिशत करने के लिए चेतन।

उत्तर

22

आप CALayer का विस्तार और इस तरह अपने कस्टम

- (void) drawInContext:(CGContextRef) context 

आप needsDisplayForKey अधिभावी (अपने कस्टम CALayer कक्षा में) द्वारा एक animatable संपत्ति कर सकते हैं लागू:

+ (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"]; 

ओह और myCircleView साथ yourCustomLayer उपयोग करने के लिए, ऐसा करते हैं:

[myCircleView.layer addSublayer:myCustomLayer]; 
+0

उत्तर के लिए धन्यवाद। मैं आज रात एक नज़र डालेगा। – Fogmeister

+0

@Fogmeister हाय, पार्टी के लिए थोड़ा देर हो चुकी है। पूछने की उम्मीद थी: हम '- (शून्य) drawInContext: (CGContextRef) संदर्भ' को कैसे लागू करेंगे? क्या यह एक नई अनुकूलित संपत्ति तैयार की जाएगी? अग्रिम में धन्यवाद। – Unheilig

+0

@ अपने नमूने में टॉम आप अवधि को मैन्युअल रूप से 1 पर सेट करते हैं ... आप UIViewAnimation ब्लॉक द्वारा प्रदान की गई अवधि/वक्र/देरी, ect ... को कैसे समझ पाएंगे? – Georg

0

आपको प्रतिशत हिस्से को स्वयं लागू करना होगा। आप अपने cgpath accroding को सेट प्रतिशत मान पर आकर्षित करने के लिए परत ड्राइंग कोड ओवरराइड कर सकते हैं। चेकआउट core animation programming guide और animation types and timing guide

+0

उत्तर के लिए धन्यवाद। मैं आज रात एक नज़र डालेगा। – Fogmeister

+0

डाउनवोट क्यों? वह भी इतने लंबे समय के बाद? – lukya

+0

कोई विचार नहीं। मेरे साथ करने को कुछ भी नहीं। – Fogmeister

3

जो पर जैसे मैंने किया था कि अधिक जानकारी के जरूरत के लिए:

है इस सवाल को कवर करने वाला एक अच्छा example from Apple

उदा। इसके लिए धन्यवाद, मैंने पाया कि आपको वास्तव में अपनी कस्टम परत को सब्लेयर के रूप में जोड़ने की आवश्यकता नहीं है (@ टोम वैन जुममेरेन सुझाता है)।इसके बजाय यह आपके व्यू क्लास में क्लास विधि जोड़ने के लिए पर्याप्त है:

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

आशा है कि यह किसी की सहायता करे।

7

पूरा स्विफ्ट 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 है, जो एक ObjC परियोजना के रूप में के रूप में अच्छी तरह से यह कैसे काम करता

के बारे में विवरण में चला जाता है नहीं है कि उसी अवधारणाओं का उपयोग करता है here:

अनिवार्य रूप से कार्रवाई (परत के लिए :) जब किसी ऑब्जेक्ट को एनीमेशन ब्लॉक से एनिमेटेड किया जा रहा है, तो हम उसी गुणों (पृष्ठभूमि रंग चोरी से चोरी) के साथ अपने स्वयं के एनिमेशन शुरू कर सकते हैं और परिवर्तनों को एनिमेट कर सकते हैं।