这一次要绘制出波浪效果,也是小白的我第一次还望轻喷。首先当然是展示效果图啦:
一.首先来说说实现思路。
想到波浪效果,当然我第一反应是用正余弦波来设计啦(也能通过贝塞尔曲线,这里我不提及这个方法但是在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对波浪波峰波谷与水位变化的调控就能达到上面的效果。下面是完整的代码:
public class WaveFunctionView extends View {
private Path mPath;//路径
private Paint mPaint, mPaintMore;//画笔
private PointF drawPoint, drawPoint2;//绘制点
private ValueAnimator animator, animatorh;
private float mWidth, mHeight, waveHeight;//控件宽,控件高,水位
private float waveDeepmin = 8f;//最小的波峰与波谷
private float waveDeepMax = 20f;//最大的波峰与波谷
private float waveDeep = 8f;//波峰与波谷
private float arcRa = 0;//圆半径
private Boolean iscircle = true;//是否是圆形图案
private Boolean antiAlias = true;//是否开启抗锯齿
public String MAINCOLOR_DEF = "#0000AA", NEXTCOLOR_DEF = "#0000FF";//默认颜色
private int mainColor = Color.parseColor(MAINCOLOR_DEF), nextColor = Color.parseColor(NEXTCOLOR_DEF);//颜色
private Double anglenum = Math.PI / 180;
private int count = 0;//绘制次数
public WaveFunctionView(Context context) {
super(context);
init();
}
public WaveFunctionView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public WaveFunctionView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mWidth = w;//获得控件宽度
mHeight = h;//获得控件高度
if (mWidth > mHeight) {//若要裁剪为圆形,以最短的长度为直径
arcRa = mHeight / 2;
if (iscircle) {
mWidth = mHeight;
}
} else {
arcRa = mWidth / 2;
if (iscircle) {
mHeight = mWidth;
}
}
waveHeight = mHeight;//初始化开始水位
ChangeWaveLevel(5);
super.onSizeChanged(w, h, oldw, oldh);
}
//是否是圆形
public void isCircle(Boolean iscircle) {
this.iscircle = iscircle;
}
//是否开启抗锯齿
public void setAntiAlias(Boolean antiAlias) {
this.antiAlias = antiAlias;
mPaint.setAntiAlias(antiAlias);
mPaintMore.setAntiAlias(antiAlias);
}
//设置主波颜色
public void setMainWaveColor(int color) {
mainColor = color;
mPaint.setColor(color);
}
//设置被遮挡的副波颜色
public void setSecondaryWaveColor(int color) {
nextColor = color;
mPaintMore.setColor(color);
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
if (iscircle) {//判断是否定义为圆形
mPath.reset();//重置路径
mPath.addCircle(arcRa, arcRa, arcRa, Path.Direction.CW);//画以(arcRa,arcRa),半径为arcRa的顺时针的圆
canvas.clipPath(mPath);//裁剪
}
drawPoint.x = 0;//重置为0,从原点开始绘制
Double rightperiod = Math.PI / 8 * count;//每次平移Math.PI/8个周期
if (count == 16) {//每次平移Math.PI/8个周期,平移第16次,平移了一个完整的周期
count = 0;//平移了一个完整周期归零重新开始计数
} else {
count++;
}
//在宽度以内绘制一条条竖线
while (drawPoint.x < mWidth) {
//第一条波的y坐标
drawPoint.y = (float) (waveHeight - waveDeep * Math.sin(drawPoint.x * anglenum - rightperiod));
//第二条波的y坐标,比第一条向右移动了Math.PI/2个周期
drawPoint2.y = (float) (waveHeight - waveDeep * Math.sin(drawPoint.x * anglenum - rightperiod - Math.PI / 2));
//先绘制被遮挡的副波的竖线
canvas.drawLine(drawPoint.x, drawPoint2.y, drawPoint.x, mHeight, mPaintMore);
//绘制最上面显示主波的竖线
canvas.drawLine(drawPoint.x, drawPoint.y, drawPoint.x, mHeight, mPaint);
//跳到下一个点继续
drawPoint.x++;
}
//定时更新
postInvalidateDelayed(17);
}
private void init() {
mPath = new Path();
mPaint = new Paint();
mPaint.setColor(mainColor);//设置颜色
mPaint.setAntiAlias(antiAlias);//抗锯齿(性能影响)
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAlpha(50);
mPaintMore = new Paint();
mPaintMore.setAntiAlias(antiAlias);//抗锯齿
mPaintMore.setStyle(Paint.Style.FILL);
mPaintMore.setColor(nextColor);//设置颜色
mPaintMore.setAlpha(30);
drawPoint = new PointF(0, 0);
drawPoint2 = new PointF(0, 0);
}
public void ChangeWaveLevel(int percent) {
animator = ValueAnimator.ofFloat(waveDeepmin, waveDeepMax);//设置属性值变化范围,最大波峰波谷与最小
animator.setDuration(1000);//设置动画时间
animator.setInterpolator(new LinearInterpolator());//控制动画的变化速率
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
waveDeep = (float) animation.getAnimatedValue();
}
});
animator.setRepeatMode(ValueAnimator.REVERSE);//往返模式
animator.setRepeatCount(1);
animatorh = ValueAnimator.ofFloat(waveHeight, mHeight * (10 - percent) / 10);//水位变化
animatorh.setDuration(2000);//设置动画时间
animatorh.setInterpolator(new DecelerateInterpolator());//控制动画的变化速率
animatorh.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
waveHeight = (float) animation.getAnimatedValue();
}
});
animator.start();//开始动画
animatorh.start();//开始动画
}
}
GitHub:https://github.com/SteinsGateZero/Mybeisaierwavetest.git
虽然简单,但是推荐还是得自己动手做一做。