Title of this article, full text:

CADisplayLink time-based animation effects, and

CADisplayLink location-based animation effects

Introduction:

Gray interviews white,

Q: “In the old Meituan tabbar, there was a click TAB, the TAB was arched”

“Is the effect of rectangular partial arch, how to achieve”

Xiaobai: “Open GL, the research is not so deep, goodbye”

Small grey: “Using CADisplayLink + sub-custom drawing can also be done”

Grey: “the developer in the interview attributed the technology without thinking to the one he had not learned. Cup with”

time-basedCADisplayLinkAnimated effect, partially arched

Example: rectangular box, left and right. Click on either side and arch up that way

Picture below: arch the left side, effect

Help: Identify which side to click on

Set the background color to transparent

Because animation is based on drawing drawRect

@objc func tap(with gesture: UITapGestureRecognizer){ guard ! jelly.animating else{ return } let point = gesture.location(in: partial) let width = partial.frame.width let isRight = point.x > width / 2 let idx = isRight ? 1 : 0 partial.backgroundColor = UIColor.clear partial.startAnimation(idx) }Copy the code

The main logic

Two auxiliary state saving classes, omitted,

See Github Repo for details

  • DrawRect is only available in bounds,

So the view needs to leave a gap beforehand

It can be understood by the border line above

  • The interval that CADisplaylink uses is the frame rate of the screen

Cannot be set manually

You have to convert the duration of a time-based animation into a certain number of times

(From state, to state)

It is difficult to set an exact duration and keep the animation smooth

Here total = time * 20, given a certain number of times, finished consumption, end

See Github Repo for details

  • The local arch is based on time

(relevant)

Over time, the arch gets higher and higher

Progress value, based on time,

Figure out where the control points are

The animations in this article, the curves and animations, are not smooth

Tuning involved in detail optimization, omitted

class Partial: UIView { var animating = false var timer: CADisplayLink! let selectInfo = SelectUtil() let helper: Helper // omits an initialization method // As can be seen from the beginning, the timer 'CADisplayLink' has been running // For demo simplification required init? (coder: NSCoder) { helper = Helper(lasting: 3) super.init(coder: coder) timer = CADisplayLink(target: self, selector: #selector(Partial.tick)) timer.add(to: RunLoop.current, forMode: Func startAnimation(_ idx: Int){// On the selected side, repeatedly click invalid guard selectInfo.selectedIndex! = idx else { return } if selectInfo.selectedIndex ! = nil{// there is already one side up // for example, left side up // need to lower left first, NeedsReset = true} animating = true selectInfo.selectedIndex = idx helper.reset()} // Only override draw() if you perform custom drawing. // An empty implementation adversely affects performance during animation. override func draw(_ rect: CGRect) { // Drawing code let height = rect.height let topY: CGFloat = 100 // Drawing part, Var progress = helper.progresspositive if selectInfo.needsReset == true{progress = helper.progressNegative} let  deltaHeight = -1 * topY * progress // print("delta: \(deltaHeight)") // This topY is very funky // Because it is found that drawing can only be done inside the bounds let topLeft = CGPoint(x: 0, y: topY) let topMid = CGPoint(x: 0, y: topY) rect.width / 2, y: topY) let topRight = CGPoint(x: rect.width, y: topY) let fourthLhs = rect.width / 4 let fourthRhs = rect.width * 3 / 4 let bottomLeft = CGPoint(x: 0, y: height) let bottomRight = CGPoint(x: rect.width, y: height) let path = UIBezierPath() UIColor.blue.setFill() path.move(to: topLeft) switch selectInfo.currentIndex{ case 0: AddQuadCurve (to: topMid, controlPoint: CGPoint(x: fourthLhs, y: deltaHeight)) case 1: AddLine (to: topRight) Path.addQuadCurve (to: topRight, controlPoint: CGPoint(x: fourthRhs, y: fourthRhs) deltaHeight)) default: () } path.addLine(to: topRight) path.addLine(to: bottomRight) path.addLine(to: BottomLeft) path.close() path.fill()} @objc func tick(){guard animating else {return} Lower left after an interval of guard selectInfo. Intervals < = 0 else {selectInfo. Intervals - = 1 return} / / judge the completion of a phase of guard helper. Count > = 0 Else {if selectInfo.needsReset == true{// Phase 1 is complete, Selectinfo.needsreset = false helper.reset()} else{// All phases are complete // only one phase // second phase animating = false} selectInfo.lastSelectedIndex = selectInfo.selectedIndex return } helper.count -= 1 setNeedsDisplay() } }Copy the code
Section:CADisplayLinkSuitable for animation

A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display.

When the system renders each frame, our method gets called,

Keep the animation smooth

Location-based animation, jelly effect

Call part

This is the displacement animation that gives you the view’s starting position and ending position

Uiview. animate works with CADisplayLink

Location-based, to the point

Uiview. animate has an initial position from and an end position to

In this process, every time the drawing, progress (the position of the control point), is clear

Every time you draw, progress (the position of the control point) is relative to the coordinates at that time

The animation is completed and the deformation is finished

Can be understood as if the displacement and jelly deformation animation is a clear video

What we’re looking at are some sampling frames from that video

@IBAction func toAnimate(_ sender: Any) { guard ! jelly.animating else{ return } let from = view.bounds.height - jelly.bounds.height / 2 let to: Clear jelly. Center = CGPoint(x: jelly. Center. X, y: from) jelly.startAnimation(from, to) UIView.animate(withDuration: 3, delay: 0, usingSpringWithDamping: 0, initialSpringVelocity: 0, options: []) {self.jelly. Center = CGPoint(x: self.jelly. X, y: to)} completion: { _ in self.jelly.completeAnimation() } }Copy the code

The implementation code

Helper classes are omitted. See Github for details

The following code implements the same logic as above,

Easier to understand

class JellyView: UIView { // ... var animating = false var helper: Helper? // Call drawRect func startAnimation(_ from: CGFloat, _ to: CGFloat){ animating = true helper = Helper(displayLink: CADisplayLink(target: self, selector: #selector(jellyview.tick)), from: from, to: to)} // completeAnimation(){animating = false helper? .displayLink.invalidate() helper = nil } // Only override draw() if you perform custom drawing. // An empty implementation adversely affects performance during animation. override func draw(_ rect: CGRect) { // Drawing code guard let info = helper, let layer = layer.presentation() else { return } var progress: CGFloat = 1 if animating{ progress = 1 - (layer.position.y - info.to) / (info.len) } let height = rect.height let // print("delta: \(deltaHeight)") let topLeft = CGPoint(x: 0, y: 0) deltaHeight) let topRight = CGPoint(x: rect.width, y: deltaHeight) let bottomLeft = CGPoint(x: 0, y: height) let bottomRight = CGPoint(x: rect.width, y: height) let path = UIBezierPath() UIColor.blue.setFill() path.move(to: topLeft) path.addQuadCurve(to: topRight, controlPoint: CGPoint(x: rect.midX, y: 0)) path.addLine(to: bottomRight) path.addQuadCurve(to: bottomLeft, controlPoint: CGPoint(x: rect.midX, y: height - deltaHeight)) path.close() path.fill() } @objc func tick(){ setNeedsDisplay() } }Copy the code

github repo