当前位置: 移动技术网 > IT编程>移动开发>Android > Android WaveView实现水流波动效果

Android WaveView实现水流波动效果

2019年07月24日  | 移动技术网IT编程  | 我要评论

智在瑞,43789金光佛,开讲啦 郑强

   水流波动的波形都是三角波,曲线是正余弦曲线,但是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, 0, 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软件编程有所帮助。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网