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



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

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

(图1)

图1

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

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

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

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

(图2)

图2

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

(图3)

这里写图片描述

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

这里写图片描述

二阶贝塞尔曲线:

一个二阶贝塞尔曲线需要一个开始点,控制点,结束点,这三点,对应到图上就是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%时候的情况).
这里写图片描述

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

具体计算公式和详细说明可参考 http://www.cnblogs.com/hnfxs/p/3148483.html
这里写图片描述



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

(图4)

图4

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

(图5)

图5

接下来看代码

首先,创建一个类,继承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);//解锁,把绘制的内容提交到屏幕上
            }
        }

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值