Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android WaveView實現水流波動效果

Android WaveView實現水流波動效果

編輯:關於Android編程

   水流波動的波形都是三角波,曲線是正余弦曲線,但是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實現水流波動效果》

以上就是本文的全部內容,希望對大家學習Android軟件編程有所幫助。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved