轉自:http://blog.csdn.net/tianjian4592/article/details/44222565
今天主要分享水波紋效果:
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 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中通過如下代碼繪制兩條靜態波紋:
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值數據,並不斷的去改變這兩個數組中的數據:
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
整體代碼如下:
-
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:
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;
然后繪制波浪和遮罩圖,繪制時設置對應的混合模式:
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);
為了形成動態的波浪效果,單開一個線程動態更新要繪制的波浪的位置:
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();
主要過程就以上這些,全部代碼如下:
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 }