上一篇讲解了WaveLoadingView的测量步骤,本篇将讲解它的绘制原理。
前期准备
在讲解Android自定义控件绘制之前,我们首先需要知道动画效果是怎么实现的。
下面这张图小球从左沿着一条弧线的轨迹运动到右边。
把它的动作分解一下。
我们称每一张图片为帧,小球运动的动画效果其实就是帧1~帧5快速切换产生的。动作分解的越细,动画的效果也越好,当然帧数变多了,动画的体积也就跟着变大了。
onDraw方法
上一篇我们完成了控件的测量工作,但此时如果我们把控件拿来使用估计什么都不会显示。那是因为onMeasue只是规定了控件的宽高,还没有把它的样子画出来。绘制工作是在onDraw方法里完成的。我们来看看onDraw方法。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
说到onDraw方法,我们还得先认识两个类:Canvas 和 Paint
Canvas叫画布,是用来显示图像的,只有在画布和控件的交集处绘制图像才能显示出来,说的有点抽象,来看看下图。
假如蓝色方框部分为控件的显示范围,红色方框为画布的范围,那么只有在黄色区域绘制的图像才会显示。
再看看坐标系,Android规定屏幕的从左往右为x轴正方向,从上到下为y轴正方向。默认下,画布和控件的范围是一致的,如下图。
再来讲讲Paint类,画笔类,从名字上也可以知道它是用来绘制图像的,一些基本形状(圆、矩形、弧线等)都是通过它绘制到Canvas上的。当然,还可以设置颜色和透明度。例如在Canvas上绘制一个100*50像素的矩形。
canvas.drawRect(0,0,100,50,mPaint);
Canvas类和Paint类还有很多强大的功能,可以看看这篇文章。
回过来看看onDraw方法。假设我们要在控件正中央画一个50*50像素的红色矩形,代码如下。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
Paint paint = new Paint();
paint.setColor(Color.RED);
int left = (width - 50) / 2;
int top = (height - 50) / 2;
int right = left + 50;
int bottom = top + 50;
canvas.drawRect(left,top,right,bottom,paint);
}
这样可以在控件正中央绘制一个50*50像素的红色正方形,但其实代码还有很多瑕疵,后面会讲解。
调用一次onDraw后,一帧画面就绘制出来了。这么说来要想实现动画效果就需要多次调用onDraw方法了。
回到刚刚小球的分解动作。
每次调用onDraw方法,在指定位置画出圆,连贯起来就可以实现动画效果了。这里需要再认识invalidate方法。invalidate方法可以强制让应用重新绘制这个控件。调用几次invalidate方法就相当于调用了多次onDraw。
现在希望小球从左到右移动,动画效果时间为1000ms,那么每250ms调用一次invalidate方法就行了。可要怎么精确把握调用时间呢,这里就需要用到Handler了。
通过Handler实现动画效果
通过以下方式可以实现每250ms调用一次onDraw的效果。
public void startAnim(){
mCount = 0;
invalidate();
sendEmptyMessageDelayed(0,250);
}
private Hnadler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
mCount++;
if(mCount <= 4){
invalidate();
sendEmptyMessageDelayed(0,250);
}
}
}
但这么做会有内存泄漏的风险,本篇讲解的重点是实现动画效果,性能优化会放在下一篇来讲。
再来简单看看WaveLoadingView的onDraw方法。
@Override
protected void onDraw(Canvas canvas) {
//1
prepareElementsX();
//2
prepareElementsY();
switch(mType){
case IMAGE_TYPE_TEXT:
onDrawText(canvas);
break;
case IMAGE_TYPE_CIRCLE:
onDrawCirclesWave(canvas);
break;
case IMAGE_TYPE_SQUARE:
onDrawSquareWave(canvas);
break;
case IMAGE_TYPE_RECT:
onDrawRectWave(canvas);
break;
case IMAGE_TYPE_NOISE:
onDrawNoiseWave(canvas);
break;
case IMAGE_TYPE_CUSTOM:
onDrawDrawable(canvas);
break;
}
}
代码1和代码2先计算好各元素的摆放位置,接着根据不同的风格绘制出一帧图像。在handler上定时调用invalidate方法就实现动画效果了。
最后
限于文章篇幅,文章只贴了关键代码,想深入了解此控件绘制的朋友可以到Github项目上阅读完整代码。
下一篇将讲解自定义控件的性能优化。