Android自定义View之鲨鱼和小鱼的等待动画
之前在看文章的时候发现了一个gif动画,感觉很有意思,适逢最近学习kotlin,就打算用kotlin在android中实现一次,部分角度及参数设置的比较随意,如有需要可以可随意修改
先看效果图
部分角度及速率还有优化的空间,不过主要的效果都已经实现出来了
设计过程
因为是个动画,刚看的时候感觉无从下手,仔细分析过后,发现…其实很简单的
首先是一个循环的动画,先要找到某个循环的开始或者结束,我截取的某个循环部分是鲨鱼鳍在右侧,本地左右变化几下,然后向左追逐小鱼到左侧部分.向右的部分,通过将向左的部分x镜像一下就可以得到.
由此可以将整个画面拆成三个部分:
- 其一是水面的部分,水面以下及水面和鱼接触的部分的留白处理都是在后面鲨鱼和小鱼处进行的,所以最后绘制的是一个水面以下部分的遮挡,这个部分也是一个比较好绘制的部分
- 其二是鲨鱼鳍的部分, 这个部分难度中等,主要的难度是两个圆找交点的计算过程…如果有需要可以根据代码设计相关参数进行调整
- 其三是小鱼的部分,这个是画面中最难实现的部分,主要涉及小鱼的绘制(椭圆及三角形),小鱼的水面的接触时水面的留白,小鱼入水的时候溅起的水花
代码实现
水面部分
代码就不放了,因为只是单纯的绘制个线和下部的遮盖,可以看看效果图
水面
为加遮盖的水面(可以看到水面之下的小鱼)
鲨鱼部分
鲨鱼鳍的主要设计思路就是先绘制两个圆,再根据两个圆的交点决定绘制的圆弧范围,注意
绘制的时候遮盖下部的水面
效果图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
|
private fun drawShark(index: Float, canvas: Canvas) { val p = Paint() p.strokeWidth = 5F p.color = Color.BLACK p.style = Paint.Style.STROKE
val r = 100F var xofset = -65 var yofset = 0
drawSharkOut(canvas , index , r , xofset , yofset , isRight , p) drawSharkIn(canvas , index , r , xofset , yofset , isRight , p)
p.color = viewBackgroundColor canvas.drawLine(index - r - xofset - viewWidth/2, 0F , index + r + xofset - viewWidth/2, 0F, p)
}
fun drawSharkOut(canvas: Canvas , index: Float, r: Float, xofset: Int, yofset: Int, directionRight: Boolean, p: Paint) { if (directionRight){ val xofset = xofset - viewWidth/2 val rectf = RectF(index - r + xofset, -r + yofset , index + r + xofset , r + yofset) canvas.drawArc(rectf, -95F , 95F, false, p) }else{ val xofset = 30 + xofset - viewWidth/2 val rectf = RectF(index + xofset + yofset , -r , index + 2 * r + xofset, r + yofset ) canvas.drawArc(rectf, -180F , 95F, false, p) }
}
fun drawSharkIn(canvas: Canvas , index: Float, r1: Float, xofset: Int, yofset: Int, directionRight: Boolean, p: Paint) { val r = (1.5 * r1).toInt() if (directionRight) { val xofset = (- (r - ( r - r1) * 0.6)).toInt() + xofset - viewWidth/2 val rectf = RectF(index - r + xofset, (- r + yofset).toFloat(), index + r + xofset, (+ r + yofset).toFloat()) canvas.drawArc(rectf, -42F, 42F, false, p) }else{ val xofset = -45 + xofset - viewWidth/2 val rectf = RectF(index + r + xofset, (- r + yofset).toFloat(), index + 3 * r + xofset, (+ r + yofset).toFloat()) canvas.drawArc(rectf, -180F, 42F, false, p) }
}
|
小鱼部分
小鱼部分设计三个部分,鱼身(本体,眼睛,鱼尾),鱼的运动,鱼入水时的水花.
小鱼的运动轨迹可以看出来是一个圆弧,涉及的部分就是要根据鲨鱼鳍的位置调整在运动圆环上的位置,简单的canvas变换就可以做到
鱼身,相对来说比较简单,一个椭圆,一个三角形,一个点就可以完成了,再实现的时候先绘制了一个和背景色相同的填充内部颜色的鱼,用来实现鱼的水面接触的部分留白.
小鱼入水的水花实际上是在鱼的两侧没测画两个圆弧,当小鱼和入水越来越深(canvas的旋转的角度越来越大的时候,同一侧的两个水花之间的间隔也会变大,用来模拟水花的下落)
小鱼还未出水的部分(关闭水面下发的遮盖)
小鱼和水面接触的部分(遮盖水面的绘制)
小鱼入水时的水花效果
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| private fun drawFish(canvas: Canvas, index: Float, p: Paint) {
var xofsetFish = viewWidth / 5 * 2 var yofsetFish = 0F val widthFish = 20F val heightFish = 12F val widthFishTail = 20F val heightFishTail = 10F
canvas.translate(xofsetFish - viewWidth/2 , yofsetFish) canvas.rotate(90F) val per = (index - viewWidth / 5 * 2) / (viewWidth / 5 * 2) val degreesi =per * 360 + 10 canvas.rotate(degreesi)
if (per > 0.4) {
xofsetFish = 0F yofsetFish = -viewWidth / 5
val rectF = RectF(xofsetFish - widthFish, yofsetFish - heightFish, xofsetFish + widthFish, yofsetFish + heightFish) p.color = viewBackgroundColor p.style = Paint.Style.FILL canvas.drawArc(rectF, 0F, 360F, true, p)
p.color = Color.BLACK p.style = Paint.Style.STROKE canvas.drawArc(rectF, 0F, 360F, true, p)
canvas.drawLine(xofsetFish + widthFish, yofsetFish, xofsetFish + widthFish + widthFishTail, yofsetFish + heightFishTail, p) canvas.drawLine(xofsetFish + widthFish + widthFishTail, yofsetFish + heightFishTail, xofsetFish + widthFish + widthFishTail, yofsetFish - heightFishTail, p) canvas.drawLine(xofsetFish + widthFish + widthFishTail, yofsetFish - heightFishTail, xofsetFish + widthFish, yofsetFish, p)
canvas.drawPoint(xofsetFish - widthFish / 3, yofsetFish , p)
if (per <= 0.48&& per >= 0.45){
val water = 50F val waterR = 30F canvas.translate(0F ,yofsetFish + water) p.color = Color.BLACK p.style = Paint.Style.STROKE val waterRectF = RectF(-waterR , -waterR , waterR , waterR) canvas.drawArc(waterRectF, -90F , 30F , false, p) val changeWater = ( 1 - (per - 0.45) / 0.03) * 45 canvas.drawArc(waterRectF, (-60 + changeWater).toFloat(), 30F , false, p)
canvas.translate(0F , - 2 * water) canvas.drawArc(waterRectF, 90F , -30F , false, p) canvas.drawArc(waterRectF, (90 - changeWater).toFloat(), -30F , false, p) } } }
|
至此这个小动画就可以实现了,具体的代码可以查看我的github