Android WaveView实现水流波动效果

Android WaveView实现水流波动效果,第1张

概述   水流波动的波形都是三角波,曲线是正余弦曲线,但是Android中没有提供绘制正余弦曲线的API,好在Path类有个绘制贝塞尔曲线的方法quadTo,绘制出来的是2阶的贝塞尔曲线,要想实现波动效果,只能用它

   水流波动的波形都是三角波,曲线是正余弦曲线,但是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:

package com.jingchen.wavevIEw;  import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask;  import androID.content.Context; import androID.graphics.Canvas; import androID.graphics.color; import androID.graphics.Paint; import androID.graphics.Paint.Align; import androID.graphics.Paint.Style; import androID.graphics.Region.Op; import androID.graphics.Path; import androID.graphics.RectF; import androID.os.Handler; import androID.os.Message; import androID.util.AttributeSet; import androID.vIEw.VIEw;  /**  * 水流波动控件  *  * @author chenjing  *  */ public class WaveVIEw extends VIEw {   private int mVIEwWIDth;  private int mVIEwHeight;   /**   * 水位线   */  private float mLevelline;   /**   * 波浪起伏幅度   */  private float mWaveHeight = 80;  /**   * 波长   */  private float mWaveWIDth = 200;  /**   * 被隐藏的最左边的波形   */  private float mleftSIDe;   private float mMoveLen;  /**   * 水波平移速度   */  public static final float SPEED = 1.7f;   private List<Point> mPointsList;  private Paint mPaint;  private Paint mTextPaint;  private Path mWavePath;  private boolean isMeasured = false;   private Timer timer;  private MyTimerTask mTask;  Handler updateHandler = new Handler()  {    @OverrIDe   public voID handleMessage(Message msg)   {    // 记录平移总位移    mMoveLen += SPEED;    // 水位上升    mLevelline -= 0.1f;    if (mLevelline < 0)     mLevelline = 0;    mleftSIDe += SPEED;    // 波形平移    for (int i = 0; i < mPointsList.size(); i++)    {     mPointsList.get(i).setX(mPointsList.get(i).getX() + SPEED);     switch (i % 4)     {     case 0:     case 2:      mPointsList.get(i).setY(mLevelline);      break;     case 1:      mPointsList.get(i).setY(mLevelline + mWaveHeight);      break;     case 3:      mPointsList.get(i).setY(mLevelline - mWaveHeight);      break;     }    }    if (mMoveLen >= mWaveWIDth)    {     // 波形平移超过一个完整波形后复位     mMoveLen = 0;     resetPoints();    }    invalIDate();   }   };   /**   * 所有点的x坐标都还原到初始状态,也就是一个周期前的状态   */  private voID resetPoints()  {   mleftSIDe = -mWaveWIDth;   for (int i = 0; i < mPointsList.size(); i++)   {    mPointsList.get(i).setX(i * mWaveWIDth / 4 - mWaveWIDth);   }  }   public WaveVIEw(Context context)  {   super(context);   init();  }   public WaveVIEw(Context context,AttributeSet attrs)  {   super(context,attrs);   init();  }   public WaveVIEw(Context context,AttributeSet attrs,int defStyle)  {   super(context,attrs,defStyle);   init();  }   private voID init()  {   mPointsList = new ArrayList<Point>();   timer = new Timer();    mPaint = new Paint();   mPaint.setAntiAlias(true);   mPaint.setStyle(Style.FILL);   mPaint.setcolor(color.BLUE);    mTextPaint = new Paint();   mTextPaint.setcolor(color.WHITE);   mTextPaint.setTextAlign(Align.CENTER);   mTextPaint.setTextSize(30);    mWavePath = new Path();  }   @OverrIDe  public voID onWindowFocusChanged(boolean hasWindowFocus)  {   super.onWindowFocusChanged(hasWindowFocus);   // 开始波动   start();  }   private voID start()  {   if (mTask != null)   {    mTask.cancel();    mTask = null;   }   mTask = new MyTimerTask(updateHandler);   timer.schedule(mTask,10);  }   @OverrIDe  protected voID onMeasure(int wIDthMeasureSpec,int heightmeasureSpec)  {   super.onMeasure(wIDthMeasureSpec,heightmeasureSpec);   if (!isMeasured)   {    isMeasured = true;    mVIEwHeight = getMeasuredHeight();    mVIEwWIDth = getMeasureDWIDth();    // 水位线从最底下开始上升    mLevelline = mVIEwHeight;    // 根据VIEw宽度计算波形峰值    mWaveHeight = mVIEwWIDth / 2.5f;    // 波长等于四倍VIEw宽度也就是VIEw中只能看到四分之一个波形,这样可以使起伏更明显    mWaveWIDth = mVIEwWIDth * 4;    // 左边隐藏的距离预留一个波形    mleftSIDe = -mWaveWIDth;    // 这里计算在可见的VIEw宽度中能容纳几个波形,注意n上取整    int n = (int) Math.round(mVIEwWIDth / mWaveWIDth + 0.5);    // n个波形需要4n+1个点,但是我们要预留一个波形在左边隐藏区域,所以需要4n+5个点    for (int i = 0; i < (4 * n + 5); i++)    {     // 从P0开始初始化到P4n+4,总共4n+5个点     float x = i * mWaveWIDth / 4 - mWaveWIDth;     float y = 0;     switch (i % 4)     {     case 0:     case 2:      // 零点位于水位线上      y = mLevelline;      break;     case 1:      // 往下波动的控制点      y = mLevelline + mWaveHeight;      break;     case 3:      // 往上波动的控制点      y = mLevelline - mWaveHeight;      break;     }     mPointsList.add(new Point(x,y));    }   }  }   @OverrIDe  protected voID onDraw(Canvas canvas)  {    mWavePath.reset();   int i = 0;   mWavePath.moveto(mPointsList.get(0).getX(),mPointsList.get(0).getY());   for (; i < mPointsList.size() - 2; i = i + 2)   {    mWavePath.quadTo(mPointsList.get(i + 1).getX(),mPointsList.get(i + 1).getY(),mPointsList.get(i + 2)        .getX(),mPointsList.get(i + 2).getY());   }   mWavePath.lineto(mPointsList.get(i).getX(),mVIEwHeight);   mWavePath.lineto(mleftSIDe,mVIEwHeight);   mWavePath.close();    // mPaint的Style是FILL,会填充整个Path区域   canvas.drawPath(mWavePath,mPaint);   // 绘制百分比   canvas.drawText("" + ((int) ((1 - mLevelline / mVIEwHeight) * 100))     + "%",mVIEwWIDth / 2,mLevelline + mWaveHeight     + (mVIEwHeight - mLevelline - mWaveHeight) / 2,mTextPaint);  }   class MyTimerTask extends TimerTask  {   Handler handler;    public MyTimerTask(Handler handler)   {    this.handler = handler;   }    @OverrIDe   public voID run()   {    handler.sendMessage(handler.obtainMessage());   }   }   class Point  {   private float x;   private float y;    public float getX()   {    return x;   }    public voID setX(float x)   {    this.x = x;   }    public float getY()   {    return y;   }    public voID setY(float y)   {    this.y = y;   }    public Point(float x,float y)   {    this.x = x;    this.y = y;   }   }  } 

代码中注释写的很多,不难看懂。
Demo的布局:

<relativeLayout xmlns:androID="http://schemas.androID.com/apk/res/androID"  androID:layout_wIDth="match_parent"  androID:layout_height="match_parent"  androID:background="#000000" >   <com.jingchen.wavevIEw.WaveVIEw   androID:layout_wIDth="100dp"   androID:background="#ffffff"   androID:layout_height="match_parent"   androID:layout_centerInParent="true" />  </relativeLayout> 

MainActivity的代码:

package com.jingchen.wavevIEw;  import androID.os.Bundle; import androID.app.Activity; import androID.vIEw.Menu;  public class MainActivity extends Activity {   @OverrIDe  protected voID onCreate(Bundle savedInstanceState)  {   super.onCreate(savedInstanceState);   setContentVIEw(R.layout.activity_main);  }   @OverrIDe  public boolean onCreateOptionsMenu(Menu menu)  {   getMenuInflater().inflate(R.menu.main,menu);   return true;  }  } 

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

源码下载: 《Android实现水流波动效果》

以上就是本文的全部内容,希望对大家学习AndroID软件编程有所帮助。

总结

以上是内存溢出为你收集整理的Android WaveView实现水流波动效果全部内容,希望文章能够帮你解决Android WaveView实现水流波动效果所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/web/1149593.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存