自定义surfaceView实现波浪动画效果

闲话少说,先上效果图:



之前公司要求在我们的app上添加一个水波纹动画效果,因为时间比较紧,所以当时没有研究怎么用画图的方式来实现动画,而是采用了帧动画的方式,做数张图片重复播放,但是用这种方式想要平衡内存的使用,就不能用大的图片,张数也不能太多,但是向内存妥协的问题在于会造成动画不清晰以及卡顿感非常严重,现在有了空闲的时间,我决定重新做这个动画、

本次我采用的是自定义一个surfaceView来播放这个动画,SurfaceView是视图(View)的继承类,这个视图里内嵌了一个专门用于绘制的Surface。你可以控制这个Surface的格式和尺寸,它的特性是:可以在主线程之外的线程中向屏幕绘图上。这样可以避免画图任务繁重的时候造成主线程阻塞,从而提高了程序的反应速度。在游戏开发中多用到SurfaceView,游戏中的背景、人物、动画等等尽量在画布canvas中画出。


1、实现自定义surfaceView:

首先继承SurfaceView并实现SurfaceHolder.Callback接口,使用SurfaceView 有一个原则,所有的绘图工作必须得在Surface 被创建之后才能开始(Surface—表面,这个概念在 图形编程中常常被提到。基本上我们可以把它当作显存的一个映射,写入到Surface 的内容
可以被直接复制到显存从而显示出来,这使得显示速度会非常快),而在Surface 被销毁之前必须结束。所以Callback 中的surfaceCreated 和surfaceDestroyed 就成了绘图处理代码的边界。

需要重写的方法

 (1)public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}

     //在surface的大小发生改变时激发

 (2)public void surfaceCreated(SurfaceHolder holder){}

     //在创建时激发,一般在这里调用画图的线程。

 (3)public void surfaceDestroyed(SurfaceHolder holder) {}

     //销毁时激发,一般在这里将画图的线程停止、释放。

SurfaceHolder
这里用到了一个类SurfaceHolder,可以把它当成surface的控制器,用来操纵surface。处理它的Canvas上画的效果和动画,控制表面,大小,像素等。
几个需要注意的方法:
(1)、abstract void addCallback(SurfaceHolder.Callback callback);
// 给SurfaceView当前的持有者一个回调对象。
(2)、abstract Canvas lockCanvas();
// 锁定画布,一般在锁定后就可以通过其返回的画布对象Canvas,在其上面画图等操作了。
(3)、abstract Canvas lockCanvas(Rect dirty);
// 锁定画布的某个区域进行画图等..因为画完图后,会调用下面的unlockCanvasAndPost来改变显示内容。
// 相对部分内存要求比较高的游戏来说,可以不用重画dirty外的其它区域的像素,可以提高速度。
(4)、abstract void unlockCanvasAndPost(Canvas canvas);
// 结束锁定画图,并提交改变。

2、用surface实现波浪动画:

这里实现上面的动画用到的其实是我们的基础知识,那就是高中学的正弦函数!两条相位不懂其它参数相同的正弦曲线滚动播放,就能实现

这个动画效果,这里主要是通过设置函数y=a*sin(bx+c)+d的a,b,c,d四个参数,实现上面的各种不同效果,不记得正弦函数图像的同学请去查看

百度百科。具体的实现代码我写在下面了,原理应该也写的很清楚了。

需要注意的是,surfaceView的控制方式其实就是控制自定义view里面的线程,想要播放动画,就让这条线程一直运行,不想播放了,想

暂停,或者停止,需要把线程中的标志位改变,终止这个线程,要重新播放动画,只需要把标志位重置,然后重新开一条新线程就可以继续

播放了,因为surfaceView是在子线程更新ui,绘制图片,因此可以不需要担心会阻塞主线程导致app卡顿。

波浪自定义surfaceView代码如下:

/**
 * @author Sentox
 *         Created on 2016/3/18.
 *         描述:自定义波浪控件
 */

@EView
public class WaterWaveView extends SurfaceView implements SurfaceHolder.Callback {

    private SurfaceHolder holder;
    private RenderThread renderThread;
    private boolean isDraw = false;// 控制绘制的开关

    private Paint paint;//画笔
    private static final int WAVE_PAINT_COLOR = Color.BLUE;// 波纹颜色
    private static final int INVALIDATE_DURATION = 20; //每次刷新的时间间隔

    private float scrollSpeed = SCROLL_NORMAL;//波纹滚动速度
    private float secondWaveValue = 30;//第二条波浪的偏位移量
    /** 快——波纹滚动速度**/
    public static final float SCROLL_FAST = 0.5f;
    /** 普通——波纹滚动速度**/
    public static final float SCROLL_NORMAL = 0.3f;
    /** 慢——波纹滚动速度**/
    public static final float SCROLL_SLOW = 0.1f;


    private int drawStyle = DRAW_STYLE_WAVE_DOUBLE;//动画样式
    /** 正弦函数波浪线**/
    public static final int DRAW_STYLE_LINE_SINX = 1;
    /** 单条波纹**/
    public static final int DRAW_STYLE_WAVE_SINGLE = 2;
    /**两条波纹**/
    public static final int DRAW_STYLE_WAVE_DOUBLE = 3;

    private int amplitudeStyle = WAVE_MIN;//波动幅度大小
    /** 波浪幅度——大**/
    public static final int WAVE_MAX = 10;
    /** 波浪幅度——小**/
    public static final int WAVE_MIN = 20;


    private int viewHeight;//View高度
    private int viewWidth;//View宽度

    //画笔点数据
    private float a_amplitude;//振幅
    private float b_period;//周期的参数,详细计算见上方说明
    private float c_phase;//初相
    private float d_offect;//图像离x轴的高度(即在y轴的高度)

    public WaterWaveView(Context context, AttributeSet attrs) {
        super(context, attrs);
        holder = this.getHolder();
        holder.addCallback(this);
        initPaint();
        renderThread = new RenderThread();
    }

    public WaterWaveView(Context context) {
        super(context);
        holder = this.getHolder();
        holder.addCallback(this);
        initPaint();
        renderThread = new RenderThread();
    }

    public void initPaint() {
        paint = new Paint();
        //颜色
        paint.setColor(WAVE_PAINT_COLOR);
        //抗锯齿
        paint.setAntiAlias(true);
        //设置风格为实线
        paint.setStyle(Paint.Style.FILL);
        //设置透明度
        paint.setAlpha(80);
    }


    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        //surface被创建,开始播放动画
        isDraw = true;
        renderThread.start();
    }

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

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        //surface被销毁
        isDraw = false;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        viewHeight = h;
        viewWidth = w;
        DefineFunction();
    }

    /**
     * 水波纹由正弦函数画出,y = a*sin(b*x+c)+d
     * a是振幅,决定y的波动范围
     * b决定周期,T=2*PI/b,我们需要一个周期填满view的宽,因此 b=2*PI/viewWidth
     * c决定初相,即函数一个周期的图像开始点在哪里,为正则图像左移,为负则图像右移
     * d决定整个图像在y轴的高度
     **/
    private float getBaseYFromSinX(float x) {
        return (float) (a_amplitude * Math.sin(b_period * x + c_phase) + d_offect);
    }

    /**获取带有偏位移量的正弦值,用于在同一时间轴画第二条正弦曲线,位移量为value**/
    private float getCustomYFromSinX(float x,float value) {
        return (float) (a_amplitude * Math.sin(b_period * x + c_phase+value) + d_offect);
    }

    //定义初始正弦函数
    private void DefineFunction() {
        if(amplitudeStyle!=0){
            a_amplitude = viewHeight / amplitudeStyle;//振幅
        }else{
            a_amplitude = viewHeight /WAVE_MIN;
        }
        b_period =  (float)(2 * Math.PI / viewWidth);//周期的参数,详细计算见上方说明
        c_phase = 0;//初相
        d_offect = (3 * viewHeight / 5);//图像离x轴的高度(即在y轴的高度)
        secondWaveValue = viewWidth/15;//第二波浪的偏位移量
    }

    private class RenderThread extends Thread {
        @Override
        public void run() {
            // 不停绘制界面
            while (isDraw) {
                drawUI();
                try {
                    sleep(INVALIDATE_DURATION);//延时20毫秒再画下一帧
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            super.run();
        }
    }

    /**
     * 界面绘制
     */
    private void drawUI() {
        Canvas canvas = holder.lockCanvas();
        //设置画布抗锯齿
        canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));
        canvas.drawColor(Color.WHITE);
        try {
            //绘制图形
          synchronized (this) {
              switch (drawStyle) {
                  case DRAW_STYLE_LINE_SINX:
                      startDrawSinX(canvas);
                      break;
                  case DRAW_STYLE_WAVE_SINGLE:
                      startDrawSingleWave(canvas);
                      break;
                  case DRAW_STYLE_WAVE_DOUBLE:
                      startDrawDoubleWave(canvas);
                      break;
                  default:
                      startDrawDoubleWave(canvas);
              }
          }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            holder.unlockCanvasAndPost(canvas);
        }
    }

    /**
     * 绘制正弦曲线
     */
    private void startDrawSinX(Canvas canvas) {
        float x = 0;
        for (; x <= viewWidth;) {
            canvas.drawPoint(x,getBaseYFromSinX(x),paint);
            x=x+scrollSpeed;
        }
        if (c_phase < viewWidth) {
            c_phase = c_phase+scrollSpeed;
        } else {
            c_phase = 0;
        }
    }

    /**
     * 绘制单条波纹
     */
    private void startDrawSingleWave(Canvas canvas) {
        float x = 0;
        for (; x <= viewWidth;) {
            canvas.drawLine(x, viewHeight, x, getBaseYFromSinX(x), paint);
            x=x+scrollSpeed;
        }
        if (c_phase < viewWidth) {
            c_phase = c_phase+scrollSpeed;
        } else {
            c_phase = 0;
        }
    }

    /**
     * 绘制两条波纹
     */
    private void startDrawDoubleWave(Canvas canvas) {
        float x = 0;
        for (; x <= viewWidth;) {
            canvas.drawLine(x, viewHeight, x, getBaseYFromSinX(x),paint);
            canvas.drawLine(x, viewHeight, x, getCustomYFromSinX(x, secondWaveValue), paint);
            x=x+scrollSpeed;
        }
        if (c_phase < viewWidth) {
            c_phase = c_phase+scrollSpeed;
        } else {
            c_phase = 0;
        }
    }

    /**
     * 重新开始动画
     */
    public void startWave() {
        if (!isDraw) {
            isDraw = true;
            renderThread = new RenderThread();
            renderThread.start();
        }
    }

    /**
     * 停止动画
     */
    public void stopWave() {
        isDraw = false;
    }

    /**
     * 获得画笔
     */
    public Paint getPaint() {
        return paint;
    }

    /**
     * 设置画笔
     */
    public void setPaint(Paint paint) {
        this.paint = paint;
    }

    /**
     * 设置波纹样式
     */
    public void setDrawStyle(int style) {
        drawStyle = style;
        if(isDraw){
            DefineFunction();
        }
    }

    /**
     * 设置波纹振幅(注意,不能为0)
     */
    public void setAmplitudeStyle(int style){
        amplitudeStyle = style;
        if(amplitudeStyle!=0){
            a_amplitude = viewHeight / amplitudeStyle;//振幅
        }else{
            a_amplitude = viewHeight /WAVE_MIN;
        }
    }

    /**
     * 设置波纹速度
     */
    public void setSpeed(float speed){
        scrollSpeed = speed;
    }
}


你可能感兴趣的:(android,动画,canvas,SurfaceView,界面)