On the implementation of sticky animation and jelly effect in iOS

Source: Internet
Author: User
Tags image flip

In a recent custom Pagecontrol--kyanimatedpagecontrol, I implemented the Calayer animation and the elastic animation of Calayer, which was first reviewed:

https://github.com/KittenYang/KYAnimatedPageControl

Let's make an outline:

第一个分享的主题是“如何让CALayer发生形变”,这个技术在我之前一个项目 ———— KYCuteView 中有涉及,也写了篇简短的实现原理博文。今天再举一个例子。之前我也做过类似果冻效果的弹性动画,比如这个项目—— KYGooeyMenu。用到的核心技术是CAKeyframeAnimation,然后设置几个不同状态的关键帧,就能初步达到这种弹性效果。但是,毕竟只有几个关键帧,而且是需要手动计算,不精确不说,动画也不够细腻,毕竟你不可能手动创建60个关键帧。所以,今天的第二个主题是 —— “如何用阻尼振动函数创建出60个关键帧”,从而实现CALayer产生类似[UIView animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion] 的弹性动画。

Body.

How to make Calayer deformation?

The key technique is simple: you need to "spell" the layer with multiple Bezier curves. The reason why we do this is self-evident, because it is convenient for us to deform.

such as Kyanimatedpagecontrol in the small ball, in fact, it is so drawn out:

The small ball is composed of Arc AB, ARC BC, ARC CD, Arc da Four, each arc is bound to two control points: Arc ab binding is C1, C2; arc BC Bound is C3, C4 ...

    • How to express each point?

First of all, A, B, C and D are four dynamic points, and the variable that controls them is the contentoffset.x of ScrollView. We can get this variable in real time in-(void) Scrollviewdidscroll: (Uiscrollview *) ScrollView, and convert it to a factor that controls the 0~1, named Factor.

_factor = MIN (1, MAX (0, (ABS (scrollview.contentoffset.x-self.lastcontentoffset)/scrollView.frame.size.width));

Suppose that the maximum variation of a, B, C, D is 2/5 of the diameter of the ball. So combining the coefficients of this 0~1, we can conclude that a, B, C, D's real change distance extra is: Extra = (self.width 2/5) factor. When factor = = 1 o'clock, the maximum deformation state is reached, at which point the change distance of four points is (Self.width * 2/5).

Note: Depending on the direction of the slide, we also need to move from point B or D.

Cgpoint Pointa = Cgpointmake (rectcenter.x, self.currentrect.origin.y += cgpointmake (self.scrolldirection = = Scrolldirectionleft? Rectcenter.x + self.currentrect.size.width/2 : Rectcenter.x + self.currentrect.size.width/2 + extra* 2  = Cgpointmake (rectcenter.x, Rectcenter.y + self.currentrect.size.height/2 -= Cgpointmake ( Self.scrolldirection = = Scrolldirectionleft? self.currentrect.origin.x-extra*2 : self.currentrect.origin.x, RECTCENTER.Y);

Then the control point:

The key is to know the A-C1, B-C2, B-C3, c-c4 .... The length of these horizontal and vertical dashed lines, named offset. After many attempts, I came to the conclusion that:

When offset is set to the diameter divided by 3.6, the arc is perfectly attached to the synthetic arc. I vaguely feel that this 3.6 is inevitable, seemingly and 360 degrees have some kind of relationship, perhaps through the calculation can draw 3.6 This value of inevitability, but I did not try.

Therefore, the coordinates of each control point are:

Cgpoint C1 = cgpointmake (pointa.x + offset, pointa.y);   = Cgpointmake (pointb.x, pointb.y-= Cgpointmake (pointb.x, pointb.y + offset);   = Cgpointmake (pointc.x += Cgpointmake (pointc.x- offset, pointc.y);   = Cgpointmake (pointd.x, pointd.y += Cgpointmake (pointd.x, Pointd.y- offset);   = Cgpointmake (Pointa.x-offset, POINTA.Y);

With the end point and control point, you can use the method provided in Uibezierpath-(void) Addcurvetopoint: (cgpoint) endPoint controlPoint1: (cgpoint) controlPoint1 ControlPoint2: (cgpoint) ControlPoint2; Draw a line.

Heavy-duty Calayer-(void) Drawincontext: (Cgcontextref) ctx; method, in which a pattern is drawn:

-(void) Drawincontext: (cgcontextref) ctx{...    . // calculate the coordinates of each point here     uibezierpath* ovalpath = [Uibezierpath Bezierpath];    [Ovalpath Movetopoint:pointa];    [Ovalpath addcurvetopoint:pointb controlpoint1:c1 controlpoint2:c2];    [Ovalpath ADDCURVETOPOINT:POINTC controlpoint1:c3 controlpoint2:c4];    [Ovalpath addcurvetopoint:pointd controlpoint1:c5 controlpoint2:c6];    [Ovalpath Addcurvetopoint:pointa controlpoint1:c7 Controlpoint2:c8];    [Ovalpath Closepath];    Cgcontextaddpath (CTX, ovalpath.cgpath);    Cgcontextsetfillcolorwithcolor (CTX, self.indicatorColor.CGColor);    Cgcontextfillpath (CTX);}

Now, when you slide the ScrollView, the ball will deform.
How do I create 60 keyframes with the damped vibration function?

In the above example, there is a very important factor, that is, ScrollView in the Contentoffset.x this variable, without this input, then nothing will happen. But to get this variable, you need the user to touch, swipe to create the interaction. In an animation, the user is not directly interactive input, such as when the finger left, to let the ball to the jelly effect bounce back to the original state, the process finger has left the screen, there is no input, then the above method will certainly not work, so we can use caanimation.

We know that iOS7 Apple in UIView (uiviewanimationwithblocks) has added a new factory method for making elastic animations:

+ (void) Animatewithduration: (nstimeinterval) Duration delay: (nstimeinterval) Delay usingspringwithdamping :(cgfloat) Dampingratio initialspringvelocity: (cgfloat) Velocity options: (uiviewanimationoptions) Options animations :(void (^) (void)) Animations completion: (void (^) (BOOL finished)) Completion Ns_available_ IOS (7_0);

But there is no direct caanimation subclass of elasticity, like cabasicanimation or cakeyframeanimation, to animate Calayer directly. The good news is that the iOS9 has been added to the public caspringanimation. But for a low-compatibility version and a knowledge-seeking perspective, we can look at how to manually create an elastic animation for Calayer.

Before you begin, you need to review the physics of high school ———— damped Vibration, you can click the Highlight font link to review a little.

According to Wikipedia, we can get the following general formula for vibrating functions:

Of course this is just a general formula, we need to get the image over (0,0), and finally decay to 1. We can make the original image flip 180 degrees around the x-axis, that is, add a minus sign. Then pan up one unit along the y-axis. So a little deformation can get the following function:

Want to see the image of the function? No problem, recommend an online view of the function image of the site--desmos, the formula 1-\left (E^{-5x}\cdot \cos (30x) \right) to copy the paste into the image can be seen.

The improved function image is this:

Perfect to meet our graphics over (0,0), vibration attenuation to 1 of the requirements. Where 5 of the equation is equivalent to the damping coefficient, the smaller the value, the greater the magnitude of the equation, 30 is equivalent to the oscillation frequency, the greater the number of shocks the more.

The next step is to convert to code.

The general idea is to create a 60 frame keyframe (because the screen has a maximum refresh rate of 60FPS) and then assign the 60 frame data to the Cakeyframeanimation values property.

After generating 60 frames with the following code, save to an array and return it, where//1 is creating 60 values using the formula just now:

+ (Nsmutablearray *) Animationvalues: (ID) Fromvalue Tovalue: (ID) Tovalue usingspringwithdamping: (cgfloat) damping initialspringvelocity: (cgfloat) Velocity Duration: (cgfloat) duration{//60 key FramesNsinteger numofpoints = Duration * -; Nsmutablearray*values =[Nsmutablearray arraywithcapacity:numofpoints];  for(Nsinteger i =0; i < numofpoints; i++) {[Values addobject:@ (0.0)]; }    //Difference ValueCGFloat d_value = [Tovalue Floatvalue]-[Fromvalue Floatvalue];  for(Nsinteger point =0; point<numofpoints; point++) {cgfloat x= (cgfloat) point/(cgfloat) numofpoints; CGFloat value= [Tovalue Floatvalue]-D_value * (POW (m_e,-damping * x) * cos (velocity * x));//1 y = 1-e^{-5x} * cos (30x)Values[point]=@ (value); }    returnvalues;}

Next, create an external class method and return a cakeyframeanimation:

+ (Cakeyframeanimation *) createspring: (NSString *) keypath Duration: (cftimeinterval) Duration usingspringwithdamping :(cgfloat) Damping initialspringvelocity: (cgfloat) Velocity fromvalue: (ID) fromvalue tovalue: (ID ) tovalue{    *anim = [cakeyframeanimation animationwithkeypath:keypath];     *values = [Kyspringlayeranimation animationvalues:fromvalue tovalue:tovalue usingspringwithdamping:damping * Dampingfactor initialspringvelocity:velocity * velocityfactor duration:duration];     = values;     = duration;     return Anim;}

Another key

Above, we created the cakeyframeanimation. But who do these values really work for? If you're familiar with coreanimation, yes, it works for incoming keypath. And these keypath are actually the attribute @property in Calayer. Like what The reason that when the incoming KeyPath is transform.rotation.x cakeyframeanimation will cause the layer to rotate, because cakeyframeanimation found in Calayer there is a property called transform, so the animation It happened. Now what we need to change is the factor variable in theme one, so it's natural to think that we can give calayer a property named Factor on the line, This cakeyframeanimation added to the layer when the layer has this factor attribute, will be 60 frames different values assigned to factor. Of course we have to control Fromvalue and Tovalue in 0~1:

Cakeyframeanimation *anim = [kyspringlayeranimation createspring:@ "factor" Duration:  0.8 usingspringwithdamping:0.5 initialspringvelocity:3 fromvalue:@ (1) tovalue:@ ( 0  0; [ Self Addanimation:anim forkey:@ "restoreanimation"];

The last step, although cakeyframeanimation in real time to change the factor we want, but we also have to notify the screen refresh, so as to see the animation.

+ (BOOL) Needsdisplayforkey: (NSString *) key{    if ([key isequal:@ "factor"  ]) {        return  YES;    }     return   [Super Needsdisplayforkey:key];}

The above code notifies the screen to refresh the screen in real time when the factor changes.

Finally, you need to reload Calayer-(ID) Initwithlayer: (Gooeycircle *) layer method, in order to ensure that the animation can be coherent, you need to copy the previous state of the layer and all its properties.

-(ID) Initwithlayer: (gooeycircle *) layer{    = [Super Initwithlayer:layer];     if (self) {        self.indicatorsize  = layer.indicatorsize;         = Layer.indicatorcolor;         = Layer.currentrect;         = Layer.lastcontentoffset;         = layer.scrolldirection;         = layer.factor;    }     return Self ;}

Summarize:

The key to making a custom animation is to have a variable, to have the input. Like sliding ScrollView, the distance of the slide is the input of the animation, it can be used as the animation variable, when there is no interaction, you can use the caanimation. In fact, there is a timer at the bottom of the caanimation, and the function of the timer is to generate variables, time is a variable, you can produce changes in the input, you can see the state of change, even up is animation.

On the implementation of sticky animation and jelly effect in iOS

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.