这一次要绘制出波浪效果,也是小白的我第一次还望轻喷。首先当然是展示效果图啦:
一.首先来说说实现思路。
想到波浪效果,当然我第一反应是用正余弦波来设计啦(也能通过贝塞尔曲线,这里我不提及这个方法但是在demo里这种方法也实现了),肯定要绘制一个静态的波,然后通过不断的对它平移刷新,这样最简单的波浪效果就有了,如果再给它加一个比它提前一定周期的波一起平移,那不是波浪效果的层次就有了。
二.绘制。
首先要绘制一个静态的波形图,嗨呀说来简单但是怎么画呢,不要慌先看下面这张丑图:
通过上面的图我们发现曲线被切分成了无数的竖线,我们可以知道波浪的高低,就是波峰与波谷,根据函数公式,通过不断的绘制竖线,就能得到一个静态波形图。代码如下:
1 //在宽度以内绘制一条条竖线 2 while (drawPoint.x < mWidth) { 3 //第一条波的y坐标 4 drawPoint.y = (float) (waveHeight - waveDeep * Math.sin(drawPoint.x * anglenum)); 5 canvas.drawLine(drawPoint.x, drawPoint.y, drawPoint.x, mHeight, mPaint); 6 //跳到下一个点继续 7 drawPoint.x++; 8 }
这里我们要注意绘制的默认坐标系如下图:
画出静态的波形图之后,我们只要每次让这个波向前或者向后移动一定周期再不断刷新,就能出现波浪效果了。重写view的ondraw方法就有如下:
1 drawPoint.x = 0;//重置为0,从原点开始绘制 2 Double rightperiod = Math.PI / 8 * count;//每次平移Math.PI/8个周期 3 if (count == 16) {//每次平移Math.PI/8个周期,平移第16次,平移了一个完整的周期 4 count = 0;//平移了一个完整周期归零重新开始计数 5 } else { 6 count++; 7 } 8 9 //在宽度以内绘制一条条竖线 10 while (drawPoint.x < mWidth) { 11 //第一条波的y坐标 12 drawPoint.y = (float) (waveHeight - waveDeep * Math.sin(drawPoint.x * anglenum - rightperiod)); 13 //绘制最上面显示主波的竖线 14 canvas.drawLine(drawPoint.x, drawPoint.y, drawPoint.x, mHeight, mPaint); 15 //跳到下一个点继续 16 drawPoint.x++; 17 } 18 //定时更新 19 postInvalidateDelayed(17);
这样一条会动的波浪就基本完成了,主体功能基本实现,之后再另外画一个平移一定周期的波浪并且通过用属性动画ValueAnimator对波浪波峰波谷与水位变化的调控就能达到上面的效果。下面是完整的代码:
1 public class WaveFunctionView extends View { 2 private Path mPath;//路径 3 private Paint mPaint, mPaintMore;//画笔 4 private PointF drawPoint, drawPoint2;//绘制点 5 private ValueAnimator animator, animatorh; 6 private float mWidth, mHeight, waveHeight;//控件宽,控件高,水位 7 private float waveDeepmin = 8f;//最小的波峰与波谷 8 private float waveDeepMax = 20f;//最大的波峰与波谷 9 private float waveDeep = 8f;//波峰与波谷 10 private float arcRa = 0;//圆半径 11 private Boolean iscircle = true;//是否是圆形图案 12 private Boolean antiAlias = true;//是否开启抗锯齿 13 public String MAINCOLOR_DEF = "#0000AA", NEXTCOLOR_DEF = "#0000FF";//默认颜色 14 private int mainColor = Color.parseColor(MAINCOLOR_DEF), nextColor = Color.parseColor(NEXTCOLOR_DEF);//颜色 15 private Double anglenum = Math.PI / 180; 16 private int count = 0;//绘制次数 17 18 public WaveFunctionView(Context context) { 19 super(context); 20 init(); 21 } 22 23 public WaveFunctionView(Context context, AttributeSet attrs, int defStyleAttr) { 24 super(context, attrs, defStyleAttr); 25 init(); 26 } 27 28 public WaveFunctionView(Context context, AttributeSet attrs) { 29 super(context, attrs); 30 init(); 31 } 32 33 @Override 34 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 35 mWidth = w;//获得控件宽度 36 mHeight = h;//获得控件高度 37 if (mWidth > mHeight) {//若要裁剪为圆形,以最短的长度为直径 38 arcRa = mHeight / 2; 39 if (iscircle) { 40 mWidth = mHeight; 41 } 42 } else { 43 arcRa = mWidth / 2; 44 if (iscircle) { 45 mHeight = mWidth; 46 } 47 } 48 waveHeight = mHeight;//初始化开始水位 49 ChangeWaveLevel(5); 50 super.onSizeChanged(w, h, oldw, oldh); 51 } 52 53 //是否是圆形 54 public void isCircle(Boolean iscircle) { 55 this.iscircle = iscircle; 56 } 57 58 //是否开启抗锯齿 59 public void setAntiAlias(Boolean antiAlias) { 60 this.antiAlias = antiAlias; 61 mPaint.setAntiAlias(antiAlias); 62 mPaintMore.setAntiAlias(antiAlias); 63 } 64 65 //设置主波颜色 66 public void setMainWaveColor(int color) { 67 mainColor = color; 68 mPaint.setColor(color); 69 } 70 71 //设置被遮挡的副波颜色 72 public void setSecondaryWaveColor(int color) { 73 nextColor = color; 74 mPaintMore.setColor(color); 75 } 76 77 @Override 78 protected void onDraw(Canvas canvas) { 79 // TODO Auto-generated method stub 80 super.onDraw(canvas); 81 if (iscircle) {//判断是否定义为圆形 82 mPath.reset();//重置路径 83 mPath.addCircle(arcRa, arcRa, arcRa, Path.Direction.CW);//画以(arcRa,arcRa),半径为arcRa的顺时针的圆 84 canvas.clipPath(mPath);//裁剪 85 } 86 drawPoint.x = 0;//重置为0,从原点开始绘制 87 Double rightperiod = Math.PI / 8 * count;//每次平移Math.PI/8个周期 88 if (count == 16) {//每次平移Math.PI/8个周期,平移第16次,平移了一个完整的周期 89 count = 0;//平移了一个完整周期归零重新开始计数 90 } else { 91 count++; 92 } 93 94 //在宽度以内绘制一条条竖线 95 while (drawPoint.x < mWidth) { 96 //第一条波的y坐标 97 drawPoint.y = (float) (waveHeight - waveDeep * Math.sin(drawPoint.x * anglenum - rightperiod)); 98 //第二条波的y坐标,比第一条向右移动了Math.PI/2个周期 99 drawPoint2.y = (float) (waveHeight - waveDeep * Math.sin(drawPoint.x * anglenum - rightperiod - Math.PI / 2)); 100 //先绘制被遮挡的副波的竖线 101 canvas.drawLine(drawPoint.x, drawPoint2.y, drawPoint.x, mHeight, mPaintMore); 102 //绘制最上面显示主波的竖线 103 canvas.drawLine(drawPoint.x, drawPoint.y, drawPoint.x, mHeight, mPaint); 104 //跳到下一个点继续 105 drawPoint.x++; 106 } 107 //定时更新 108 postInvalidateDelayed(17); 109 } 110 111 private void init() { 112 mPath = new Path(); 113 mPaint = new Paint(); 114 mPaint.setColor(mainColor);//设置颜色 115 mPaint.setAntiAlias(antiAlias);//抗锯齿(性能影响) 116 mPaint.setStyle(Paint.Style.FILL); 117 mPaint.setAlpha(50); 118 mPaintMore = new Paint(); 119 mPaintMore.setAntiAlias(antiAlias);//抗锯齿 120 mPaintMore.setStyle(Paint.Style.FILL); 121 mPaintMore.setColor(nextColor);//设置颜色 122 mPaintMore.setAlpha(30); 123 drawPoint = new PointF(0, 0); 124 drawPoint2 = new PointF(0, 0); 125 } 126 127 public void ChangeWaveLevel(int percent) { 128 animator = ValueAnimator.ofFloat(waveDeepmin, waveDeepMax);//设置属性值变化范围,最大波峰波谷与最小 129 animator.setDuration(1000);//设置动画时间 130 animator.setInterpolator(new LinearInterpolator());//控制动画的变化速率 131 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 132 @Override 133 public void onAnimationUpdate(ValueAnimator animation) { 134 waveDeep = (float) animation.getAnimatedValue(); 135 } 136 }); 137 animator.setRepeatMode(ValueAnimator.REVERSE);//往返模式 138 animator.setRepeatCount(1); 139 animatorh = ValueAnimator.ofFloat(waveHeight, mHeight * (10 - percent) / 10);//水位变化 140 animatorh.setDuration(2000);//设置动画时间 141 animatorh.setInterpolator(new DecelerateInterpolator());//控制动画的变化速率 142 143 animatorh.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 144 @Override 145 public void onAnimationUpdate(ValueAnimator animation) { 146 waveHeight = (float) animation.getAnimatedValue(); 147 } 148 }); 149 animator.start();//开始动画 150 animatorh.start();//开始动画 151 } 152 }
GitHub:https://github.com/SteinsGateZero/Mybeisaierwavetest.git
虽然简单,但是推荐还是得自己动手做一做。