If mobile access is poor, you can visit my personal blog
A few days ago read a blog about animation called Hand to teach you to write Slack of the Loading animation, looking very flashy, but it is the Android version, thinking of The imitation of writing an iOS version, the following is I write this animation decomposition ~
The usual first and demo address:
When I first saw this animation, there were two schemes in my mind, one of which was to draw drawRect
it, and then to keep drawing the line, and the CADisplayLink
second CAShapeLayer
was CAAnimation
to achieve the animation effect by matching. Think twice about using the latter, because the former need to calculate a lot, more complex, and after testing the former compared to the latter consumes more CPU, the following will be my idea write down:
Related configuration and initialization methods
Before we write this animation, we write the attributes we need first, such as the thickness of the lines, the time of the animation, and so on, and the following are the relevant configuration and initial methods:
//width of line varLinewidth:cgfloat =0 //Length of line varLinelength:cgfloat =0 //Margin varMargin:cgfloat =0 //Animation time varDuration:double =2 //time interval of animation varInterval:double =1 //Four lines of color varColors:[uicolor] = [Uicolor.init (RGBA:"#9DD4E9"), Uicolor.init (RGBA:"#F5BD58"), Uicolor.init (RGBA:"#FF317E"), Uicolor.init (RGBA:"#6FC9B5")]//State of the animation Private(Set)varStatus:animationstatus =. Normal//Four lines Private varLines:[cashapelayer] = []enumAnimationstatus {//Normal status CaseNormal//In animation CaseAnimating//Pause CasePause}//mark:initial MethodsConvenience init (fram:cgrect, colors: [Uicolor]) {self.init () Self.frame = frame Self.colors = Col ORS Config ()}OverrideInit (frame:cgrect) {super.init (frame:frame) config ()} required init? (Coder Adecoder:nscoder) {super.init (Coder:adecoder) config ()}PrivateFuncConfig() {linelength = max (Frame.width, frame.height) linewidth = linelength/6.0margin = linelength/4.5+ linewidth/2Drawlineshapelayer () transform = cgaffinetransformrotate (cgaffinetransformidentity, Angle (- -)) }
Draw lines with Cashapelayer
When I saw this line, I thought about it, CAShapeLayer
because CAShapeLayer
it was completely achievable, and its strokeEnd
properties could be used to animate the length of the line, and to draw four lines of code below:
//mark: Drawing lines /** Draw four lines * / PrivateFuncDrawlineshapelayer() {//Start pointLet StartPoint = [Point (linewidth/2, Y:margin), point (Linelength-margin, y:linewidth/2), point (linelength-linewidth/2, Y:linelength-margin), point (margin, y:linelength-linewidth/2)]//End pointLet EndPoint = [Point (linelength-linewidth/2, Y:margin), point (Linelength-margin, y:linelength-linewidth/2), point (linewidth/2, Y:linelength-margin), point (margin, y:linewidth/2)] forI in0... 3{Let Line:cashapelayer = Cashapelayer () line.linewidth = linewidth Line.linecap = Kcalinecapround line.opacity =0.8Line.strokecolor = Colors[i]. Cgcolor Line.path = Getlinepath (Startpoint[i], endpoint:endpoint[i]). Cgpath Layer.addsublayer (line) lines.append (line)}}/** Get line path-parameter startPoint: Start point-parameter endPoint: End point-Returns: Line path * / PrivateFuncGetlinepath(Startpoint:cgpoint, Endpoint:cgpoint), Uibezierpath {Let path = Uibezierpath () path.movetopoint (STA Rtpoint) Path.addlinetopoint (endPoint)returnPath}PrivateFunc Point(X:cgfloat, y:cgfloat), Cgpoint {returnCgpointmake (x, y)}PrivateFuncAngle(angle:double), CGFloat {returnCGFloat (Angle * (m_pi/ the)) }
After the implementation of the same effect is the same ~ ~ ~
Animation decomposition
After analysis, the animation can be divided into four steps:
- Rotation animation of the canvas, rotating two circles
- The line is executed by a shorter animation, a canvas-selected animation, and a circle of rotation ends
- The displacement animation of the line, the line gradually toward the middle, and then the brush after rotating a circle of time to execute, two laps when the end
- The line is animated by a short, long animation, when the canvas is rotated two times to execute
First step canvas rotate animation
Here we use the CABasicAnimation
base animation, which keyPath
acts on the canvas transform.rotation.z
, rotates with the z-axis as the target, and the following is the code:
//MARK: 动画步骤 /** 旋转的动画,旋转两圈 */ privateangleAnimation() { let angleAnimation "transform.rotation.z") angleAnimation.fromValue = angle(-30) angleAnimation.toValue = angle(690) angleAnimation.fillMode = kCAFillModeForwards false angleAnimation.duration = duration angleAnimation.delegate = self "angleAnimation") }
The second step of the line by the long short animation
Here we still use the CABasicAnimation
basic animation, keyPath
acting on the properties of the line strokeEnd
, let the strokeEnd
line from 1 to zero to achieve the length of the animation, the following is and code:
The first step of the/** line animation, the line length from long to short * / PrivateFuncLineanimationone() {Let Lineanimationone = Cabasicanimation.init (keypath:"Strokeend") Lineanimationone.duration = duration/2Lineanimationone.fillmode = Kcafillmodeforwards Lineanimationone.removedoncompletion =falseLineanimationone.fromvalue =1Lineanimationone.tovalue =0 forI in0... 3{Let Linelayer = Lines[i] Linelayer.addanimation (Lineanimationone, Forkey:"Lineanimationone") } }
Displacement animation of the third step line
Here we also use the CABasicAnimation
basic animation, keyPath
acting on the line transform.translation.x
and transform.translation.y
attributes, to achieve the effect of the middle, the following is and the code:
The second step of the/** line animation, the line to the middle translation * / PrivateFuncLineanimationtwo() { forIinch 0... 3{varKeyPath ="Transform.translation.x" ifi%2==1{KeyPath ="TRANSFORM.TRANSLATION.Y"} LetLineanimationtwo = Cabasicanimation.init (keypath:keypath) lineanimationtwo.begintime = CACurrentMediaTime () + duration/2Lineanimationtwo.duration = duration/4Lineanimationtwo.fillmode = Kcafillmodeforwards Lineanimationtwo.removedoncompletion =falseLineanimationtwo.autoreverses =trueLineanimationtwo.fromvalue =0 ifI <2{Lineanimationtwo.tovalue = linelength/4}Else{Lineanimationtwo.tovalue =-linelength/4} LetLinelayer = Lines[i] Linelayer.addanimation (Lineanimationtwo, Forkey:"Lineanimationtwo") }//ratio on both sides of the triangle LetScale = (LineLength-2*margin)/(Linelength-linewidth) forIinch 0... 3{varKeyPath ="TRANSFORM.TRANSLATION.Y" ifi%2==1{KeyPath ="Transform.translation.x"} LetLineanimationtwo = Cabasicanimation.init (keypath:keypath) lineanimationtwo.begintime = CACurrentMediaTime () + duration/2Lineanimationtwo.duration = duration/4Lineanimationtwo.fillmode = Kcafillmodeforwards Lineanimationtwo.removedoncompletion =falseLineanimationtwo.autoreverses =trueLineanimationtwo.fromvalue =0 ifi = =0|| i = =3{Lineanimationtwo.tovalue = linelength/4* scale}Else{Lineanimationtwo.tovalue =-linelength/4* scale} LetLinelayer = Lines[i] Linelayer.addanimation (Lineanimationtwo, Forkey:"Lineanimationthree") } }
Fourth Step line Restore the original length of the animation
Here we still use the CABasicAnimation
basic animation, keyPath
acting on the properties of the line strokeEnd
, let the strokeEnd
line from 0 to one to achieve the length of the animation, the following is and code:
The third step of the/** line animation, the line from the short variable length * / PrivateFuncLineanimationthree() {//Line Moving animationLet Lineanimationfour = Cabasicanimation.init (keypath:"Strokeend") Lineanimationfour.begintime = Cacurrentmediatime () + Duration lineanimationfour.duration = duration/4Lineanimationfour.fillmode = Kcafillmodeforwards Lineanimationfour.removedoncompletion =falseLineanimationfour.fromvalue =0Lineanimationfour.tovalue =1 forI in0... 3{ifi = =3{lineanimationfour.delegate = self} Let Linelayer = Lines[i] Linelayer.a Ddanimation (Lineanimationfour, Forkey:"Lineanimationfour") } }
The final step is to combine the animations.
About the animation mix I didn't use CAAnimationGroup
, because these animations are not added to the same layer, plus the animation type a bit more trouble, I have animated beginTime
properties to control the order of the animation, but also added animation pause and continue the function, effects and code see:
//mark:public Methods /** Start animation * /Func startanimation () {angleanimation () Lineanimationone () lineanimationtwo () Lineanimationth REE ()}/** Pause Animation * /Func pauseanimation () {layer.pauseanimation () forLinelayer in lines {linelayer.pauseanimation ()} status =. Pause}/** Continue animation * /Func resumeanimation () {layer.resumeanimation () forLinelayer in lines {linelayer.resumeanimation ()} status =. animating} extension Calayer {//Pause animationFunc pauseanimation () {//Convert the current time cacurrentmediatime to the time on the layer, converting the parent to localtimeLet Pausetime = ConvertTime (Cacurrentmediatime (), Fromlayer:nil)//Set the Timeoffset of layer and use it to continue OperationTimeoffset = Pausetimethe ratio of//localtime to Parenttime is 0, which means the localtime is pausedSpeed =0; }//Resume animationFunc resumeanimation () {Let pausedtime = timeoffset speed =1Timeoffset =0; BeginTime =0 //Calculate pause TimeLet Sincepause = ConvertTime (Cacurrentmediatime (), Fromlayer:nil)-Pausedtime//local time vs. BeginTimeBeginTime = Sincepause}}//mark:animation Delegate OverrideFunc Animationdidstart (anim:caanimation) {ifLet animation = Anim as? cabasicanimation {ifAnimation.keypath = ="Transform.rotation.z"{status =. Animating}}}OverrideFunc animationdidstop (anim:caanimation, finished Flag:bool) {ifLet animation = Anim as? cabasicanimation {ifAnimation.keypath = ="Strokeend"{ifFlag {status =. Normal Dispatch_after (Dispatch_time (Dispatch_time_now, Int64 (interval) * Int64 (NSEC_PER_SEC)), dispatch _get_main_queue (), {ifSelf.status! =. animating {self.startanimation ()}}) } } } }//mark:override OverrideFunc touchesended (touches:set<uitouch>, withevent event:uievent?) {Switch Status { Case. Animating:pauseanimation () Case. Pause:resumeanimation () Case. Normal:startanimation ()}}
Summarize
The animation looks very complex, but carefully divided out also so that, in the animation before you want to think about the steps of animation, this is the key, I hope you can learn something through this blog, what good suggestions can be put forward at any time, thank you for reading ~~demo address
iOS animation advanced-hand touch teach you to write Slack Loading animation