转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38556891

水流波动的波形都是三角波,曲线是正余弦曲线,但是Android中没有提供绘制正余弦曲线的API,好在Path类有个绘制贝塞尔曲线的方法quadTo,绘制出来的是2阶的贝塞尔曲线,要想实现波动效果,只能用它来绘制Path曲线。待会儿再讲解2阶的贝塞尔曲线是怎么回事,先来看实现的效果:

这个波长比较短,还看不到起伏,只是荡漾,把波长拉长再看一下:

已经可以看到起伏很明显了,再拉长看一下:

这个的起伏感就比较强了。利用这个波动效果,可以用在绘制水位线的时候使用到,还可以做一个波动的进度条WaveUpProgress,比如这样:

是不是很动感?

那这样的波动效果是怎么做的呢?前面讲到的贝塞尔曲线到底是什么呢?下面一一讲解。想要用好贝塞尔曲线就得先理解它的表达式,为了形象描述,我从网上盗了些动图。

首先看1阶贝塞尔曲线的表达式:

随着t的变化,它实际是一条P0到P1的直线段:

Android中Path的quadTo是3点的2阶贝塞尔曲线,那么2阶的表达式是这样的:

看起来很复杂,我把它拆分开来看:

然后再合并成这样:

看到什么了吧?如果看不出来再替换成这样:

B0和B1分别是P0到P1和P1到P2的1阶贝塞尔曲线。而2阶贝塞尔曲线B就是B0到B1的1阶贝塞尔曲线。显然,它的动态图表示出来就不难理解了:

红色点的运动轨迹就是B的轨迹,这就是2阶贝塞尔曲线了。当P1位于P0和P2的垂直平分线上时,B就是开口向上或向下的抛物线了。而在WaveView中就是用的开口向上和向下的抛物线模拟水波。在Android里用Path的方法,首先path.moveTo(P0),然后path.quadTo(P1, P2),canvas.drawPath(path, paint)曲线就出来了,如果想要绘制多个贝塞尔曲线就不断的quadTo吧。

讲完贝塞尔曲线后就要开始讲水波动的效果是怎么来的了,首先要理解,机械波的传输就是通过介质的震动把波形往传输方向平移,每震动一个周期波形刚好平移一个波长,所有介质点又回到一个周期前的状态。所以要实现水波动效果只需要把波形平移就可以了。

那么WaveView的实现原理是这样的:

首先在View上根据View宽计算可以容纳几个完整波形,不够一个的算一个,然后在View的不可见处预留一个完整的波形;然后波动开始的时候将所有点同时在x方向上移动相同的距离,这样隐藏的波形就会被平移出来,当平移距离达到一个波长时,这时候将所有点的x坐标又恢复到平移前的值,这样就可以一个波形一个波形地往外传输。用草图表示如下:

WaveView的原理在上图很直观的看出来了,P[2n+1],n>=0都是贝塞尔曲线的控制点,红线为水位线。

知道原理以后可以看代码了:

WaveView.java:

**[java]** [view plain](http://blog.csdn.net/zhongkejingwang/article/details/38556891#)[copy](http://blog.csdn.net/zhongkejingwang/article/details/38556891#)
  <div>
    <embed id="ZeroClipboardMovie_1" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_1">
    </embed>
  </div>
</div>
- <span class="keyword">package</span> com.jingchen.waveview;

- 
- <span class="keyword">import</span> java.util.ArrayList;

- <span class="keyword">import</span> java.util.List;

- <span class="keyword">import</span> java.util.Timer;

- <span class="keyword">import</span> java.util.TimerTask;

- 
- <span class="keyword">import</span> android.content.Context;

- <span class="keyword">import</span> android.graphics.Canvas;

- <span class="keyword">import</span> android.graphics.Color;

- <span class="keyword">import</span> android.graphics.Paint;

- <span class="keyword">import</span> android.graphics.Paint.Align;

- <span class="keyword">import</span> android.graphics.Paint.Style;

- <span class="keyword">import</span> android.graphics.Region.Op;

- <span class="keyword">import</span> android.graphics.Path;

- <span class="keyword">import</span> android.graphics.RectF;

- <span class="keyword">import</span> android.os.Handler;

- <span class="keyword">import</span> android.os.Message;

- <span class="keyword">import</span> android.util.AttributeSet;

- <span class="keyword">import</span> android.view.View;

- 
- <span class="comment">/**</span>

- <span class="comment"> * 水流波动控件</span>

- <span class="comment"> * </span>

- <span class="comment"> * @author chenjing</span>

- <span class="comment"> * </span>

- <span class="comment"> */</span>

- <span class="keyword">public</span> <span class="keyword">class</span> WaveView <span class="keyword">extends</span> View

- {

- 
- <span class="keyword">private</span> <span class="keyword">int</span> mViewWidth;

- <span class="keyword">private</span> <span class="keyword">int</span> mViewHeight;

- 
- <span class="comment">/**</span>

- <span class="comment">     * 水位线</span>

- <span class="comment">     */</span>

- <span class="keyword">private</span> <span class="keyword">float</span> mLevelLine;

- 
- <span class="comment">/**</span>

- <span class="comment">     * 波浪起伏幅度</span>

- <span class="comment">     */</span>

- <span class="keyword">private</span> <span class="keyword">float</span> mWaveHeight = <span class="number">80</span>;

- <span class="comment">/**</span>

- <span class="comment">     * 波长</span>

- <span class="comment">     */</span>

- <span class="keyword">private</span> <span class="keyword">float</span> mWaveWidth = <span class="number">200</span>;

- <span class="comment">/**</span>

- <span class="comment">     * 被隐藏的最左边的波形</span>

- <span class="comment">     */</span>

- <span class="keyword">private</span> <span class="keyword">float</span> mLeftSide;

- 
- <span class="keyword">private</span> <span class="keyword">float</span> mMoveLen;

- <span class="comment">/**</span>

- <span class="comment">     * 水波平移速度</span>

- <span class="comment">     */</span>

- <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">float</span> SPEED = <span class="number">1</span>.7f;

- 
- <span class="keyword">private</span> List<Point> mPointsList;

- <span class="keyword">private</span> Paint mPaint;

- <span class="keyword">private</span> Paint mTextPaint;

- <span class="keyword">private</span> Path mWavePath;

- <span class="keyword">private</span> <span class="keyword">boolean</span> isMeasured = <span class="keyword">false</span>;

- 
- <span class="keyword">private</span> Timer timer;

- <span class="keyword">private</span> MyTimerTask mTask;

- Handler updateHandler = <span class="keyword">new</span> Handler()

- {

- 
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> <span class="keyword">void</span> handleMessage(Message msg)

- {

- <span class="comment">// 记录平移总位移</span>

- mMoveLen += SPEED;

- <span class="comment">// 水位上升</span>

- mLevelLine -= <span class="number"></span>.1f;

- <span class="keyword">if</span> (mLevelLine < <span class="number"></span>)

- mLevelLine = <span class="number"></span>;

- mLeftSide += SPEED;

- <span class="comment">// 波形平移</span>

- <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number"></span>; i < mPointsList.size(); i++)

- {

- mPointsList.get(i).setX(mPointsList.get(i).getX() + SPEED);

- <span class="keyword">switch</span> (i % <span class="number">4</span>)

- {

- <span class="keyword">case</span> <span class="number"></span>:

- <span class="keyword">case</span> <span class="number">2</span>:

- mPointsList.get(i).setY(mLevelLine);

- <span class="keyword">break</span>;

- <span class="keyword">case</span> <span class="number">1</span>:

- mPointsList.get(i).setY(mLevelLine + mWaveHeight);

- <span class="keyword">break</span>;

- <span class="keyword">case</span> <span class="number">3</span>:

- mPointsList.get(i).setY(mLevelLine &#8211; mWaveHeight);

- <span class="keyword">break</span>;

- }

- }

- <span class="keyword">if</span> (mMoveLen >= mWaveWidth)

- {

- <span class="comment">// 波形平移超过一个完整波形后复位</span>

- mMoveLen = <span class="number"></span>;

- resetPoints();

- }

- invalidate();

- }

- 
- };

- 
- <span class="comment">/**</span>

- <span class="comment">     * 所有点的x坐标都还原到初始状态,也就是一个周期前的状态</span>

- <span class="comment">     */</span>

- <span class="keyword">private</span> <span class="keyword">void</span> resetPoints()

- {

- mLeftSide = -mWaveWidth;

- <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number"></span>; i < mPointsList.size(); i++)

- {

- mPointsList.get(i).setX(i * mWaveWidth / <span class="number">4</span> &#8211; mWaveWidth);

- }

- }

- 
- <span class="keyword">public</span> WaveView(Context context)

- {

- <span class="keyword">super</span>(context);

- init();

- }

- 
- <span class="keyword">public</span> WaveView(Context context, AttributeSet attrs)

- {

- <span class="keyword">super</span>(context, attrs);

- init();

- }

- 
- <span class="keyword">public</span> WaveView(Context context, AttributeSet attrs, <span class="keyword">int</span> defStyle)

- {

- <span class="keyword">super</span>(context, attrs, defStyle);

- init();

- }

- 
- <span class="keyword">private</span> <span class="keyword">void</span> init()

- {

- mPointsList = <span class="keyword">new</span> ArrayList<Point>();

- timer = <span class="keyword">new</span> Timer();

- 
- mPaint = <span class="keyword">new</span> Paint();

- mPaint.setAntiAlias(<span class="keyword">true</span>);

- mPaint.setStyle(Style.FILL);

- mPaint.setColor(Color.BLUE);

- 
- mTextPaint = <span class="keyword">new</span> Paint();

- mTextPaint.setColor(Color.WHITE);

- mTextPaint.setTextAlign(Align.CENTER);

- mTextPaint.setTextSize(<span class="number">30</span>);

- 
- mWavePath = <span class="keyword">new</span> Path();

- }

- 
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> <span class="keyword">void</span> onWindowFocusChanged(<span class="keyword">boolean</span> hasWindowFocus)

- {

- <span class="keyword">super</span>.onWindowFocusChanged(hasWindowFocus);

- <span class="comment">// 开始波动</span>

- start();

- }

- 
- <span class="keyword">private</span> <span class="keyword">void</span> start()

- {

- <span class="keyword">if</span> (mTask != <span class="keyword">null</span>)

- {

- mTask.cancel();

- mTask = <span class="keyword">null</span>;

- }

- mTask = <span class="keyword">new</span> MyTimerTask(updateHandler);

- timer.schedule(mTask, <span class="number"></span>, <span class="number">10</span>);

- }

- 
- <span class="annotation">@Override</span>

- <span class="keyword">protected</span> <span class="keyword">void</span> onMeasure(<span class="keyword">int</span> widthMeasureSpec, <span class="keyword">int</span> heightMeasureSpec)

- {

- <span class="keyword">super</span>.onMeasure(widthMeasureSpec, heightMeasureSpec);

- <span class="keyword">if</span> (!isMeasured)

- {

- isMeasured = <span class="keyword">true</span>;

- mViewHeight = getMeasuredHeight();

- mViewWidth = getMeasuredWidth();

- <span class="comment">// 水位线从最底下开始上升</span>

- mLevelLine = mViewHeight;

- <span class="comment">// 根据View宽度计算波形峰值</span>

- mWaveHeight = mViewWidth / <span class="number">2</span>.5f;

- <span class="comment">// 波长等于四倍View宽度也就是View中只能看到四分之一个波形,这样可以使起伏更明显</span>

- mWaveWidth = mViewWidth * <span class="number">4</span>;

- <span class="comment">// 左边隐藏的距离预留一个波形</span>

- mLeftSide = -mWaveWidth;

- <span class="comment">// 这里计算在可见的View宽度中能容纳几个波形,注意n上取整</span>

- <span class="keyword">int</span> n = (<span class="keyword">int</span>) Math.round(mViewWidth / mWaveWidth + <span class="number">0.5</span>);

- <span class="comment">// n个波形需要4n+1个点,但是我们要预留一个波形在左边隐藏区域,所以需要4n+5个点</span>

- <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number"></span>; i < (<span class="number">4</span> * n + <span class="number">5</span>); i++)

- {

- <span class="comment">// 从P0开始初始化到P4n+4,总共4n+5个点</span>

- <span class="keyword">float</span> x = i * mWaveWidth / <span class="number">4</span> &#8211; mWaveWidth;

- <span class="keyword">float</span> y = <span class="number"></span>;

- <span class="keyword">switch</span> (i % <span class="number">4</span>)

- {

- <span class="keyword">case</span> <span class="number"></span>:

- <span class="keyword">case</span> <span class="number">2</span>:

- <span class="comment">// 零点位于水位线上</span>

- y = mLevelLine;

- <span class="keyword">break</span>;

- <span class="keyword">case</span> <span class="number">1</span>:

- <span class="comment">// 往下波动的控制点</span>

- y = mLevelLine + mWaveHeight;

- <span class="keyword">break</span>;

- <span class="keyword">case</span> <span class="number">3</span>:

- <span class="comment">// 往上波动的控制点</span>

- y = mLevelLine &#8211; mWaveHeight;

- <span class="keyword">break</span>;

- }

- mPointsList.add(<span class="keyword">new</span> Point(x, y));

- }

- }

- }

- 
- <span class="annotation">@Override</span>

- <span class="keyword">protected</span> <span class="keyword">void</span> onDraw(Canvas canvas)

- {

- 
- mWavePath.reset();

- <span class="keyword">int</span> i = <span class="number"></span>;

- mWavePath.moveTo(mPointsList.get(<span class="number"></span>).getX(), mPointsList.get(<span class="number"></span>).getY());

- <span class="keyword">for</span> (; i < mPointsList.size() &#8211; <span class="number">2</span>; i = i + <span class="number">2</span>)

- {

- mWavePath.quadTo(mPointsList.get(i + <span class="number">1</span>).getX(),

- mPointsList.get(i + <span class="number">1</span>).getY(), mPointsList.get(i + <span class="number">2</span>)

- .getX(), mPointsList.get(i + <span class="number">2</span>).getY());

- }

- mWavePath.lineTo(mPointsList.get(i).getX(), mViewHeight);

- mWavePath.lineTo(mLeftSide, mViewHeight);

- mWavePath.close();

- 
- <span class="comment">// mPaint的Style是FILL,会填充整个Path区域</span>

- canvas.drawPath(mWavePath, mPaint);

- <span class="comment">// 绘制百分比</span>

- canvas.drawText(<span class="string">&#8220;&#8221;</span> + ((<span class="keyword">int</span>) ((<span class="number">1</span> &#8211; mLevelLine / mViewHeight) * <span class="number">100</span>))

- + <span class="string">&#8220;%&#8221;</span>, mViewWidth / <span class="number">2</span>, mLevelLine + mWaveHeight

- + (mViewHeight &#8211; mLevelLine &#8211; mWaveHeight) / <span class="number">2</span>, mTextPaint);

- }

- 
- <span class="keyword">class</span> MyTimerTask <span class="keyword">extends</span> TimerTask

- {

- Handler handler;

- 
- <span class="keyword">public</span> MyTimerTask(Handler handler)

- {

- <span class="keyword">this</span>.handler = handler;

- }

- 
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> <span class="keyword">void</span> run()

- {

- handler.sendMessage(handler.obtainMessage());

- }

- 
- }

- 
- <span class="keyword">class</span> Point

- {

- <span class="keyword">private</span> <span class="keyword">float</span> x;

- <span class="keyword">private</span> <span class="keyword">float</span> y;

- 
- <span class="keyword">public</span> <span class="keyword">float</span> getX()

- {

- <span class="keyword">return</span> x;

- }

- 
- <span class="keyword">public</span> <span class="keyword">void</span> setX(<span class="keyword">float</span> x)

- {

- <span class="keyword">this</span>.x = x;

- }

- 
- <span class="keyword">public</span> <span class="keyword">float</span> getY()

- {

- <span class="keyword">return</span> y;

- }

- 
- <span class="keyword">public</span> <span class="keyword">void</span> setY(<span class="keyword">float</span> y)

- {

- <span class="keyword">this</span>.y = y;

- }

- 
- <span class="keyword">public</span> Point(<span class="keyword">float</span> x, <span class="keyword">float</span> y)

- {

- <span class="keyword">this</span>.x = x;

- <span class="keyword">this</span>.y = y;

- }

- 
- }

- 
- }

代码中注释写的很多,不难看懂。

Demo的布局:

**[html]** [view plain](http://blog.csdn.net/zhongkejingwang/article/details/38556891#)[copy](http://blog.csdn.net/zhongkejingwang/article/details/38556891#)
  <div>
    <embed id="ZeroClipboardMovie_2" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_2">
    </embed>
  </div>
</div>
- <span class="tag"><</span><span class="tag-name">RelativeLayout</span> <span class="attribute">xmlns:android</span>=<span class="attribute-value">&#8220;http://schemas.android.com/apk/res/android&#8221;</span>

- <span class="attribute">android:layout_width</span>=<span class="attribute-value">&#8220;match_parent&#8221;</span>

- <span class="attribute">android:layout_height</span>=<span class="attribute-value">&#8220;match_parent&#8221;</span>

- <span class="attribute">android:background</span>=<span class="attribute-value">&#8220;#000000&#8221;</span> <span class="tag">></span>

- 
- <span class="tag"><</span><span class="tag-name">com.jingchen.waveview.WaveView</span>

- <span class="attribute">android:layout_width</span>=<span class="attribute-value">&#8220;100dp&#8221;</span>

- <span class="attribute">android:background</span>=<span class="attribute-value">&#8220;#ffffff&#8221;</span>

- <span class="attribute">android:layout_height</span>=<span class="attribute-value">&#8220;match_parent&#8221;</span>

- <span class="attribute">android:layout_centerInParent</span>=<span class="attribute-value">&#8220;true&#8221;</span> <span class="tag">/></span>

- 
- <span class="tag"></</span><span class="tag-name">RelativeLayout</span><span class="tag">></span>

MainActivity的代码:

**[java]** [view plain](http://blog.csdn.net/zhongkejingwang/article/details/38556891#)[copy](http://blog.csdn.net/zhongkejingwang/article/details/38556891#)
  <div>
    <embed id="ZeroClipboardMovie_3" src="http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf" type="application/x-shockwave-flash" width="18" height="18" align="middle" name="ZeroClipboardMovie_3">
    </embed>
  </div>
</div>
- <span class="keyword">package</span> com.jingchen.waveview;

- 
- <span class="keyword">import</span> android.os.Bundle;

- <span class="keyword">import</span> android.app.Activity;

- <span class="keyword">import</span> android.view.Menu;

- 
- <span class="keyword">public</span> <span class="keyword">class</span> MainActivity <span class="keyword">extends</span> Activity

- {

- 
- <span class="annotation">@Override</span>

- <span class="keyword">protected</span> <span class="keyword">void</span> onCreate(Bundle savedInstanceState)

- {

- <span class="keyword">super</span>.onCreate(savedInstanceState);

- setContentView(R.layout.activity_main);

- }

- 
- <span class="annotation">@Override</span>

- <span class="keyword">public</span> <span class="keyword">boolean</span> onCreateOptionsMenu(Menu menu)

- {

- getMenuInflater().inflate(R.menu.main, menu);

- <span class="keyword">return</span> <span class="keyword">true</span>;

- }

- 
- }

代码量很少。这样就可以很简单的做出水波效果啦~

源码下载

💬 评论