O(∩_∩)O哈利诺!又是一個(gè)很cool的動(dòng)畫剩燥。原作者在這里灭红。畫面看著太美好,于是又試著用Swift寫了一個(gè)变擒。我的在這里
動(dòng)畫分解
1赁项、首先是橢圓背景色的漸變,灰色---》淡藍(lán)色悠菜。
2、臉的動(dòng)畫摩窃。這里又分為臉盤(??就這么叫吧)和器官(2眼睛+1嘴巴)的動(dòng)畫。
- 臉盤的動(dòng)畫鹦聪,比較簡單蒂秘,就是x的位移。
- 眼睛+嘴巴這個(gè)整體的動(dòng)畫姻僧,有個(gè)回彈的效果。先是向右飛出去赌莺,然后到達(dá)右邊之后松嘶,又從左邊飛進(jìn)來,最后回彈一下巢音。
3尽超、嘴巴的動(dòng)畫。off的時(shí)候橙弱,是個(gè)長方形的嘴,切換到on斜筐,變成了笑臉嘴蛀缝,并且有個(gè)逐漸張開的過程。
AIYA嗤练,還真是難描述在讶。
元素分解
知道了動(dòng)畫步驟之后,元素就比較好分了革答。臉盤是1個(gè)layer,2眼睛+嘴巴==1個(gè)layer途茫,橢圓背景=1view溪食。
開工開工
嘴巴
首先注意到的是嘴巴的繪制错沃,on和off的狀態(tài)其實(shí)是2種不同的曲線,長方形很好畫捎废,那么張開的嘴呢致燥?當(dāng)然都是用萬能的貝塞爾曲線啦嫌蚤。其實(shí)看了代碼,都覺得簡單o(>﹏<)o智政。
閉嘴的frame=(0.25w, 0.7h, 0.5w, 0.1h)
張嘴的箱蝠,x,y,w都是一樣的,只不過是控制2個(gè)control point的位置宦搬。
private func mouthPath() -> UIBezierPath {
if isOn {
let path = UIBezierPath()
path.moveToPoint(CGPointMake(mouthRect().origin.x, mouthRect().origin.y))
path.addCurveToPoint(CGPointMake(mouthLenth() + mouthRect().origin.x, mouthY()), controlPoint1: CGPointMake(mouthRect().origin.x + mouthOffset / 4, mouthY() + mouthOffset / 2),
controlPoint2: CGPointMake(mouthRect().origin.x + mouthOffset * 3 / 4, mouthY() + mouthOffset / 2))
path.closePath()
return path
} else {
let path = UIBezierPath(rect: mouthRect())
return path
}
}
眼睛
眼睛就是2個(gè)橢圓间校,有現(xiàn)成的。frame=(0.2w, 0.25h, 0.4w, 0.6h)
private func rightEyePath() -> UIBezierPath {
let origin = rightEyeOrigin()
let size = eyeSize()
let path = UIBezierPath(ovalInRect: CGRectMake(origin.x, origin.y, size.width, size.height))
return path
}
眼睛+嘴巴
其實(shí)就是在layer上面胁附,分別把他們畫上去滓彰。
override func drawInContext(ctx: CGContext) {
let bezierLeft = leftEyePath()
let bezierRight = rightEyePath()
let bezierMouth = mouthPath()
CGContextAddPath(ctx, bezierLeft.CGPath)
CGContextAddPath(ctx, bezierRight.CGPath)
CGContextAddPath(ctx, bezierMouth.CGPath)
CGContextSetFillColorWithColor(ctx, color().CGColor)
CGContextSetStrokeColorWithColor(ctx, UIColor.clearColor().CGColor)
CGContextFillPath(ctx)
}
臉盤/橢圓背景
這個(gè)更簡單了,用cornerRadius或者bezierPath都行饼暑。
嘴張開的動(dòng)畫
因?yàn)樽焓怯胋ezier曲線畫的,有2個(gè)control point彰居。所以主要是操控control point撰筷,來實(shí)現(xiàn)張開的效果。
為了支持自定義屬性動(dòng)畫抬闯,需要用到needsDisplayForKey
关筒,改變屬性后,可以引發(fā)drawInContext重繪蒸播。mouthOffset最后其實(shí)是等于mounLength的,所以只需要將mouthOffset從0變到mounLength即可胀屿。
internal override class func needsDisplayForKey(key: String) -> Bool {
if key == "mouthOffset" {
return true
}
return super.needsDisplayForKey(key)
}
動(dòng)畫順序
拿從off到on的過程來說包雀。
1、背景漸變+臉盤移動(dòng)+器官向右飛出
2葡兑、臉盤移動(dòng)的動(dòng)畫結(jié)束后赞草,器官從左往右飛出,并回彈蜕劝。同時(shí)嘴巴開始張開動(dòng)畫轰异。
3、回彈動(dòng)畫結(jié)束搭独,整個(gè)動(dòng)畫結(jié)束牙肝。
動(dòng)畫注意
在動(dòng)畫結(jié)束之后嗤朴,要將對(duì)應(yīng)值設(shè)成動(dòng)畫之后的值虫溜。
override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
if flag {
if let type = anim.valueForKey("animation") as? String {
// 臉盤移動(dòng)到端點(diǎn)
if type == "faceBgLayerAnimation" {
faceBgLayer.removeAllAnimations()
faceBgLayer.position = positionOfFaceView(!isOn)
faceLayer.isOn = !isOn
faceLayer.mouthOffset = !isOn ? faceLayer.bounds.size.width / 2 : 0
faceLayer.setNeedsDisplay()
if (!isOn) {
mouthAnimation(isOn, offset: faceLayer.bounds.size.width / 2)
moveRightBackAnimation()
} else {
moveLeftBackAnimation()
}
} else if type == "backgroundAnimation" {
self.backgroundColor = isOn ? onColor : offColor
} else if type == "moveBackAnimation" {
faceLayer.removeAllAnimations()
isOn = !isOn
isAnimating = false
}
}
}
}