使用surfaceView结合贝塞尔曲线生成波形动态控件

时间:2023-02-09 21:56:07


首先,使用surfaceViw是为了让控件减少主线程资源的占用。考虑到该控件有可能是长时间在当前界面上运行,而且该控件是处于一直刷新的状态,如果使用普通view,会占用主线程的资源,造成界面卡顿;

波形动态控件的波形实现是先使用路径绘制多个波形封闭形状,并填充半透明颜色,然后将这多个封闭形状叠加在一起。

(图1)

使用surfaceView结合贝塞尔曲线生成波形动态控件

波形封闭形状使用Path来绘制,先绘制上面的曲线,然后依次绘制==>A.右边的竖直线==>B.底部直线=>C左边竖直线。
AB两条竖直线使用Path的lineTo方法绘制,最后的C调用Path的close方法,表示将这条路径封闭。

上面的曲线绘制就要使用到Path的quadTo方法了,这个方法就是绘制2阶贝塞尔曲线,3阶贝塞尔曲线使用cubicTo方法。这里用quadTo就可以了。

2阶,3阶贝塞尔曲线大概可以这么理解:

2阶:一条线,首尾两端固定,然后中间取一个控制点,然后拉动这个点,使这条线变弯曲。(实际情况是控制点不是在曲线上)

(图2)

使用surfaceView结合贝塞尔曲线生成波形动态控件

3阶:一条线,首尾两端固定,然后中间取两个点,然后拉动这个两个控制点,使这条线变弯曲。(实际情况是控制点不是在曲线上)

(图3)

使用surfaceView结合贝塞尔曲线生成波形动态控件

然后,为什么开始坐标,结束坐标,控制点坐标都一样,生成的曲线就一定是A的形状呢,这个要从贝塞尔曲线的原理说起

使用surfaceView结合贝塞尔曲线生成波形动态控件

二阶贝塞尔曲线:

一个二阶贝塞尔曲线需要一个开始点,控制点,结束点,这三点,对应到图上就是P1,P2,P3
把这三个点用两条直线L1,L2连接起来,

我们要知道曲线可以认为是无数个点连接起来的.接下来就可以取点绘制曲线了,
第一个点:先取L1的0%的位置和L2的0%的位置连接一个直线L3,然后在L3的0%位置取一个点,后面曲线经过这个点时会和这个点相切
第二个点:先取L1的10%的位置和L2的10%的位置连接一个直线L3,然后在L3的10%位置取一个点,后面曲线经过这个点时会和这个点相切
第三个点:先取L1的20%的位置和L2的20%的位置连接一个直线L3,然后在L3的20%位置取一个点,后面曲线经过这个点时会和这个点相切

这个过程是我自己的理解,公式什么的起始我也不懂,只是看下面参考网址的动图推测的,建议大家进去看一下,人家写的比我专业多了,也有动图帮助理解.
这样直到100%,这根2阶贝塞尔曲线就画出来了(图中的辅助线是到50%时候的情况).
使用surfaceView结合贝塞尔曲线生成波形动态控件

然后3阶,4阶级也都是用同样的方法,只不过线和点的数量增加了(图中的辅助线是到50%时候的情况).

具体计算公式和详细说明可参考 http://www.cnblogs.com/hnfxs/p/3148483.html
使用surfaceView结合贝塞尔曲线生成波形动态控件



了解这个之后就可以开始画图形了,图形绘制如下步骤:

(图4)

使用surfaceView结合贝塞尔曲线生成波形动态控件

图形划出来了,接下来就要让这个波形怎么让他动起来,这个很简单,只要让每次刷新的时候,然这个图形向后移动一点,
这也是为什么上面绘制图形的时候要先绘制到屏幕外面。这样看起来就像波形在动了。

(图5)

使用surfaceView结合贝塞尔曲线生成波形动态控件

接下来看代码

首先,创建一个类,继承SurfaceView,并实现SurfaceHolder.Callback和Runnable

class JonsonWaveView extends SurfaceView implements SurfaceHolder.Callback , Runnable  

然后在这个类的构造器初始化方法里面做以下操作

    //获取屏幕密度
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(displayMetrics);
mDensity = displayMetrics.density;

//获取xml文件中填写的属性
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.JonsonWaveView);
xmlWaveHeightIndex = typedArray.getDimension(R.styleable.JonsonWaveView_waveHeight , 300f);//波形位置(水位高度)
xmlSwingHeight = typedArray.getDimension(R.styleable.JonsonWaveView_swingHeight, 10f);//波形波动的高度(波峰到基线的高度)
xmlWave1Color = typedArray.getColor(R.styleable.JonsonWaveView_wave1Color , Color.parseColor("#BFE5FF"));//第一层形状颜色
xmlWave2Color = typedArray.getColor(R.styleable.JonsonWaveView_wave2Color , Color.parseColor("#7AE5FF"));//第二层形状颜色
xmlWave3Color = typedArray.getColor(R.styleable.JonsonWaveView_wave3Color , Color.parseColor("#0CE5FF"));//第三层形状颜色
xmlWave2Range = typedArray.getFloat(R.styleable.JonsonWaveView_wave3Range , 15f);//第二层和第一层的距离
xmlWave3Range = typedArray.getFloat(R.styleable.JonsonWaveView_wave2Range , 20f);//第三层第一层的距离
xmlBgStartColor = typedArray.getColor(R.styleable.JonsonWaveView_startColor, Color.WHITE);//背景为渐变色,渐变色的开始颜色
xmlBgEndColor = typedArray.getColor(R.styleable.JonsonWaveView_endColor, Color.parseColor("#C8E5FF"));//背景为渐变色,渐变色的结束颜色
xmlWaveSpeed = typedArray.getFloat(R.styleable.JonsonWaveView_waveSpeed, 1.5f);//波形流动的速度
cyclerCount = typedArray.getInteger(R.styleable.JonsonWaveView_cyclerCount, 2);//控件显示多少个周期

mHolder = getHolder();
mHolder.addCallback(this);//获取SurfaceHolder并添加回调

//初始化波浪画笔
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(xmlWave1Color);
mPaint.setStyle(Paint.Style.FILL);

//初始化背景画笔
mBgPaint = new Paint();
mBgPaint.setStyle(Paint.Style.FILL);


下面的这两行代码是取出SurfaceView中的holder,然后这个holder需要给他设置一个SurfaceHolder.Callback.

mHolder = getHolder();
mHolder.addCallback(this);


这个callback会回调以下方法:
1.surfaceCreated(surfaceView 初始化完成后调用)
2.surfaceChanged
3.surfaceDestroyed(surfaceView被关闭时调用)

我们的JonsonWaveView已经实现了这个接口,所以用this即可.


构造器执行完成后,接下来就会执行到回调surfaceCreated

sufaceCreated方法的内容如下:

@Override
public void surfaceCreated(SurfaceHolder holder) {
//是否可以绘制的标志
mIsDrawing = true;

//获取控件宽高
width = getWidth();
height = getHeight();


//初始化渐变,给背景色画笔设置渐变
LinearGradient linearGradient = new LinearGradient(0, 0, 0, getHeight() , new int[] {xmlBgStartColor, xmlBgEndColor}, null, Shader.TileMode.MIRROR);
mBgPaint.setShader(linearGradient);

//第一层波形底部高度
float shape1Top = getHeight() - xmlWaveHeightIndex;//第一层形状的top位置

swingHeight = (int) (xmlSwingHeight * mDensity);//波动高度(波峰到基线高度)
layerRange1 = (int) (shape1Top + xmlSwingHeight);//第一层图形贝塞尔曲线基线高度
layerRange2 = (int) (xmlWave2Range * mDensity + layerRange1);//第二层图形贝塞尔曲线基线高度
layerRange3 = (int) (xmlWave3Range * mDensity + layerRange1);//第三层图形贝塞尔曲线基线高度

cycleWidth = width / cyclerCount;//一个波动周期宽度
besselWidth = cycleWidth / 2;//半个波动周期宽度(一个2阶贝塞尔曲线的宽度)

new Thread(this).start();//执行子线程中的任务(绘制图形)
}

首先设置一个mIsDrawing标志,这个标志用于控制是否要绘制.
这个主要是防止surfaceView不在当前显示后会闪退的问题,因为设置路径和绘制图形的时候,是在子线程中执行的,
如果surfaceView不显示的时候,子线程绘制未完成,那么等子线程完成后,它就会把完成绘制的画板提交到主线程显示,
这时候由于surfaceView控件已经不显示了,而子线程又要主线程来显示不存在的控件,这个就会导致崩溃.
所以在回调surfaceDestroyed的时候,这个标志会被设置为false.


接下来获取控件宽高尺寸,初始化背景渐变色,各项尺寸,留着备用.
最后一行new Thread(this).start(),这个就是开始执行绘制图形任务了.绘制图形任务代码如下:(绘制顺序可以参照上面的图4)

@Override
public void run() {
while(mIsDrawing){

//第一层图形路径
mPath = new Path();
mPath.moveTo(-cycleWidth + offset , layerRange1);//路径移动起始点到负一个周期的位置,加上当前时间的图形移动距离

mPath.quadTo(-cycleWidth + besselWidth / 2 + offset , layerRange1 + swingHeight , -cycleWidth / 2 + offset , layerRange1);
mPath.quadTo(-cycleWidth / 2 + besselWidth /2 + offset , layerRange1 - swingHeight , offset , layerRange1);

for (int i = 0; i < cyclerCount; i++) {
//quadTo
//参数1:贝塞尔曲线控制点X坐标
//参数2:贝塞尔曲线控制点Y坐标
//参数3:贝塞尔曲线终点X坐标
//参数4:贝塞尔曲线终点y坐标
float controlX = (i * cycleWidth) + (besselWidth / 2) + offset;
float controlY = layerRange1 + swingHeight;
float endX = (cycleWidth * i) + besselWidth + offset;
float endY = layerRange1;
mPath.quadTo(controlX , controlY , endX , endY);//第i个周期内的第一个贝塞尔曲线

float controlX1 = endX + (besselWidth/2);
float controlY1 = layerRange1 - swingHeight;
float endX1 = endX + besselWidth;
float endY1 = endY;
mPath.quadTo(controlX1 , controlY1 , endX1 , endY1);//第i个周期内的第二个贝塞尔曲线
if(i == cyclerCount - 1){
mPath.lineTo(width , height);//路径-右边竖直线
mPath.lineTo(-cycleWidth , height);//路径-底部横直线
mPath.close();//路径-封闭
}
}


//第二层图形路径
...(同上)
//第三层图形路径
...(同上)

//递增图形的移动量;
offset += (int) (2 * xmlWaveSpeed* mDensity);//图形1的偏移量
offset1 += (int) (1 * xmlWaveSpeed * mDensity);//图形2的偏移量
offset2 += (int) (1.5 * xmlWaveSpeed * mDensity);//图形3的偏移量

draw();//路径创建按完成后就可以开始按照路径来绘制了。
}
}

绘制图形路径,首先
先创建一个路径(Path)->
把路径移动到图形的左上角->

绘制预备周期:

预备周期的第一条曲线->
预备周期的第一条曲线->

绘制正式周期:

for循环周期数,一共要绘制多少个周期通过cyclerCount控制,绘制周期的时候加上偏移量
(偏移量是控制图形移动的关键)

接下来用同样的方法绘制第二层,第三层路径.
然后递增每一个图形的偏移量,为下一次的绘制做准备
然后调用绘制方法draw();

绘制方法draw()的代码如下:

private void draw(){
try {
mCanvas = mHolder.lockCanvas();//获取画板,

mCanvas.drawColor(Color.WHITE);//清屏

mCanvas.drawRect(0, 0, getWidth(), getHeight(), mBgPaint);//绘制控件的底色


mPaint.setColor(xmlWave1Color);//开始绘制第一层图形,设置画笔颜色为第一层的颜色
mCanvas.drawPath(mPath, mPaint);//绘制第一层图形路径
mPaint.setColor(xmlWave2Color);//开始绘制第二层图形,设置画笔颜色为第二层的颜色
mCanvas.drawPath(mPath1 , mPaint);//绘制第二层图形路径
mPaint.setColor(xmlWave3Color);//开始绘制第三层图形,设置画笔颜色为第三层的颜色
mCanvas.drawPath(mPath2 , mPaint);//绘制第三层图形路径


//判移动移量是否已经超过预备周期的宽度,是的话把偏移量重置为0
if (offset > cycleWidth) {
offset = 0;
}
if (offset1 > cycleWidth) {
offset1 = 0;
}
if (offset2 > cycleWidth) {
offset2 = 0;
}
}catch (Exception e){
}finally {
/*mCanvas != null 是因为在华为4.4上,跳转Activity后,mCanvas会变成空,返回的时候有可能报空指针异常*/
if(mIsDrawing && mCanvas != null){
mHolder.unlockCanvasAndPost(mCanvas);//解锁,把绘制的内容提交到屏幕上
}
}

}

holder.lockCanvas方法返回一个画板,显示的内容最终就要绘制到该画板上,并提交给主线程显示.
然后需要先把上次画板上绘制的内容清除掉,所以这里直接给画板绘制一个铺满整个画板的颜色,这样来覆盖掉原来的内容;
然后按照层叠顺序,先绘制底色,再绘制后面的三个波浪图形.
最后的finally,确保绘制的内容都会执行提交,注意,在华为4.4上,跳转Activity后,mCanvas会变成空,这里要加个判断.

提交以后,绘制的内容就会显示到屏幕上啦.由于run()方法中执行的是 while(mIsDrawing),所以在mIsDrawing等于false之前,他都会一直执行绘制,递增偏移量.这样就可以实现动画的效果.

最终这个控件用在了17做网店 App的登录界面上.

最后这个控件的使用方法如下:
1.先把JonsonWAveView复制到项目中,
2.在src/main/values文件夹下的attrs中添加以下自定义属性:

<declare-styleable name="JonsonWaveView">
<attr name="waveHeight" format="dimension"/><!--波形位置(水位高度)-->
<attr name="swingHeight" format="dimension"/><!--波形波动的高度(波峰到基线的高度)-->
<attr name="wave1Color" format="color"/><!--第一层形状颜色-->
<attr name="wave2Color" format="color"/><!--第二层形状颜色-->
<attr name="wave3Color" format="color"/><!--第三层形状颜色-->
<attr name="wave2Range" format="dimension"/><!--第二层和第一层的距离-->
<attr name="wave3Range" format="dimension"/><!--第三层第一层的距离-->
<attr name="startColor" format="color"/><!--背景为渐变色,渐变色的开始颜色-->
<attr name="endColor" format="color"/><!--背景为渐变色,渐变色的结束颜色-->
<attr name="waveSpeed" format="float"/><!--波形流动的速度-->
<attr name="cyclerCount" format="integer"/><!--控件显示多少个周期-->
</declare-styleable>

这样在xml中的根标签添加xmlns:jonson_waveView="http://schemas.android.com/apk/res-auto"就可以使用这些属性了.

后面,贴上整个java文件的代码


public class JonsonWaveView extends SurfaceView implements SurfaceHolder.Callback , Runnable {

private SurfaceHolder mHolder;
private boolean mIsDrawing = false;
private int width;
private int height;
private float mDensity;
private Paint mPaint;
private Canvas mCanvas;
private int layerRange1;
private int layerRange2;
private int layerRange3;
private Paint mBgPaint;
private float xmlWaveHeightIndex;
private float xmlSwingHeight;
private int xmlBgStartColor;
private int xmlBgEndColor;
private float xmlWaveSpeed;
private int xmlWave1Color;
private int xmlWave2Color;
private int xmlWave3Color;
private float xmlWave2Range;
private float xmlWave3Range;
private int cyclerCount;

private int offset = 0;
private int offset1 = 0;
private int offset2 = 0;

int swingHeight;
int cycleWidth;
int besselWidth;

Path mPath;
Path mPath1;
Path mPath2;


public JonsonWaveView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context , attrs);
}

public JonsonWaveView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context , attrs);
}

public JonsonWaveView(Context context) {
super(context);
}

private void initView(Context context , AttributeSet attrs){

WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics displayMetrics = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(displayMetrics);
mDensity = displayMetrics.density;

//获取xml文件中填写的属性
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.JonsonWaveView);
xmlWaveHeightIndex = typedArray.getDimension(R.styleable.JonsonWaveView_waveHeight , 300f);//波形位置(水位高度)
xmlSwingHeight = typedArray.getDimension(R.styleable.JonsonWaveView_swingHeight, 10f);//波形波动的高度(波峰到基线的高度)
xmlWave1Color = typedArray.getColor(R.styleable.JonsonWaveView_wave1Color , Color.parseColor("#BFE5FF"));//第一层形状颜色
xmlWave2Color = typedArray.getColor(R.styleable.JonsonWaveView_wave2Color , Color.parseColor("#7AE5FF"));//第二层形状颜色
xmlWave3Color = typedArray.getColor(R.styleable.JonsonWaveView_wave3Color , Color.parseColor("#0CE5FF"));//第三层形状颜色
xmlWave2Range = typedArray.getFloat(R.styleable.JonsonWaveView_wave3Range , 15f);//第二层和第一层的距离
xmlWave3Range = typedArray.getFloat(R.styleable.JonsonWaveView_wave2Range , 20f);//第三层第一层的距离
xmlBgStartColor = typedArray.getColor(R.styleable.JonsonWaveView_startColor, Color.WHITE);//背景为渐变色,渐变色的开始颜色
xmlBgEndColor = typedArray.getColor(R.styleable.JonsonWaveView_endColor, Color.parseColor("#C8E5FF"));//背景为渐变色,渐变色的结束颜色
xmlWaveSpeed = typedArray.getFloat(R.styleable.JonsonWaveView_waveSpeed, 1.5f);//波形流动的速度
cyclerCount = typedArray.getInteger(R.styleable.JonsonWaveView_cyclerCount, 2);//控件显示多少个周期

mHolder = getHolder();
mHolder.addCallback(this);//获取SurfaceHolder并添加回调

//初始化波浪画笔
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(xmlWave1Color);
mPaint.setStyle(Paint.Style.FILL);

//初始化背景画笔
mBgPaint = new Paint();
mBgPaint.setStyle(Paint.Style.FILL);
}

@Override
public void surfaceCreated(SurfaceHolder holder) {
//是否可以绘制的标志
mIsDrawing = true;

//获取控件宽高
width = getWidth();
height = getHeight();


//初始化渐变,给背景色画笔设置渐变
LinearGradient linearGradient = new LinearGradient(0, 0, 0, getHeight() , new int[] {xmlBgStartColor, xmlBgEndColor}, null, Shader.TileMode.MIRROR);
mBgPaint.setShader(linearGradient);

//第一层波形底部高度
float shape1Top = getHeight() - xmlWaveHeightIndex;//第一层形状的top位置

swingHeight = (int) (xmlSwingHeight * mDensity);//波动高度(波峰到基线高度)
layerRange1 = (int) (shape1Top + xmlSwingHeight);//第一层图形贝塞尔曲线基线高度
layerRange2 = (int) (xmlWave2Range * mDensity + layerRange1);//第二层图形贝塞尔曲线基线高度
layerRange3 = (int) (xmlWave3Range * mDensity + layerRange1);//第三层图形贝塞尔曲线基线高度

cycleWidth = width / cyclerCount;//一个波动周期宽度
besselWidth = cycleWidth / 2;//半个波动周期宽度(一个2阶贝塞尔曲线的宽度)

new Thread(this).start();//执行子线程中的任务
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mIsDrawing = false;
}

@Override
public void run() {
while(mIsDrawing){

//第一层图形路径
mPath = new Path();
mPath.moveTo(-cycleWidth + offset , layerRange1);//路径移动起始点到负一个周期的位置,加上当前时间的图形移动距离

mPath.quadTo(-cycleWidth + besselWidth / 2 + offset , layerRange1 + swingHeight , -cycleWidth / 2 + offset , layerRange1);
mPath.quadTo(-cycleWidth / 2 + besselWidth /2 + offset , layerRange1 - swingHeight , offset , layerRange1);

for (int i = 0; i < cyclerCount; i++) {
//quadTo
//参数1:贝塞尔曲线控制点X坐标
//参数2:贝塞尔曲线控制点Y坐标
//参数3:贝塞尔曲线终点X坐标
//参数4:贝塞尔曲线终点y坐标
float controlX = (i * cycleWidth) + (besselWidth / 2) + offset;
float controlY = layerRange1 + swingHeight;
float endX = (cycleWidth * i) + besselWidth + offset;
float endY = layerRange1;

mPath.quadTo(controlX , controlY , endX , endY);

float controlX1 = endX + (besselWidth/2);
float controlY1 = layerRange1 - swingHeight;
float endX1 = endX + besselWidth;
float endY1 = endY;
mPath.quadTo(controlX1 , controlY1 , endX1 , endY1);
if(i == cyclerCount - 1){
mPath.lineTo(width , height);//路径-右边竖直线
mPath.lineTo(-cycleWidth , height);//路径-底部横直线
mPath.close();//路径-封闭
}
}


//第二层图形路径
mPath1 = new Path();
mPath1.moveTo(-cycleWidth + offset1 , layerRange2);//路径移动起始点到负一个周期的位置,加上当前时间的图形移动距离

mPath1.quadTo(-cycleWidth + besselWidth / 2 + offset1 , layerRange2 + swingHeight , -cycleWidth / 2 + offset1 , layerRange2);
mPath1.quadTo(-cycleWidth / 2 + besselWidth /2 + offset1 , layerRange2 - swingHeight , offset1 , layerRange2);

for (int i = 0; i < cyclerCount; i++) {
float controlX = (i * cycleWidth) + (besselWidth / 2) + offset1;
float controlY = layerRange2 + swingHeight;
float endX = (cycleWidth * i) + besselWidth + offset1;
float endY = layerRange2;

mPath1.quadTo(controlX , controlY , endX , endY);

float controlX1 = endX + (besselWidth/2);
float controlY1 = layerRange2 - swingHeight;
float endX1 = endX + besselWidth;
float endY1 = endY;
mPath1.quadTo(controlX1 , controlY1 , endX1 , endY1);
if(i == cyclerCount - 1){
mPath1.lineTo(width , height);//路径-右边竖直线
mPath1.lineTo(-cycleWidth , height);//路径-底部横直线
mPath1.close();//路径-封闭
}
}

//第三层图形路径
mPath2 = new Path();
mPath2.moveTo(-cycleWidth + offset2 , layerRange3);//路径移动起始点到负一个周期的位置,加上当前时间的图形移动距离

mPath2.quadTo(-cycleWidth + besselWidth / 2 + offset2 , layerRange3 + swingHeight , -cycleWidth / 2 + offset2 , layerRange3);
mPath2.quadTo(-cycleWidth / 2 + besselWidth /2 + offset2 , layerRange3 - swingHeight , offset2 , layerRange3);

for (int i = 0; i < cyclerCount; i++) {
float controlX = (i * cycleWidth) + (besselWidth / 2) + offset2;
float controlY = layerRange3 + swingHeight;
float endX = (cycleWidth * i) + besselWidth + offset2;
float endY = layerRange3;

mPath2.quadTo(controlX , controlY , endX , endY);

float controlX1 = endX + (besselWidth/2);
float controlY1 = layerRange3 - swingHeight;
float endX1 = endX + besselWidth;
float endY1 = endY;
mPath2.quadTo(controlX1 , controlY1 , endX1 , endY1);
if(i == cyclerCount - 1){
mPath2.lineTo(width , height);//路径-右边竖直线
mPath2.lineTo(-cycleWidth , height);//路径-底部横直线
mPath2.close();//路径-封闭
}
}

//递增图形的移动量;
offset += (int) (2 * xmlWaveSpeed* mDensity);
offset1 += (int) (1 * xmlWaveSpeed * mDensity);
offset2 += (int) (1.5 * xmlWaveSpeed * mDensity);

draw();//路径创建按完成后就可以开始按照路径来绘制了。
}
}

private void draw(){
try {
mCanvas = mHolder.lockCanvas();//锁住画板,

mCanvas.drawColor(Color.WHITE);//清屏

mCanvas.drawRect(0, 0, getWidth(), getHeight(), mBgPaint);//绘制控件的底色


mPaint.setColor(xmlWave1Color);//开始绘制第一层图形,设置画笔颜色为第一层的颜色
mCanvas.drawPath(mPath, mPaint);//绘制第一层图形路径
mPaint.setColor(xmlWave2Color);//开始绘制第二层图形,设置画笔颜色为第二层的颜色
mCanvas.drawPath(mPath1 , mPaint);//绘制第二层图形路径
mPaint.setColor(xmlWave3Color);//开始绘制第三层图形,设置画笔颜色为第三层的颜色
mCanvas.drawPath(mPath2 , mPaint);//绘制第三层图形路径


//判移动移量是否已经超过预备周期的宽度,是的话把偏移量重置为0
if (offset > cycleWidth) {
offset = 0;
}
if (offset1 > cycleWidth) {
offset1 = 0;
}
if (offset2 > cycleWidth) {
offset2 = 0;
}
}catch (Exception e){
}finally {
/*mCanvas != null 是因为在华为4.4上,跳转Activity后,mCanvas会变成空,返回的时候有可能报空指针异常*/
if(mIsDrawing && mCanvas != null){
mHolder.unlockCanvasAndPost(mCanvas);//解锁,把绘制的内容提交到屏幕上
}
}

}
}