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-basedCADisplayLink
Animated 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:CADisplayLink
Suitable 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