1. 程式人生 > >Android 自定義view實現水波紋效果

Android 自定義view實現水波紋效果

今天主要分享水波紋效果:

1.標準正餘弦水波紋;

2.非標準圓形液柱水波紋;

雖說都是水波紋,但兩者在實現上差異是比較大的,一個通過正餘弦函式模擬水波紋效果,另外一個會運用到影象的混合模式(PorterDuffXfermode);

先看效果:

                           

自定義View根據實際情況可以選擇繼承自View、TextView、ImageView或其他,我們先只需要瞭解如何利用android給我們提供好的利刃去滿足UI妹紙;

這次的實現我們都選擇繼承view,在實現的過程中我們需要關注如下幾個方法:

1.onMeasure():最先回調,用於控制元件的測量;

2.onSizeChanged():在onMeasure後面回撥,可以拿到view的寬高等資料,在橫豎屏切換時也會回撥;

3.onDraw():真正的繪製部分,繪製的程式碼都寫到這裡面;

既然如此,我們先複寫這三個方法,然後來實現如上兩個效果;

一:標準正餘弦水波紋

這種水波紋可以用具體函式模擬出具體的軌跡,所以思路基本如下:

1.確定水波函式方程

2.根據函式方程得出每一個波紋上點的座標;

3.將水波進行平移,即將水波上的點不斷的移動;

4.不斷的重新繪製,生成動態水波紋;

有了上面的思路,我們一步一步進行實現:

正餘弦函式方程為:

y = Asin(wx+b)+h ,這個公式裡:w影響週期,A影響振幅,h影響y位置,b為初相;

根據上面的方程選取自己覺得中意的波紋效果,確定對應引數的取值;

然後根據確定好的方程得出所有的方程上y的數值,並將所有y值儲存在數組裡:

  1. // 將週期定為view總寬度  
  2. mCycleFactorW = (float) (2 * Math.PI / mTotalWidth);  
  3. // 根據view總寬度得出所有對應的y值  
  4. for (int i = 0; i <mTotalWidth; i++) {  
  5.     mYPositions[i] = (float) (STRETCH_FACTOR_A * Math.sin(mCycleFactorW * i) + OFFSET_Y);  
  6. }  
根據得出的所有y值,則可以在onDraw中通過如下程式碼繪製兩條靜態波紋:
  1. for (int i = 0; i <mTotalWidth; i++) {  
  2.       // 減400只是為了控制波紋繪製的y的在螢幕的位置,大家可以改成一個變數,然後動態改變這個變數,從而形成波紋上升下降效果  
  3.       // 繪製第一條水波紋  
  4.       canvas.drawLine(i, mTotalHeight - mResetOneYPositions[i] - 400, i,  
  5.               mTotalHeight,  
  6.               mWavePaint);  
  7.       // 繪製第二條水波紋  
  8.       canvas.drawLine(i, mTotalHeight - mResetTwoYPositions[i] - 400, i,  
  9.               mTotalHeight,  
  10.               mWavePaint);  
  11.   }  

這種方式類似於數學裡面的細分法,一條波紋,如果橫向以一個畫素點為單位進行細分,則形成view總寬度條直線,並且每條直線的起點和終點我們都能知道,在此基礎上我們只需要迴圈繪製出所有細分出來的直線(直線都是縱向的),則形成了一條靜態的水波紋;

接下來我們讓水波紋動起來,之前用了一個數組儲存了所有的y值點,有兩條水波紋,再利用兩個同樣大小的陣列來儲存兩條波紋的y值資料,並不斷的去改變這兩個陣列中的資料:

  1.  private void resetPositonY() {  
  2.     // mXOneOffset代表當前第一條水波紋要移動的距離  
  3.     int yOneInterval = mYPositions.length - mXOneOffset;  
  4.     // 使用System.arraycopy方式重新填充第一條波紋的資料  
  5.     System.arraycopy(mYPositions, mXOneOffset, mResetOneYPositions, 0, yOneInterval);  
  6.     System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval, mXOneOffset);  
  7.     int yTwoInterval = mYPositions.length - mXTwoOffset;  
  8.     System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0,  
  9.             yTwoInterval);  
  10.     System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval, mXTwoOffset);  
  11. }  

如此下來只要不斷的改變這兩個陣列的資料,然後不斷重新整理,即可生成動態水波紋了;

重新整理可以呼叫invalidate()或postInvalidate(),區別在於後者可以在子執行緒中更新UI

整體程式碼如下:

  1. public class DynamicWave extends View {  
  2.     // 波紋顏色  
  3.     private static final int WAVE_PAINT_COLOR = 0x880000aa;  
  4.     // y = Asin(wx+b)+h  
  5.     private static final float STRETCH_FACTOR_A = 20;  
  6.     private static final int OFFSET_Y = 0;  
  7.     // 第一條水波移動速度  
  8.     private static final int TRANSLATE_X_SPEED_ONE = 7;  
  9.     // 第二條水波移動速度  
  10.     private static final int TRANSLATE_X_SPEED_TWO = 5;  
  11.     private float mCycleFactorW;  
  12.     private int mTotalWidth, mTotalHeight;  
  13.     private float[] mYPositions;  
  14.     private float[] mResetOneYPositions;  
  15.     private float[] mResetTwoYPositions;  
  16.     private int mXOffsetSpeedOne;  
  17.     private int mXOffsetSpeedTwo;  
  18.     private int mXOneOffset;  
  19.     private int mXTwoOffset;  
  20.     private Paint mWavePaint;  
  21.     private DrawFilter mDrawFilter;  
  22.     public DynamicWave(Context context, AttributeSet attrs) {  
  23.         super(context, attrs);  
  24.         // 將dp轉化為px,用於控制不同解析度上移動速度基本一致  
  25.         mXOffsetSpeedOne = UIUtils.dipToPx(context, TRANSLATE_X_SPEED_ONE);  
  26.         mXOffsetSpeedTwo = UIUtils.dipToPx(context, TRANSLATE_X_SPEED_TWO);  
  27.         // 初始繪製波紋的畫筆  
  28.         mWavePaint = new Paint();  
  29.         // 去除畫筆鋸齒  
  30.         mWavePaint.setAntiAlias(true);  
  31.         // 設定風格為實線  
  32.         mWavePaint.setStyle(Style.FILL);  
  33.         // 設定畫筆顏色  
  34.         mWavePaint.setColor(WAVE_PAINT_COLOR);  
  35.         mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);  
  36.     }  
  37.     @Override  
  38.     protected void onDraw(Canvas canvas) {  
  39.         super.onDraw(canvas);  
  40.         // 從canvas層面去除繪製時鋸齒  
  41.         canvas.setDrawFilter(mDrawFilter);  
  42.         resetPositonY();  
  43.         for (int i = 0; i <mTotalWidth; i++) {  
  44.             // 減400只是為了控制波紋繪製的y的在螢幕的位置,大家可以改成一個變數,然後動態改變這個變數,從而形成波紋上升下降效果  
  45.             // 繪製第一條水波紋  
  46.             canvas.drawLine(i, mTotalHeight - mResetOneYPositions[i] - 400, i,  
  47.                     mTotalHeight,  
  48.                     mWavePaint);  
  49.             // 繪製第二條水波紋  
  50.             canvas.drawLine(i, mTotalHeight - mResetTwoYPositions[i] - 400, i,  
  51.                     mTotalHeight,  
  52.                     mWavePaint);  
  53.         }  
  54.         // 改變兩條波紋的移動點  
  55.         mXOneOffset += mXOffsetSpeedOne;  
  56.         mXTwoOffset += mXOffsetSpeedTwo;  
  57.         // 如果已經移動到結尾處,則重頭記錄  
  58.         if (mXOneOffset >= mTotalWidth) {  
  59.             mXOneOffset = 0;  
  60.         }  
  61.         if (mXTwoOffset > mTotalWidth) {  
  62.             mXTwoOffset = 0;  
  63.         }  
  64.         // 引發view重繪,一般可以考慮延遲20-30ms重繪,空出時間片  
  65.         postInvalidate();  
  66.     }  
  67.     private void resetPositonY() {  
  68.         // mXOneOffset代表當前第一條水波紋要移動的距離  
  69.         int yOneInterval = mYPositions.length - mXOneOffset;  
  70.         // 使用System.arraycopy方式重新填充第一條波紋的資料  
  71.         System.arraycopy(mYPositions, mXOneOffset, mResetOneYPositions, 0, yOneInterval);  
  72.         System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval, mXOneOffset);  
  73.         int yTwoInterval = mYPositions.length - mXTwoOffset;  
  74.         System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0,  
  75.                 yTwoInterval);  
  76.         System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval, mXTwoOffset);  
  77.     }  
  78.     @Override  
  79.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  80.         super.onSizeChanged(w, h, oldw, oldh);  
  81.         // 記錄下view的寬高  
  82.         mTotalWidth = w;  
  83.         mTotalHeight = h;  
  84.         // 用於儲存原始波紋的y值  
  85.         mYPositions = new float[mTotalWidth];  
  86. <