Android自定义控件练手——波浪效果

Posted steinsgatezero

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android自定义控件练手——波浪效果相关的知识,希望对你有一定的参考价值。

    这一次要绘制出波浪效果,也是小白的我第一次还望轻喷。首先当然是展示效果图啦:

技术分享图片

    一.首先来说说实现思路。

    想到波浪效果,当然我第一反应是用正余弦波来设计啦(也能通过贝塞尔曲线,这里我不提及这个方法但是在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

    虽然简单,但是推荐还是得自己动手做一做。

 

以上是关于Android自定义控件练手——波浪效果的主要内容,如果未能解决你的问题,请参考以下文章

Android自定义控件实战——滚动选择器PickerView

Android 自定义控件demo集合

Android 自定义控件demo集合

怎么为android控件边缘添加阴影

Android 控件进阶修炼-仿360手机卫士波浪球进度控件

Android 自定义录音播放动画View,让你的录音浪起来