कोर एनीमेशन के साथ कस्टम सहजता फ़ंक्शन कैसे बनाएं?


115

मैं iOS में काफी अच्छी तरह से (QuadCurve) के CALayerसाथ CGPathएनिमेशन कर रहा हूं । लेकिन मैं कुछ की तुलना में एक और अधिक दिलचस्प सहजता समारोह का उपयोग करना चाहते प्रदान की एप्पल (EaseIn / EaseOut आदि) द्वारा। उदाहरण के लिए, एक उछाल या लोचदार कार्य।

ये चीजें MediaTimingFunction (bezier) के साथ संभव हैं:

यहां छवि विवरण दर्ज करें

लेकिन मैं ऐसे टाइमिंग फ़ंक्शंस बनाना चाहूंगा जो अधिक जटिल हों। समस्या यह है कि मीडिया टाइमिंग के लिए एक क्यूबिक बेज़ियर की आवश्यकता होती है जो इन प्रभावों को बनाने के लिए पर्याप्त शक्तिशाली नहीं है:

यहां छवि विवरण दर्ज करें
(स्रोत: गौरैया- framework.org )

कोड अन्य चौखटे में सरल पर्याप्त से अधिक है, जो यह बहुत निराशा होती है बनाने के लिए। ध्यान दें कि घटता आउटपुट समय (Tt वक्र) के लिए इनपुट समय मैप कर रहा है और समय-स्थिति घटता नहीं। उदाहरण के लिए, easOutBounce (T) = t एक नया t देता है । फिर उस टी का उपयोग आंदोलन की साजिश करने के लिए किया जाता है (या जो भी संपत्ति हमें चेतन करनी चाहिए)।

इसलिए, मैं एक जटिल रिवाज बनाना चाहता हूं, CAMediaTimingFunctionलेकिन मुझे कोई सुराग नहीं है कि ऐसा कैसे करें, या यदि यह संभव है? क्या कोई विकल्प है?

संपादित करें:

यहाँ चरणों में एक ठोस उदाहरण दिया गया है। बहुत शैक्षिक :)

  1. मैं बिंदु से बी तक एक रेखा के साथ एक वस्तु को चेतन करना चाहता हूं , लेकिन मैं चाहता हूं कि यह आसानी से ऊपर की ओर वक्र के उपयोग से लाइन के साथ अपने आंदोलन को "उछाल" करे। इसका मतलब है कि यह से बी तक की सटीक लाइन का पालन करेगा , लेकिन वर्तमान बेज़ियर-आधारित कैमीडियाटिमिंग फंक्शन का उपयोग करके जितना संभव हो उतना अधिक जटिल तरीके से तेजी और पतन होगा।

  2. चलो उस लाइन को CGPath के साथ निर्दिष्ट किसी भी मनमानी वक्र गति को बनाते हैं। यह अभी भी उस वक्र के साथ आगे बढ़ना चाहिए, लेकिन इसे उसी तरह से तेज करना चाहिए और ठीक करना चाहिए जैसे लाइन उदाहरण में।

सिद्धांत रूप में मुझे लगता है कि इसे इस तरह काम करना चाहिए:

चलन वक्र को एक कीफ्रेम एनीमेशन चाल (t) = p के रूप में वर्णित करते हैं , जहाँ t समय [0..1] है, p की स्थिति समय t पर आंकी जाती है । तो चाल (0) वक्र की शुरुआत में स्थिति लौटाता है, सटीक मध्य स्थानांतरित (0.5) और अंत में (1) चलते हैं । समयावधि फ़ंक्शन समय (T) = t का उपयोग करके मूव के लिए t मान प्रदान करने के लिए मुझे वह देना चाहिए जो मुझे चाहिए। एक उछल प्रभाव के लिए, समय समारोह एक ही लौट जाना टी के लिए मूल्यों समय (0.8) और समय (0.8)(सिर्फ एक उदाहरण)। बस एक अलग प्रभाव प्राप्त करने के लिए समय समारोह को बदलें।

(हां, चार पंक्ति खंडों को बनाने और शामिल करने से लाइन-बाउंसिंग करना संभव है, जो आगे और पीछे जाता है, लेकिन यह आवश्यक नहीं होना चाहिए। आखिरकार, यह सिर्फ एक सरल रैखिक फ़ंक्शन है जो पदों के लिए समय मानों को मैप करता है ।)

मुझे आशा है कि मैं यहाँ समझ बना रहा हूँ।


ज्ञात रहे कि (जैसा कि अक्सर एसओ पर होता है), यह बहुत पुराना प्रश्न अब बहुत पुराना हो चुका है .. वर्तमान उत्तरों को नीचे स्क्रॉल करना सुनिश्चित करें। (अत्यधिक उल्लेखनीय: cubic-bezier.com !)
Fattie

जवाबों:


48

मुझे मिला:

कोको विद लव - कोर एनिमेशन में पैरामीट्रिक त्वरण घटता है

लेकिन मुझे लगता है कि ब्लॉकों का उपयोग करके इसे थोड़ा सरल और अधिक पठनीय बनाया जा सकता है। तो हम CAKeyframeAnimation पर एक श्रेणी को परिभाषित कर सकते हैं जो कुछ इस तरह दिखता है:

CAKeyframeAnimation + Parametric.h:

// this should be a function that takes a time value between 
//  0.0 and 1.0 (where 0.0 is the beginning of the animation
//  and 1.0 is the end) and returns a scale factor where 0.0
//  would produce the starting value and 1.0 would produce the
//  ending value
typedef double (^KeyframeParametricBlock)(double);

@interface CAKeyframeAnimation (Parametric)

+ (id)animationWithKeyPath:(NSString *)path 
      function:(KeyframeParametricBlock)block
      fromValue:(double)fromValue
      toValue:(double)toValue;

CAKeyframeAnimation + Parametric.m:

@implementation CAKeyframeAnimation (Parametric)

+ (id)animationWithKeyPath:(NSString *)path 
      function:(KeyframeParametricBlock)block
      fromValue:(double)fromValue
      toValue:(double)toValue {
  // get a keyframe animation to set up
  CAKeyframeAnimation *animation = 
    [CAKeyframeAnimation animationWithKeyPath:path];
  // break the time into steps
  //  (the more steps, the smoother the animation)
  NSUInteger steps = 100;
  NSMutableArray *values = [NSMutableArray arrayWithCapacity:steps];
  double time = 0.0;
  double timeStep = 1.0 / (double)(steps - 1);
  for(NSUInteger i = 0; i < steps; i++) {
    double value = fromValue + (block(time) * (toValue - fromValue));
    [values addObject:[NSNumber numberWithDouble:value]];
    time += timeStep;
  }
  // we want linear animation between keyframes, with equal time steps
  animation.calculationMode = kCAAnimationLinear;
  // set keyframes and we're done
  [animation setValues:values];
  return(animation);
}

@end

अब उपयोग कुछ इस तरह दिखेगा:

// define a parametric function
KeyframeParametricBlock function = ^double(double time) {
  return(1.0 - pow((1.0 - time), 2.0));
};

if (layer) {
  [CATransaction begin];
    [CATransaction 
      setValue:[NSNumber numberWithFloat:2.5]
      forKey:kCATransactionAnimationDuration];

    // make an animation
    CAAnimation *drop = [CAKeyframeAnimation 
      animationWithKeyPath:@"position.y"
      function:function fromValue:30.0 toValue:450.0];
    // use it
    [layer addAnimation:drop forKey:@"position"];

  [CATransaction commit];
}

मुझे पता है कि यह उतना सरल नहीं हो सकता जितना आप चाहते थे, लेकिन यह एक शुरुआत है।


1
मैंने जेसी क्रॉसन की प्रतिक्रिया ली, और उस पर थोड़ा विस्तार किया। आप इसका उपयोग CGPoints को चेतन करने के लिए कर सकते हैं, और उदाहरण के लिए CGSize कर सकते हैं। IOS 7 के रूप में, आप UIView एनिमेशन के साथ मनमाने समय के कार्यों का भी उपयोग कर सकते हैं। आप बाहर परिणामों पर जाँच कर सकते हैं github.com/jjackson26/JMJParametricAnimation
जे जे जैक्सन

@JJJackson एनिमेशन का शानदार संग्रह! उन्हें इकट्ठा करने के लिए धन्यवाद
कोडी

33

IOS 10 से दो नए टाइमिंग ऑब्जेक्ट का उपयोग करके कस्टम टाइमिंग फ़ंक्शन को आसान बनाना संभव हो गया।

1) UICubicTimingParameters क्यूबिक बेज़ियर वक्र को एक सहज कार्य के रूप में परिभाषित करने की अनुमति देता है ।

let cubicTimingParameters = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.25, y: 0.1), controlPoint2: CGPoint(x: 0.25, y: 1))
let animator = UIViewPropertyAnimator(duration: 0.3, timingParameters: cubicTimingParameters)

या बस एनिमेटर इनिशियलाइज़ेशन पर नियंत्रण बिंदुओं का उपयोग करना

let controlPoint1 = CGPoint(x: 0.25, y: 0.1)
let controlPoint2 = CGPoint(x: 0.25, y: 1)
let animator = UIViewPropertyAnimator(duration: 0.3, controlPoint1: controlPoint1, controlPoint2: controlPoint2) 

यह भयानक सेवा आपके घटता के लिए नियंत्रण बिंदु चुनने में मदद करने वाली है।

2) UISpringTimingParameters डेवलपर्स को वांछित वसंत व्यवहार बनाने के लिए भिगोना अनुपात , द्रव्यमान , कठोरता और प्रारंभिक वेग में हेरफेर करने देता है।

let velocity = CGVector(dx: 1, dy: 0)
let springParameters = UISpringTimingParameters(mass: 1.8, stiffness: 330, damping: 33, initialVelocity: velocity)
let springAnimator = UIViewPropertyAnimator(duration: 0.0, timingParameters: springParameters)

अवधि पैरामीटर अभी भी एनिमेटर में प्रस्तुत किया गया है, लेकिन वसंत समय के लिए इसे नजरअंदाज कर दिया जाएगा।

यदि ये दो विकल्प पर्याप्त नहीं हैं, तो आप UITimingCurveProvider प्रोटोकॉल की पुष्टि करके अपने स्वयं के टाइमिंग वक्र को भी लागू कर सकते हैं ।

अधिक विवरण, विभिन्न समय मापदंडों के साथ एनिमेशन कैसे बनाएं, आप प्रलेखन में पा सकते हैं ।

इसके अलावा, कृपया UDCit एनिमेशन और बदलाव की प्रस्तुति को WWDC 2016 से देखें।


आप iOS10- एनिमेशन-डेमो रिपॉजिटरी में टाइमिंग फ़ंक्शन का उपयोग करने के उदाहरण पा सकते हैं ।
ओल्गा कोनोरेवा

क्या आप इस बारे में विस्तार से बता सकते हैं कि "यदि ये दो विकल्प पर्याप्त नहीं हैं तो आप UITimingCurveProvider प्रोटोकॉल की पुष्टि करके अपने स्वयं के टाइमिंग वक्र को भी लागू कर सकते हैं।" ?
क्रिश्चियन स्चनोर

बहुत बढ़िया! और ( ) का CoreAnimationसंस्करण iOS 9 में वापस समर्थित है! UISpringTimingParametersCASpringAnimation
रिदमिक फ़िस्टमैन

@OlgaKonoreva, cubic-bezier.com सेवा UTRELY PRICELESS और WONDERFUL है । आशा है कि आप एक SO उपयोगकर्ता हैं - धन्यवाद कहने के लिए आपको एक मोटा-मोटा इनाम भेज रहे हैं :)
Fattie

@ChristianSchnorr मुझे लगता है कि क्या जवाब देना लाजिमी है, UITimingCurveProviderन केवल एक घन या एक वसंत एनीमेशन का प्रतिनिधित्व करने की क्षमता है , बल्कि एक एनीमेशन का प्रतिनिधित्व करने के लिए भी है जो घन और वसंत दोनों को जोड़ती है UITimingCurveType.composed
डेविड जेम्स

14

एक कस्टम टाइमिंग फंक्शन बनाने का एक तरीका फंक्शनविथकंट्रोलपॉइंट्स :::: फैक्ट्री मेथड है कैमीडियाटिमिंगफंक्शन (इसमें एक समान इनिविटीकंट्रोल पॉइंट्स :::: इनिट मेथड भी है)। यह क्या करता है अपने समय समारोह के लिए एक Bézier वक्र बनाता है। यह एक मनमाना वक्र नहीं है, लेकिन Bézier घटता बहुत शक्तिशाली और लचीला है। नियंत्रण बिंदुओं को लटका पाने के लिए थोड़ा अभ्यास करना पड़ता है। एक टिप: अधिकांश ड्राइंग प्रोग्राम Bézier घटता बना सकते हैं। उन लोगों के साथ खेलना आपको नियंत्रण बिंदु के साथ प्रतिनिधित्व कर रहे वक्र पर एक दृश्य प्रतिक्रिया देगा।

इस लिंक पर सेब के दस्तावेज़ इंगित करता है। घटता से पूर्व-निर्माण कार्यों का निर्माण कैसे किया जाता है, इस पर एक छोटा लेकिन उपयोगी खंड है।

संपादित करें: निम्नलिखित कोड एक साधारण उछाल एनीमेशन दिखाता है। ऐसा करने के लिए, मैंने एक समयावधि समारोह ( मान और समय NSArray गुण) बनाया और एनीमेशन के प्रत्येक खंड को एक अलग समय लंबाई ( कीटाइम्स प्रॉपर्टी) दिया। इस तरह से आप एनिमेशन के लिए अधिक परिष्कृत समय की रचना करने के लिए बेज़ियर वक्रों की रचना कर सकते हैं। यह एक अच्छा नमूना कोड के साथ इस प्रकार के एनिमेशन पर एक अच्छा लेख है।

- (void)viewDidLoad {
    UIView *v = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 50.0, 50.0)];

    v.backgroundColor = [UIColor redColor];
    CGFloat y = self.view.bounds.size.height;
    v.center = CGPointMake(self.view.bounds.size.width/2.0, 50.0/2.0);
    [self.view addSubview:v];

    //[CATransaction begin];

    CAKeyframeAnimation * animation; 
    animation = [CAKeyframeAnimation animationWithKeyPath:@"position.y"]; 
    animation.duration = 3.0; 
    animation.removedOnCompletion = NO;
    animation.fillMode = kCAFillModeForwards;

    NSMutableArray *values = [NSMutableArray array];
    NSMutableArray *timings = [NSMutableArray array];
    NSMutableArray *keytimes = [NSMutableArray array];

    //Start
    [values addObject:[NSNumber numberWithFloat:25.0]];
    [timings addObject:GetTiming(kCAMediaTimingFunctionEaseIn)];
    [keytimes addObject:[NSNumber numberWithFloat:0.0]];


    //Drop down
    [values addObject:[NSNumber numberWithFloat:y]];
    [timings addObject:GetTiming(kCAMediaTimingFunctionEaseOut)];
    [keytimes addObject:[NSNumber numberWithFloat:0.6]];


    // bounce up
    [values addObject:[NSNumber numberWithFloat:0.7 * y]];
    [timings addObject:GetTiming(kCAMediaTimingFunctionEaseIn)];
    [keytimes addObject:[NSNumber numberWithFloat:0.8]];


    // fihish down
    [values addObject:[NSNumber numberWithFloat:y]];
    [keytimes addObject:[NSNumber numberWithFloat:1.0]];
    //[timings addObject:GetTiming(kCAMediaTimingFunctionEaseIn)];



    animation.values = values;
    animation.timingFunctions = timings;
    animation.keyTimes = keytimes;

    [v.layer addAnimation:animation forKey:nil];   

    //[CATransaction commit];

}

हाँ वह सच है। आप जो चाहते हैं उसे प्राप्त करने के लिए CAKeyframeAnimation के मानों और समय-सीमा गुणों का उपयोग करके कई समय कार्यों को जोड़ सकते हैं। मैं एक उदाहरण पर काम करूंगा और कोड को एक बिट में डालूंगा।
10

आपके प्रयासों के लिए धन्यवाद, और कोशिश करने के लिए अंक :) यह दृष्टिकोण सिद्धांत रूप में काम कर सकता है, लेकिन इसे प्रत्येक उपयोग के लिए अनुकूलित करने की आवश्यकता है। मैं एक सामान्य समाधान की तलाश में हूं जो सभी एनिमेशन के लिए एक समय समारोह के रूप में काम करता है। सच कहूँ तो यह अच्छा piecing लाइनों और घटता एक साथ नहीं दिखता है। "जैक इन ए बॉक्स" उदाहरण भी इससे पीड़ित है।
मार्टिन विकमैन

1
आप एक keyframe एनीमेशन के लिए मूल्यों की एक सरणी के बजाय एक CGPathRef निर्दिष्ट कर सकते हैं, लेकिन अंत में Felz सही है - CAKeyframeAnimation और समय-निर्धारण आपको वह सब कुछ देगा जो आपको चाहिए। मुझे यकीन नहीं है कि आपको क्यों लगता है कि यह "सामान्य समाधान" नहीं है जिसे "प्रत्येक उपयोग के लिए अनुकूलित" होना है। आप फ़ैक्टरी विधि या किसी चीज़ में एक बार keyframe एनीमेशन का निर्माण करते हैं और आप उस एनीमेशन को किसी भी परत पर बार-बार जोड़ सकते हैं जो आपको पसंद है। प्रत्येक उपयोग के लिए इसे अनुकूलित करने की आवश्यकता क्यों होगी? मुझे लगता है कि यह आपके विचार से अलग नहीं है कि कार्य करने के लिए कितने समय में काम करना चाहिए।
मैट लॉन्ग

1
ओह दोस्तों, ये अब 4 साल देर हो चुकी है, लेकिन अगर functionWithControlPoints :::: पूरी तरह से बाउंसी / स्प्रिंगली एनिमेशन कर सकते हैं। घन-bezier.com/#.6,1.87,.63,.74 के
मैट फोली

10

यकीन नहीं होता कि आप अभी भी देख रहे हैं, लेकिन PRTween अपनी क्षमता के मामले में काफी प्रभावशाली लग रहा है, जो कि कोर एनिमेशन आपको सबसे खास तौर पर कस्टम टाइमिंग फंक्शन्स में प्रदान करता है। यह भी कई के साथ पैक किया जाता है - यदि सभी लोकप्रिय सहजता घटता नहीं है, जो विभिन्न वेब फ्रेमवर्क प्रदान करते हैं।


2

एक स्विफ्ट संस्करण कार्यान्वयन TFAnimation है । डेमो एक पाप वक्र एनीमेशन है। TFBasicAnimationकेवल एक ब्लॉक CABasicAnimationको छोड़कर timeFunctionअन्य के साथ असाइन करने के अलावा timingFunction

प्रमुख मुद्दा उपवर्ग है CAKeyframeAnimationऔर calculate फ्रेम स्थिति timeFunctionमें 1 / 60fpsरों अंतराल .After सब करने के लिए सभी गणना मूल्य जोड़ने valuesके CAKeyframeAnimationलिए अंतराल से और समय keyTimesभी।


2

मैंने एक ब्लॉक आधारित दृष्टिकोण बनाया, जो एक एनिमेशन समूह बनाता है, जिसमें कई एनिमेशन हैं।

प्रत्येक एनीमेशन, प्रति संपत्ति, 33 विभिन्न पैरामीट्रिक घटता में से 1 का उपयोग कर सकते हैं, प्रारंभिक वेग के साथ एक क्षय समय समारोह, या अपनी आवश्यकताओं के लिए एक कस्टम स्प्रिंग कॉन्फ़िगर किया गया है।

एक बार समूह उत्पन्न होने के बाद, यह दृश्य पर कैश्ड हो जाता है, और एनीमेशनके साथ, एनीमेशन के साथ या इसके बिना ट्रिगर किया जा सकता है। एक बार ट्रिगर होने के बाद एनीमेशन को प्रस्तुति परत के मूल्यों के अनुसार सिंक्रनाइज़ किया जाता है, और तदनुसार लागू किया जाता है।

यहां की रूपरेखा FlightAnimator से देखी जा सकती है

यहाँ एक उदाहरण नीचे दिया गया है:

struct AnimationKeys {
    static let StageOneAnimationKey  = "StageOneAnimationKey"
    static let StageTwoAnimationKey  = "StageTwoAnimationKey"
}

...

view.registerAnimation(forKey: AnimationKeys.StageOneAnimationKey, maker:  { (maker) in

    maker.animateBounds(toValue: newBounds,
                     duration: 0.5,
                     easingFunction: .EaseOutCubic)

    maker.animatePosition(toValue: newPosition,
                     duration: 0.5,
                     easingFunction: .EaseOutCubic)

    maker.triggerTimedAnimation(forKey: AnimationKeys.StageTwoAnimationKey,
                           onView: self.secondaryView,
                           atProgress: 0.5, 
                           maker: { (makerStageTwo) in

        makerStageTwo.animateBounds(withDuration: 0.5,
                             easingFunction: .EaseOutCubic,
                             toValue: newSecondaryBounds)

        makerStageTwo.animatePosition(withDuration: 0.5,
                             easingFunction: .EaseOutCubic,
                             toValue: newSecondaryCenter)
     })                    
})

एनीमेशन को ट्रिगर करने के लिए

view.applyAnimation(forKey: AnimationKeys.StageOneAnimationKey)
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.