Android自定義View實現水波浪效果

http://blog.csdn.NET/tianjian4592/article/details/44222565

在實際的開發中,很多時候還會遇到相對比較複雜的需求,比如產品妹紙或UI妹紙在哪看了個讓人興奮的效果,興致高昂的來找你,看了之後目的很明確,當然就是希望你能給她;

在這樣的關鍵時候,身子板就一定得硬了,可千萬別說不行,爺們兒怎麼能說不行呢;


好了,爲了讓大家都能給妹紙們想要的,後面會逐漸分享一些比較比較不錯的效果,目的只有一個,通過自定義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值保存在數組裏:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. // 將週期定爲view總寬度  
  2. mCycleFactorW = (float) (2 * Math.PI / mTotalWidth);  
  3.   
  4. // 根據view總寬度得出所有對應的y值  
  5. for (int i = 0; i < mTotalWidth; i++) {  
  6.     mYPositions[i] = (float) (STRETCH_FACTOR_A * Math.sin(mCycleFactorW * i) + OFFSET_Y);  
  7. }  


根據得出的所有y值,則可以在onDraw中通過如下代碼繪製兩條靜態波紋:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. for (int i = 0; i < mTotalWidth; i++) {  
  2.   
  3.       // 減400只是爲了控制波紋繪製的y的在屏幕的位置,大家可以改成一個變量,然後動態改變這個變量,從而形成波紋上升下降效果  
  4.       // 繪製第一條水波紋  
  5.       canvas.drawLine(i, mTotalHeight - mResetOneYPositions[i] - 400, i,  
  6.               mTotalHeight,  
  7.               mWavePaint);  
  8.   
  9.       // 繪製第二條水波紋  
  10.       canvas.drawLine(i, mTotalHeight - mResetTwoYPositions[i] - 400, i,  
  11.               mTotalHeight,  
  12.               mWavePaint);  
  13.   }  

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

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

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  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.   
  8.     int yTwoInterval = mYPositions.length - mXTwoOffset;  
  9.     System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0,  
  10.             yTwoInterval);  
  11.     System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval, mXTwoOffset);  
  12. }  

如此下來只要不斷的改變這兩個數組的數據,然後不斷刷新,即可生成動態水波紋了;

刷新可以調用invalidate()或postInvalidate(),區別在於後者可以在子線程中更新UI

整體代碼如下:

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

二:非標準圓形液柱水波紋

前面的波形使用函數模擬,這個我們換種方式,採用圖進行實現,先用PS整張不像波紋的波紋圖;



爲了銜接緊密,首尾都比較平,並高度一致;

思路:

1.使用一個圓形圖作爲遮罩過濾波形圖;

2.平移波紋圖,即不斷改變繪製的波紋圖的區域,即srcRect;

3.當一個週期繪製完,則從波紋圖的最前面重新計算;


首先初始化bitmap:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void initBitmap() {  
  2.         mSrcBitmap = ((BitmapDrawable) getResources().getDrawable(R.drawable.wave_2000))  
  3.                 .getBitmap();  
  4.         mMaskBitmap = ((BitmapDrawable) getResources().getDrawable(  
  5.                 R.drawable.circle_500))  
  6.                 .getBitmap();  
  7.     }  


使用drawable獲取的方式,全局只會生成一份,並且系統會進行管理,而BitmapFactory.decode()出來的則decode多少次生成多少張,務必自己進行recycle;

然後繪製波浪和遮罩圖,繪製時設置對應的混合模式:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. /*  
  2.  * 將繪製操作保存到新的圖層  
  3.  */  
  4. int sc = canvas.saveLayer(0, 0, mTotalWidth, mTotalHeight, null, Canvas.ALL_SAVE_FLAG);  
  5.   
  6. // 設定要繪製的波紋部分  
  7. mSrcRect.set(mCurrentPosition, 0, mCurrentPosition + mCenterX, mTotalHeight);  
  8. // 繪製波紋部分  
  9. canvas.drawBitmap(mSrcBitmap, mSrcRect, mDestRect, mBitmapPaint);  
  10.   
  11. // 設置圖像的混合模式  
  12. mBitmapPaint.setXfermode(mPorterDuffXfermode);  
  13. // 繪製遮罩圓  
  14. canvas.drawBitmap(mMaskBitmap, mMaskSrcRect, mMaskDestRect,  
  15.         mBitmapPaint);  
  16. mBitmapPaint.setXfermode(null);  
  17. canvas.restoreToCount(sc);  

爲了形成動態的波浪效果,單開一個線程動態更新要繪製的波浪的位置:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1.  new Thread() {  
  2.     public void run() {  
  3.         while (true) {  
  4.             // 不斷改變繪製的波浪的位置  
  5.             mCurrentPosition += mSpeed;  
  6.             if (mCurrentPosition >= mSrcBitmap.getWidth()) {  
  7.                 mCurrentPosition = 0;  
  8.             }  
  9.             try {  
  10.                 // 爲了保證效果的同時,儘可能將cpu空出來,供其他部分使用  
  11.                 Thread.sleep(30);  
  12.             } catch (InterruptedException e) {  
  13.             }  
  14.   
  15.             postInvalidate();  
  16.         }  
  17.   
  18.     };  
  19. }.start();  

主要過程就以上這些,全部代碼如下:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class PorterDuffXfermodeView extends View {  
  2.   
  3.     private static final int WAVE_TRANS_SPEED = 4;  
  4.   
  5.     private Paint mBitmapPaint, mPicPaint;  
  6.     private int mTotalWidth, mTotalHeight;  
  7.     private int mCenterX, mCenterY;  
  8.     private int mSpeed;  
  9.   
  10.     private Bitmap mSrcBitmap;  
  11.     private Rect mSrcRect, mDestRect;  
  12.   
  13.     private PorterDuffXfermode mPorterDuffXfermode;  
  14.     private Bitmap mMaskBitmap;  
  15.     private Rect mMaskSrcRect, mMaskDestRect;  
  16.     private PaintFlagsDrawFilter mDrawFilter;  
  17.   
  18.     private int mCurrentPosition;  
  19.   
  20.     public PorterDuffXfermodeView(Context context, AttributeSet attrs) {  
  21.         super(context, attrs);  
  22.         initPaint();  
  23.         initBitmap();  
  24.         mPorterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);  
  25.         mSpeed = UIUtils.dipToPx(mContext, WAVE_TRANS_SPEED);  
  26.         mDrawFilter = new PaintFlagsDrawFilter(Paint.ANTI_ALIAS_FLAG, Paint.DITHER_FLAG);  
  27.         new Thread() {  
  28.             public void run() {  
  29.                 while (true) {  
  30.                     // 不斷改變繪製的波浪的位置  
  31.                     mCurrentPosition += mSpeed;  
  32.                     if (mCurrentPosition >= mSrcBitmap.getWidth()) {  
  33.                         mCurrentPosition = 0;  
  34.                     }  
  35.                     try {  
  36.                         // 爲了保證效果的同時,儘可能將cpu空出來,供其他部分使用  
  37.                         Thread.sleep(30);  
  38.                     } catch (InterruptedException e) {  
  39.                     }  
  40.   
  41.                     postInvalidate();  
  42.                 }  
  43.   
  44.             };  
  45.         }.start();  
  46.     }  
  47.   
  48.     @Override  
  49.     protected void onDraw(Canvas canvas) {  
  50.         super.onDraw(canvas);  
  51.   
  52.         // 從canvas層面去除鋸齒  
  53.         canvas.setDrawFilter(mDrawFilter);  
  54.         canvas.drawColor(Color.TRANSPARENT);  
  55.   
  56.         /*  
  57.          * 將繪製操作保存到新的圖層  
  58.          */  
  59.         int sc = canvas.saveLayer(0, 0, mTotalWidth, mTotalHeight, null, Canvas.ALL_SAVE_FLAG);  
  60.   
  61.         // 設定要繪製的波紋部分  
  62.         mSrcRect.set(mCurrentPosition, 0, mCurrentPosition + mCenterX, mTotalHeight);  
  63.         // 繪製波紋部分  
  64.         canvas.drawBitmap(mSrcBitmap, mSrcRect, mDestRect, mBitmapPaint);  
  65.   
  66.         // 設置圖像的混合模式  
  67.         mBitmapPaint.setXfermode(mPorterDuffXfermode);  
  68.         // 繪製遮罩圓  
  69.         canvas.drawBitmap(mMaskBitmap, mMaskSrcRect, mMaskDestRect,  
  70.                 mBitmapPaint);  
  71.         mBitmapPaint.setXfermode(null);  
  72.         canvas.restoreToCount(sc);  
  73.     }  
  74.   
  75.     // 初始化bitmap  
  76.     private void initBitmap() {  
  77.         mSrcBitmap = ((BitmapDrawable) getResources().getDrawable(R.drawable.wave_2000))  
  78.                 .getBitmap();  
  79.         mMaskBitmap = ((BitmapDrawable) getResources().getDrawable(  
  80.                 R.drawable.circle_500))  
  81.                 .getBitmap();  
  82.     }  
  83.   
  84.     // 初始化畫筆paint  
  85.     private void initPaint() {  
  86.   
  87.         mBitmapPaint = new Paint();  
  88.         // 防抖動  
  89.         mBitmapPaint.setDither(true);  
  90.         // 開啓圖像過濾  
  91.         mBitmapPaint.setFilterBitmap(true);  
  92.   
  93.         mPicPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
  94.         mPicPaint.setDither(true);  
  95.         mPicPaint.setColor(Color.RED);  
  96.     }  
  97.   
  98.     @Override  
  99.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  100.         super.onSizeChanged(w, h, oldw, oldh);  
  101.         mTotalWidth = w;  
  102.         mTotalHeight = h;  
  103.         mCenterX = mTotalWidth / 2;  
  104.         mCenterY = mTotalHeight / 2;  
  105.   
  106.         mSrcRect = new Rect();  
  107.         mDestRect = new Rect(0, 0, mTotalWidth, mTotalHeight);  
  108.   
  109.         int maskWidth = mMaskBitmap.getWidth();  
  110.         int maskHeight = mMaskBitmap.getHeight();  
  111.         mMaskSrcRect = new Rect(0, 0, maskWidth, maskHeight);  
  112.         mMaskDestRect = new Rect(0, 0, mTotalWidth, mTotalHeight);  
  113.     }  
  114.   
  115. }  


源碼CSDN下載地址:http://download.csdn.net/download/tianjian4592/8496385



轉載自http://blog.csdn.net/jdsjlzx/article/details/44601239

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章