यहाँ तुम क्या झिलमिलाहट को खत्म करने के कुछ जानना चाहते हैं:
रूप कालेब तरह की ने कहा, कोर एनिमेशन अपने परत घूमता है, ताकि उसके सकारात्मक एक्स अक्ष अपने पथ के स्पर्श के साथ निहित है। आपको इसके साथ अपनी छवि का "प्राकृतिक" अभिविन्यास कार्य करने की आवश्यकता है। तो, मान है कि आपके उदाहरण छवियों में एक हरे रंग अंतरिक्ष यान है, तो आप सही जब यह करने के लिए लागू रोटेशन नहीं है को इंगित करने के अंतरिक्ष यान की जरूरत है।
एक को बदलने कि रोटेशन शामिल स्थापना रोटेशन `kCAAnimationRotateAuto 'द्वारा लागू हस्तक्षेप करता है। एनीमेशन लागू करने से पहले आपको अपने ट्रांसफॉर्म से रोटेशन को हटाना होगा।
- बेशक आप परिवर्तन पुन: लागू करने के लिए जब एनीमेशन पूरा करता है की जरूरत है इसका मतलब है कि के
। और निश्चित रूप से आप छवि की उपस्थिति में किसी भी झिलमिलाहट को देखे बिना ऐसा करना चाहते हैं। यह मुश्किल नहीं है, लेकिन वहां कुछ गुप्त सॉस शामिल हैं, जिन्हें मैं नीचे समझाता हूं।
संभवतः आप अपनी स्पेसशिप पथ के स्पर्शरेखा के साथ इंगित करना शुरू करना चाहते हैं, भले ही स्पेसशिप अभी भी एनिमेटेड न हो। अपने अंतरिक्ष यान छवि सही करने के लिए इशारा कर रही है, लेकिन अपने पथ ऊपर जाता है, तो आप छवि के बदलने एक 90 डिग्री रोटेशन शामिल करने के लिए सेट करना होगा। लेकिन शायद आप उस घूर्णन को हार्डकोड नहीं करना चाहते हैं - इसके बजाय आप पथ को देखना चाहते हैं और इसके शुरुआती स्पर्शरेखा को समझना चाहते हैं।
मैं यहां कुछ महत्वपूर्ण कोड दिखाऊंगा। आप my test project on github पा सकते हैं। आपको इसे डाउनलोड करने और इसे आजमाने में कुछ उपयोग मिल सकता है। एनीमेशन देखने के लिए बस हरे "स्पेसशिप" पर टैप करें।
तो, मेरे परीक्षण प्रोजेक्ट में, मैंने को animate:
नामक एक क्रिया से जोड़ा है। जब आप इसे स्पर्श करते हैं, तो छवि आकृति 8 के आधे भाग और आकार में युगल के साथ चलता है। जब आप इसे फिर से स्पर्श करते हैं, तो छवि आकृति 8 के दूसरे भाग (प्रारंभिक स्थिति पर वापस) के साथ चलता है, और इसके मूल आकार पर वापस आ जाता है। दोनों एनिमेशन kCAAnimationRotateAuto
उपयोग करते हैं, तो पथ के स्पर्श के साथ छवि अंक।
यहाँ animate:
, जहां मैं यह पता लगाने की क्या पथ, पैमाने, और गंतव्य बिंदु छवि पर समाप्त होना चाहिए की शुरुआत है:
- (IBAction)animate:(id)sender {
UIImageView* theImage = self.imageView;
UIBezierPath *path = _isReset ? _path0 : _path1;
CGFloat newScale = 3 - _currentScale;
CGPoint destination = [path currentPoint];
तो, पहली बात मैं क्या करने की जरूरत से किसी भी रोटेशन को दूर है छवि के, को बदलने के बाद से के रूप में मैं उल्लेख किया है, यह kCAAnimationRotateAuto
के साथ हस्तक्षेप करेगा:
// Strip off the image's rotation, because it interferes with `kCAAnimationRotateAuto`.
theImage.transform = CGAffineTransformMakeScale(_currentScale, _currentScale);
इसके बाद, मैं एक UIView
एनीमेशन ब्लॉक में जाने ताकि सिस्टम छवि को देखने के लिए एनिमेशन लागू होंगे:
[UIView animateWithDuration:3 animations:^{
मैं पद के लिए मुख्य-फ़्रेम एनीमेशन बना सकते हैं और इसके गुणों की एक जोड़ी सेट:
// Prepare my own keypath animation for the layer position.
// The layer position is the same as the view center.
CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
positionAnimation.path = path.CGPath;
positionAnimation.rotationMode = kCAAnimationRotateAuto;
अगला एनीमेशन के अंत में झिलमिलाहट रोकने के लिए गुप्त सॉस है। याद है कि एनिमेशन नहीं प्रभाव "मॉडल परत" है कि आप (इस मामले में theImage.layer
) करने के लिए उन्हें संलग्न के गुणों से करते हैं। इसके बजाय, वे "प्रेजेंटेशन लेयर" के गुणों को अपडेट करते हैं, जो स्क्रीन पर वास्तव में क्या दर्शाता है।
तो पहले मैं keyframe एनीमेशन के लिए removedOnCompletion
NO
पर सेट करता हूं। इसका मतलब है कि एनीमेशन पूर्ण होने पर एनीमेशन मॉडल परत से जुड़ा रहेगा, जिसका अर्थ है कि मैं प्रस्तुति परत तक पहुंच सकता हूं। मुझे प्रेजेंटेशन लेयर से ट्रांसफॉर्म मिलता है, एनीमेशन को हटा दें, और मॉडल परत में ट्रांसफॉर्म लागू करें। चूंकि यह सब मुख्य धागे पर हो रहा है, इसलिए ये संपत्ति एक स्क्रीन रीफ्रेश चक्र में होती है, इसलिए कोई झिलमिलाहट नहीं होती है।
positionAnimation.removedOnCompletion = NO;
[CATransaction setCompletionBlock:^{
CGAffineTransform finalTransform = [theImage.layer.presentationLayer affineTransform];
[theImage.layer removeAnimationForKey:positionAnimation.keyPath];
theImage.transform = finalTransform;
}];
अब जब मैंने पूरा करने वाला ब्लॉक स्थापित किया है, तो मैं वास्तव में दृश्य गुणों को बदल सकता हूं। जब मैं ऐसा करता हूं तो सिस्टम स्वचालित रूप से परत पर एनिमेशन संलग्न करेगा।
// UIView will add animations for both of these changes.
theImage.transform = CGAffineTransformMakeScale(newScale, newScale);
theImage.center = destination;
मैं अपने-फ़्रेम एनीमेशन के लिए अपने आप जोड़े स्थिति एनीमेशन से कुछ प्रमुख गुण कॉपी:
// Copy properties from UIView's animation.
CAAnimation *autoAnimation = [theImage.layer animationForKey:positionAnimation.keyPath];
positionAnimation.duration = autoAnimation.duration;
positionAnimation.fillMode = autoAnimation.fillMode;
और अंत में मैं-फ़्रेम एनीमेशन के साथ अपने आप जोड़े स्थिति एनीमेशन बदल देते हैं:
// Replace UIView's animation with my animation.
[theImage.layer addAnimation:positionAnimation forKey:positionAnimation.keyPath];
}];
डबल-अंत में मैं छवि दृश्य में परिवर्तन को दर्शाने के लिए अपने आवृत्ति चर अद्यतन करता हूं:
_currentScale = newScale;
_isReset = !_isReset;
}
यह छवि दृश्य को एनिमेट करने के लिए कोई झिलमिलाहट नहीं है।
और अब स्टीव जॉब्स कहेंगे, एक अंतिम बात। जब मैं दृश्य लोड करता हूं, तो मुझे छवि दृश्य के परिवर्तन को सेट करने की आवश्यकता होती है ताकि यह पहले पथ के स्पर्शरेखा के साथ इंगित करने के लिए घुमाया जा सके जिसे मैं इसे एनिमेट करने के लिए उपयोग करूंगा। मुझे लगता है कि एक विधि में reset
नामित कार्य करें:
- (void)reset {
self.imageView.center = _path1.currentPoint;
self.imageView.transform = CGAffineTransformMakeRotation(startRadiansForPath(_path0));
_currentScale = 1;
_isReset = YES;
}
बेशक
, मुश्किल सा है कि startRadiansForPath
समारोह में छिपा हुआ है। यह वास्तव में मुश्किल नहीं है। मैं पथ के तत्वों को संसाधित करने के लिए CGPathApply
फ़ंक्शन का उपयोग करता हूं, जो पहले दो बिंदुओं को वास्तव में उपपथ बनाता है, और मैं उन दो बिंदुओं द्वारा बनाई गई रेखा के कोण की गणना करता हूं। (एक घुमावदार पथ खंड या तो एक वर्गिक या घन बेजियर स्पलीन है, और उन स्प्लिंस में संपत्ति है कि स्पलीन के पहले बिंदु पर टेंगेंट पहले बिंदु से अगले बिंदु बिंदु तक लाइन है।)
I ' मीटर सिर्फ कोड भावी पीढ़ी के लिए, विवरण के बिना यहां डंप करने के लिए जा:
typedef struct {
CGPoint p0;
CGPoint p1;
CGPoint firstPointOfCurrentSubpath;
CGPoint currentPoint;
BOOL p0p1AreSet : 1;
} PathState;
static inline void updateStateWithMoveElement(PathState *state, CGPathElement const *element) {
state->currentPoint = element->points[0];
state->firstPointOfCurrentSubpath = state->currentPoint;
}
static inline void updateStateWithPoints(PathState *state, CGPoint p1, CGPoint currentPoint) {
if (!state->p0p1AreSet) {
state->p0 = state->currentPoint;
state->p1 = p1;
state->p0p1AreSet = YES;
}
state->currentPoint = currentPoint;
}
static inline void updateStateWithPointsElement(PathState *state, CGPathElement const *element, int newCurrentPointIndex) {
updateStateWithPoints(state, element->points[0], element->points[newCurrentPointIndex]);
}
static void updateStateWithCloseElement(PathState *state, CGPathElement const *element) {
updateStateWithPoints(state, state->firstPointOfCurrentSubpath, state->firstPointOfCurrentSubpath);
}
static void updateState(void *info, CGPathElement const *element) {
PathState *state = info;
switch (element->type) {
case kCGPathElementMoveToPoint: return updateStateWithMoveElement(state, element);
case kCGPathElementAddLineToPoint: return updateStateWithPointsElement(state, element, 0);
case kCGPathElementAddQuadCurveToPoint: return updateStateWithPointsElement(state, element, 1);
case kCGPathElementAddCurveToPoint: return updateStateWithPointsElement(state, element, 2);
case kCGPathElementCloseSubpath: return updateStateWithCloseElement(state, element);
}
}
CGFloat startRadiansForPath(UIBezierPath *path) {
PathState state;
memset(&state, 0, sizeof state);
CGPathApply(path.CGPath, &state, updateState);
return atan2f(state.p1.y - state.p0.y, state.p1.x - state.p0.x);
}
यह मैं बाहर बहुत मदद की! धन्यवाद! – RyanG