最近开始学习自定义View,看到如今公司项目上的一个动画效果,顿时想到其实能够本身画,因而就开始着手优(zhuang)化(bi)这个动画。java
动画以下: git
其实很简单对不对,但初学者的我仍是要思考一下。github
动画有两部分,canvas
根据UI提供的详细动画细节,能够知道:app
动效总共时间为2S,以后反复循环,每秒帧数为24帧,其中圆形元素控件大小为6px,拉伸都是以圆心为拉伸中心点进行拉伸。 0-1s为从左到右的拉伸动画,1s-2s为从右到左的拉伸动画,以后为循环。ide
每个点,都有5种不一样的拉伸量,这里咱们把对应的拉伸后的Y的高度命名为:函数
public float maxHeight;
public float threeHeight;
public float halfHeight;
public float oneHeight;
复制代码
其中还有一种拉伸量为0。maxHeight是最大的高度,threeHeight为次最高高度。post
第1帧: 初始化,每个的点均为 6 px * 6 px性能
第2帧: 元素1 高7.3px 对应 oneHeight,其余元素保持初始状态。学习
第3帧: 元素1 高11px 对应 halfHeight,其余元素保持初始状态
第4帧: 元素1 高14.7px 对应 threeHeight,元素2 高8.3px 对应 oneHeight,其余元素保持初始状态。
第5帧: 元素1 高16px 对应 maxHeight,元素2 高15px 对应 halfHeight,其余元素保持初始状态。
第6帧: 元素1 高14.7px 对应 threeHeight,元素2 高21.7px 对应 threeHeight,元素3 高10.1px 对应 oneHeight,其余元素保持初始状态。
... 经过观察,我...先定义一个类,来存储这些点的四种拉伸量的高度(咱们也称之为状态)和中心位置的坐标:
public class VoiceAnimPoint {
public int centerX, centerY;
public float maxHeight;
public float threeHeight;
public float halfHeight;
public float oneHeight;
public VoiceAnimPoint(int centerX, int centerY, float maxHeight, float threeHeight, float halfHeight, float oneHeight) {
this.centerX = centerX;
this.centerY = centerY;
this.maxHeight = maxHeight;
this.threeHeight = threeHeight;
this.halfHeight = halfHeight;
this.oneHeight = oneHeight;
}
}
复制代码
而后在 onSizeChanged
中初始化这五个点:
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
points = new VoiceAnimPoint[5];
points[0] = new VoiceAnimPoint(getWidth()/2-24,getHeight()/2,16f,14.7f,11f,7.3f);
points[1] = new VoiceAnimPoint(getWidth()/2-12,getHeight()/2,24f,21.7f,15f, 8.3f);
points[2] = new VoiceAnimPoint(getWidth()/2,getHeight()/2,38f,33.9f,22f,10.1f);
points[3] = new VoiceAnimPoint(getWidth()/2+12,getHeight()/2,24f,21.7f,15f,8.3f);
points[4] = new VoiceAnimPoint(getWidth()/2+24,getHeight()/2,16f,14.7f,11f,7.3f);
}
复制代码
对于元素1,咱们看到有这样的一个变化过程:
那么其余的元素呢,咱们把他们的变化过程放在一块儿:
那么onDraw中能够这样来:pointIndex 表明着元素1绘制的第 N 帧,而后依次按照如上所分析的去获得其余元素的对应帧。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < points.length; i++) {
VoiceAnimPoint point = points[i];
int y = indexChangeFunc(pointIndex - i*2);
switch (y) {
case 0:
canvas.drawLine(point.centerX,point.centerY, point.centerX,point.centerY+0.01f,paint);
break;
case 2:
canvas.drawLine(point.centerX,point.centerY-point.halfHeight/2+pointWidth/2,
point.centerX,point.centerY+point.halfHeight/2-pointWidth/2,paint);
break;
case 4:
canvas.drawLine(point.centerX,point.centerY-point.maxHeight/2+pointWidth/2,
point.centerX,point.centerY+point.maxHeight/2-pointWidth/2,paint);
break;
case 1:
canvas.drawLine(point.centerX,point.centerY-point.oneHeight/2+pointWidth/2,
point.centerX,point.centerY+point.oneHeight/2-pointWidth/2,paint);
break;
case 3:
canvas.drawLine(point.centerX,point.centerY-point.threeHeight/2+pointWidth/2,
point.centerX,point.centerY+point.threeHeight/2-pointWidth/2,paint);
break;
}
}
}
复制代码
其中的0-4状态就是上面的函数的Y值,经过X获得相应的Y,而Y则对应则元素的5中状态,onDraw中就是根据5中状态去绘制相应的高度:
/** * 动画轨迹其实符合一个函数 * 这里传入对应的x,返回函数的y * @param x 位置 * @return y 4 : 最大, 3:threeHeight, 2: 一半, 1:oneHeight, 0 :0 。 */
private int indexChangeFunc(int x) {
if (x<0)
return 0;
else if (x<4)
return x;
else if (x<8)
return -x + 8;
else
return 0;
}
复制代码
drawLine
画,并且上下端是圆角的,因此咱们要设置Paint
的线帽为圆形,paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(0xffC2E379);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeWidth(pointWidth);
paint.setStrokeCap(Paint.Cap.ROUND);
复制代码
注意的一点,假如你的Paint
宽10px,而同时你又设置了线帽为圆形,画了20px的line
,那其实你画的以下:
因此才有上面的onDraw
中的,高度要减去线帽:
canvas.drawLine(point.centerX,point.centerY-point.maxHeight/2+pointWidth/2,point.centerX,point.centerY+point.maxHeight/2-pointWidth/2,paint);
复制代码
pointIndex
自增表明正序播放,pointIndex
自减表明倒叙播放。@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(bgBitmap, getWidth() / 2 - bgBitmap.getWidth() / 2, getHeight() / 2 - bgBitmap.getHeight() / 2, paint);
for (int i = 0; i < points.length; i++) {
VoiceAnimPoint point = points[i];
int y = indexChangeFunc(pointIndex - i*2);
switch (y) {
case 0:
canvas.drawLine(point.centerX,point.centerY, point.centerX,point.centerY+0.01f,paint);
break;
case 2:
canvas.drawLine(point.centerX,point.centerY-point.halfHeight/2+pointWidth/2,
point.centerX,point.centerY+point.halfHeight/2-pointWidth/2,paint);
break;
case 4:
canvas.drawLine(point.centerX,point.centerY-point.maxHeight/2+pointWidth/2,
point.centerX,point.centerY+point.maxHeight/2-pointWidth/2,paint);
break;
case 1:
canvas.drawLine(point.centerX,point.centerY-point.oneHeight/2+pointWidth/2,
point.centerX,point.centerY+point.oneHeight/2-pointWidth/2,paint);
break;
case 3:
canvas.drawLine(point.centerX,point.centerY-point.threeHeight/2+pointWidth/2,
point.centerX,point.centerY+point.threeHeight/2-pointWidth/2,paint);
break;
}
}
if (!isRevert) {
pointIndex++;
}
else {
pointIndex--;
}
if (pointIndex == 23) {
isRevert = true;
pointIndex = 17;
}
else if (pointIndex == -6) {
pointIndex = 0;
isRevert = false;
}
}
复制代码
invalidate()
方法。private Runnable r = new Runnable() {
@Override
public void run() {
VoiceAnimView.this.invalidate();
VoiceAnimView.this.postDelayed(r, 42);
}
};
public void startAnim() {
if (!isStart) {
isStart = true;
this.post(r);
}
}
public void stopAnim() {
if (isStart) {
isStart = false;
this.removeCallbacks(r);
}
}
复制代码
lottie
直接去实现,可谓方便快捷:用了自定义动画后:
这算是本身的第一个自定义View,可能实现思路上有问题,或者你们有更好的思路,欢迎一块儿讨论! 还有几个问题:
invalidate()
的无参数方法,使用有参数的方法,但我查了一下官方文档,在API21后,有参数的方法就无论用了。还有什么别的方法优化本身的自定义动画?