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值儲存在數組裡:
- // 將週期定為view總寬度
- mCycleFactorW = (float) (2 * Math.PI / mTotalWidth);
- // 根據view總寬度得出所有對應的y值
- for (int i = 0; i <mTotalWidth; i++) {
- mYPositions[i] = (float) (STRETCH_FACTOR_A * Math.sin(mCycleFactorW * i) + OFFSET_Y);
- }
- for (int i = 0; i <mTotalWidth; i++) {
- // 減400只是為了控制波紋繪製的y的在螢幕的位置,大家可以改成一個變數,然後動態改變這個變數,從而形成波紋上升下降效果
- // 繪製第一條水波紋
- canvas.drawLine(i, mTotalHeight - mResetOneYPositions[i] - 400, i,
- mTotalHeight,
- mWavePaint);
- // 繪製第二條水波紋
- canvas.drawLine(i, mTotalHeight - mResetTwoYPositions[i] - 400, i,
- mTotalHeight,
- mWavePaint);
- }
這種方式類似於數學裡面的細分法,一條波紋,如果橫向以一個畫素點為單位進行細分,則形成view總寬度條直線,並且每條直線的起點和終點我們都能知道,在此基礎上我們只需要迴圈繪製出所有細分出來的直線(直線都是縱向的),則形成了一條靜態的水波紋;
接下來我們讓水波紋動起來,之前用了一個數組儲存了所有的y值點,有兩條水波紋,再利用兩個同樣大小的陣列來儲存兩條波紋的y值資料,並不斷的去改變這兩個陣列中的資料:
- private void resetPositonY() {
- // mXOneOffset代表當前第一條水波紋要移動的距離
- int yOneInterval = mYPositions.length - mXOneOffset;
- // 使用System.arraycopy方式重新填充第一條波紋的資料
- System.arraycopy(mYPositions, mXOneOffset, mResetOneYPositions, 0, yOneInterval);
- System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval, mXOneOffset);
- int yTwoInterval = mYPositions.length - mXTwoOffset;
- System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0,
- yTwoInterval);
- System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval, mXTwoOffset);
- }
如此下來只要不斷的改變這兩個陣列的資料,然後不斷重新整理,即可生成動態水波紋了;
重新整理可以呼叫invalidate()或postInvalidate(),區別在於後者可以在子執行緒中更新UI
整體程式碼如下:
- public class DynamicWave extends View {
- // 波紋顏色
- private static final int WAVE_PAINT_COLOR = 0x880000aa;
- // y = Asin(wx+b)+h
- private static final float STRETCH_FACTOR_A = 20;
- private static final int OFFSET_Y = 0;
- // 第一條水波移動速度
- private static final int TRANSLATE_X_SPEED_ONE = 7;
- // 第二條水波移動速度
- private static final int TRANSLATE_X_SPEED_TWO = 5;
- private float mCycleFactorW;
- private int mTotalWidth, mTotalHeight;
- private float[] mYPositions;
- private float[] mResetOneYPositions;
- private float[] mResetTwoYPositions;
- private int mXOffsetSpeedOne;
- private int mXOffsetSpeedTwo;
- private int mXOneOffset;
- private int mXTwoOffset;
- private Paint mWavePaint;
- private DrawFilter mDrawFilter;
- public DynamicWave(Context context, AttributeSet attrs) {
- super(context, attrs);
- // 將dp轉化為px,用於控制不同解析度上移動速度基本一致
- mXOffsetSpeedOne = UIUtils.dipToPx(context, TRANSLATE_X_SPEED_ONE);
- mXOffsetSpeedTwo = UIUtils.dipToPx(context, TRANSLATE_X_SPEED_TWO);
- // 初始繪製波紋的畫筆
- mWavePaint = new Paint();
- // 去除畫筆鋸齒
- mWavePaint.setAntiAlias(true);
- // 設定風格為實線
- mWavePaint.setStyle(Style.FILL);
- // 設定畫筆顏色
- mWavePaint.setColor(WAVE_PAINT_COLOR);
- mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
- }
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- // 從canvas層面去除繪製時鋸齒
- canvas.setDrawFilter(mDrawFilter);
- resetPositonY();
- for (int i = 0; i <mTotalWidth; i++) {
- // 減400只是為了控制波紋繪製的y的在螢幕的位置,大家可以改成一個變數,然後動態改變這個變數,從而形成波紋上升下降效果
- // 繪製第一條水波紋
- canvas.drawLine(i, mTotalHeight - mResetOneYPositions[i] - 400, i,
- mTotalHeight,
- mWavePaint);
- // 繪製第二條水波紋
- canvas.drawLine(i, mTotalHeight - mResetTwoYPositions[i] - 400, i,
- mTotalHeight,
- mWavePaint);
- }
- // 改變兩條波紋的移動點
- mXOneOffset += mXOffsetSpeedOne;
- mXTwoOffset += mXOffsetSpeedTwo;
- // 如果已經移動到結尾處,則重頭記錄
- if (mXOneOffset >= mTotalWidth) {
- mXOneOffset = 0;
- }
- if (mXTwoOffset > mTotalWidth) {
- mXTwoOffset = 0;
- }
- // 引發view重繪,一般可以考慮延遲20-30ms重繪,空出時間片
- postInvalidate();
- }
- private void resetPositonY() {
- // mXOneOffset代表當前第一條水波紋要移動的距離
- int yOneInterval = mYPositions.length - mXOneOffset;
- // 使用System.arraycopy方式重新填充第一條波紋的資料
- System.arraycopy(mYPositions, mXOneOffset, mResetOneYPositions, 0, yOneInterval);
- System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval, mXOneOffset);
- int yTwoInterval = mYPositions.length - mXTwoOffset;
- System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0,
- yTwoInterval);
- System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval, mXTwoOffset);
- }
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- // 記錄下view的寬高
- mTotalWidth = w;
- mTotalHeight = h;
- // 用於儲存原始波紋的y值
- mYPositions = new float[mTotalWidth];
- <