闲话少说,先上效果图:
之前公司要求在我们的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; } }