Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 自定義view實現水波紋效果

自定義view實現水波紋效果

編輯:關於Android編程

今天看到一篇自定view 實現水波紋效果 覺得真心不錯 學習之後再次寫下筆記和心得.但是感覺原作者寫得有些晦澀難懂,也許是本人愚笨 所以重寫此作者教程.感覺他在自定義view方面非常厲害,本文是基於此作者原文重新改寫,擁有大量像相似部分

先看下效果吧:
1. 效果1:
這裡寫圖片描述
2. 效果2
這裡寫圖片描述


我先們來學習效果1:

效果1實現本質:用一張波形圖和一個圓形圖的圖片,然後圓形圖在波形圖上方,然後使用安卓的圖片遮罩模式desIn(不懂?那麼先記住有這樣一個遮罩模式).(只顯示上部圖像和下部圖像公共部分的下半部分),是不是很難懂?那麼我在說清一點並且配圖.假設圓形圖在波形圖上面,那麼只會顯示兩者相交部分的波形圖
下面是解釋效果圖(正方形藍色圖片在黃色圓形上面):
這裡寫圖片描述

 

這裡寫圖片描述

所用到波形圖:
這裡寫圖片描述

所用到圓形圖:

這裡寫圖片描述

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

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

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

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

先來看看我們定義的變量:

    //波形圖
    Bitmap waveBitmap;

    //圓形遮罩圖
    Bitmap circleBitmap;

    //波形圖src
    Rect waveSrcRect;
    //波形圖dst
    Rect waveDstRect;

    //圓形遮罩src
    Rect circleSrcRect;

    //圓形遮罩dst
    Rect circleDstRect;

    //畫筆
    Paint mpaint;

    //圖片遮罩模式
    PorterDuffXfermode mode;

    //控件的寬
    int viewWidth;
    //控件的高
    int viewHeight;

    //圖片過濾器
    PaintFlagsDrawFilter paintFlagsDrawFilter ;

    //每次移動的距離
    int speek = 10 ;

    //當前移動距離
    int nowOffSet;

介紹一個方法:

 void android.graphics.Canvas.drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)

此方法的參數:

參數1:你的圖片

參數2:矩形 .也就是說此矩形決定你畫出圖片參數1 的哪個位置,比如說你的矩形是設定是Rect rect= new Rect(0,0,圖片寬,圖片高) 那麼將會畫出圖片全部

參數3:矩形.決定你圖片縮放比例和在view中的位置.假設你的矩形Rect rect= new Rect(0,0,100,100) 那麼你將在自定義view中(0,0)點到(100,100)繪畫此圖片並且如果圖片大於(小於)此矩形那麼按比例縮小(放大)

來看看 初始化方法

//初始化
    private void init() {

        mpaint = new Paint();
        //處理圖片抖動
        mpaint.setDither(true);
        //抗鋸齒
        mpaint.setAntiAlias(true);
        //設置圖片過濾波
        mpaint.setFilterBitmap(true);
        //設置圖片遮罩模式
        mode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);

        //給畫布直接設定參數
        paintFlagsDrawFilter = new PaintFlagsDrawFilter(0, Paint.DITHER_FLAG|Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG);

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

        //獲取波形圖
        waveBitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.wave_2000)).getBitmap();

        //獲取圓形遮罩圖
        circleBitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.circle_500)).getBitmap();

        //不斷刷新波形圖距離 讀者可以先不看這部分內容  因為需要結合其他方法
        new Thread(){
            public void run() {
                while (true) {
                    try {
                        //移動波形圖
                        nowOffSet=nowOffSet+speek;
                        //如果移動波形圖的末尾那麼重新來
                        if (nowOffSet>=waveBitmap.getWidth()) {
                            nowOffSet=0;
                        }
                        sleep(30);
                        postInvalidate();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

            };
        }.start();

    }

以下獲取view的寬高並設置對應的波形圖和圓形圖矩形(會在onMesure回調後執行)

@Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //獲取view寬高
        viewWidth = w;
        viewHeight = h ;

        //波形圖的矩陣初始化
        waveSrcRect = new Rect();
        waveDstRect = new Rect(0,0,w,h);

        //圓球矩陣初始化
        circleSrcRect = new Rect(0,0,circleBitmap.getWidth(),circleBitmap.getHeight());

        circleDstRect = new Rect(0,0,viewWidth,viewHeight);


    }

那麼最後來看看繪畫部分吧

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);




        //給圖片直接設置過濾效果
        canvas.setDrawFilter(paintFlagsDrawFilter);
        //給圖片上色
        canvas.drawColor(Color.TRANSPARENT);
        //添加圖層 注意!!!!!使用圖片遮罩模式會影響全部此圖層(也就是說在canvas.restoreToCount 所有圖都會受到影響) 
        int saveLayer = canvas.saveLayer(0,0, viewWidth,viewHeight,null, Canvas.ALL_SAVE_FLAG);
        //畫波形圖部分 矩形
        waveSrcRect.set(nowOffSet, 0, nowOffSet+viewWidth/2, viewHeight);
        //畫矩形
        canvas.drawBitmap(waveBitmap,waveSrcRect,waveDstRect,mpaint);
        //設置圖片遮罩模式
        mpaint.setXfermode(mode);
        //畫遮罩
        canvas.drawBitmap(circleBitmap, circleSrcRect, circleDstRect,mpaint);
        //還原畫筆模式
        mpaint.setXfermode(null);
        //將圖層放上
        canvas.restoreToCount(saveLayer);
    }

最後看下完整的代碼

package com.fmy.shuibo1;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.icu.text.TimeZoneFormat.ParseOption;
import android.util.AttributeSet;
import android.view.View;

public class MySinUi extends View{


    //波形圖
    Bitmap waveBitmap;

    //圓形遮罩圖
    Bitmap circleBitmap;

    //波形圖src
    Rect waveSrcRect;
    //波形圖dst
    Rect waveDstRect;

    //圓形遮罩src
    Rect circleSrcRect;

    //圓形遮罩dst
    Rect circleDstRect;

    //畫筆
    Paint mpaint;

    //圖片遮罩模式
    PorterDuffXfermode mode;

    //控件的寬
    int viewWidth;
    //控件的高
    int viewHeight;

    //圖片過濾器
    PaintFlagsDrawFilter paintFlagsDrawFilter ;

    //每次移動的距離
    int speek = 10 ;

    //當前移動距離
    int nowOffSet;

    public MySinUi(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();

    }

    //初始化
    private void init() {

        mpaint = new Paint();
        //處理圖片抖動
        mpaint.setDither(true);
        //抗鋸齒
        mpaint.setAntiAlias(true);
        //設置圖片過濾波
        mpaint.setFilterBitmap(true);
        //設置圖片遮罩模式
        mode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);

        //給畫布直接設定參數
        paintFlagsDrawFilter = new PaintFlagsDrawFilter(0, Paint.DITHER_FLAG|Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG);

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

        //獲取波形圖
        waveBitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.wave_2000)).getBitmap();

        //獲取圓形遮罩圖
        circleBitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.circle_500)).getBitmap();

        //不斷刷新波形圖距離 讀者可以先不看這部分內容  因為需要結合其他方法
        new Thread(){
            public void run() {
                while (true) {
                    try {
                        //移動波形圖
                        nowOffSet=nowOffSet+speek;
                        //如果移動波形圖的末尾那麼重新來
                        if (nowOffSet>=waveBitmap.getWidth()) {
                            nowOffSet=0;
                        }
                        sleep(30);
                        postInvalidate();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

            };
        }.start();

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);




        //給圖片直接設置過濾效果
        canvas.setDrawFilter(paintFlagsDrawFilter);
        //給圖片上色
        canvas.drawColor(Color.TRANSPARENT);
        //添加圖層 注意!!!!!使用圖片遮罩模式會影響全部此圖層(也就是說在canvas.restoreToCount 所有圖都會受到影響) 
        int saveLayer = canvas.saveLayer(0,0, viewWidth,viewHeight,null, Canvas.ALL_SAVE_FLAG);
        //畫波形圖部分 矩形
        waveSrcRect.set(nowOffSet, 0, nowOffSet+viewWidth/2, viewHeight);
        //畫矩形
        canvas.drawBitmap(waveBitmap,waveSrcRect,waveDstRect,mpaint);
        //設置圖片遮罩模式
        mpaint.setXfermode(mode);
        //畫遮罩
        canvas.drawBitmap(circleBitmap, circleSrcRect, circleDstRect,mpaint);
        //還原畫筆模式
        mpaint.setXfermode(null);
        //將圖層放上
        canvas.restoreToCount(saveLayer);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //獲取view寬高
        viewWidth = w;
        viewHeight = h ;

        //波形圖的矩陣初始化
        waveSrcRect = new Rect();
        waveDstRect = new Rect(0,0,w,h);

        //圓球矩陣初始化
        circleSrcRect = new Rect(0,0,circleBitmap.getWidth(),circleBitmap.getHeight());

        circleDstRect = new Rect(0,0,viewWidth,viewHeight);


    }
}

學習效果2:

此方法實現原理:運用三角函數畫出兩個不同速率正弦函數圖

我們先來復習三角函數吧

正余弦函數方程為:
y = Asin(wx+b)+h ,這個公式裡:w影響周期,A影響振幅,h影響y位置,b為初相;

w:周期就是一個完整正弦曲線圖此數值越大sin的周期越小 (cos越大)
如下圖:
這裡寫圖片描述
(原作者說我們畫一個以自定義view的寬度為周期的圖:意思是說你view的寬度正好可以畫一個上面的圖.)

這裡寫圖片描述

A:振幅兩個山峰最大的高度.如果A越大兩個山峰越高和越低

h:你正弦曲線和y軸相交點.(影響正弦圖初始高度的位置)

b:初相會讓你圖片向x軸平移

具體大家可以百度學習,我們在學編程,不是數學


為什麼要兩個正弦圖畫?好看…..
先來看看變量:

// 波紋顏色
    private static final int WAVE_PAINT_COLOR = 0x880000aa;

    // 第一個波紋移動的速度
    private int oneSeep = 7;

    // 第二個波紋移動的速度
    private int twoSeep = 10;

    // 第一個波紋移動速度的像素值
    private int oneSeepPxil;
    // 第二個波紋移動速度的像素值
    private int twoSeepPxil;

    // 存放原始波紋的每個y坐標點
    private float wave[];

    // 存放第一個波紋的每一個y坐標點
    private float oneWave[];

    // 存放第二個波紋的每一個y坐標點
    private float twoWave[];

    // 第一個波紋當前移動的距離
    private int oneNowOffSet;
    // 第二個波紋當前移動的
    private int twoNowOffSet;

    // 振幅高度
    private int amplitude = 20;

    // 畫筆
    private Paint mPaint;

    // 創建畫布過濾
    private DrawFilter mDrawFilter;

    // view的寬度
    private int viewWidth;

    // view高度
    private int viewHeight;

畫初始的波形圖並且保存到數組中

// 大小改變
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // 獲取view的寬高
        viewHeight = h;
        viewWidth = w;

        // 初始化保存波形圖的數組
        wave = new float[w];
        oneWave = new float[w];
        twoWave = new float[w];

        // 設置波形圖周期
        float zq = (float) (Math.PI * 2 / w);

        // 設置波形圖的周期
        for (int i = 0; i < viewWidth; i++) {
            wave[i] = (float) (amplitude * Math.sin(zq * i));
        }


    }

初始化各種

// 初始化
    private void init() {
        // 創建畫筆
        mPaint = new Paint();
        // 設置畫筆顏色
        mPaint.setColor(WAVE_PAINT_COLOR);
        // 設置繪畫風格為實線
        mPaint.setStyle(Style.FILL);
        // 抗鋸齒
        mPaint.setAntiAlias(true);
        // 設置圖片過濾波和抗鋸齒
        mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);

        // 第一個波的像素移動值 換算成手機像素值讓其在各個手機移動速度差不多
        oneSeepPxil = dpChangPx(oneSeep);

        // 第二個波的像素移動值
        twoSeepPxil = dpChangPx(twoSeep);
    }
// 繪畫方法
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.setDrawFilter(mDrawFilter);

        oneNowOffSet =oneNowOffSet+oneSeepPxil;

        twoNowOffSet = twoNowOffSet+twoSeepPxil;

        if (oneNowOffSet>=viewWidth) {
            oneNowOffSet = 0;
        }
        if (twoNowOffSet>=viewWidth) {
            twoNowOffSet = 0;
        }
        //此方法會讓兩個保存波形圖的 數組更新 頭到NowOffSet變成尾部,尾部的變成頭部實現動態移動
        reSet();

        Log.e("fmy", Arrays.toString(twoWave));

        for (int i = 0; i < viewWidth; i++) {

            canvas.drawLine(i, viewHeight, i, viewHeight-400-oneWave[i], mPaint);
            canvas.drawLine(i, viewHeight, i, viewHeight-400-twoWave[i], mPaint);
        }

        postInvalidate();
    }

來看看能讓兩個數組重置的

    public void reSet() {
        // one是指 走到此處的波紋的位置 (這個理解方法看個人了)
        int one = viewWidth - oneNowOffSet;
        // 把未走過的波紋放到最前面 進行重新拼接
        System.arraycopy(wave, oneNowOffSet, oneWave, 0, one);
        // 把已走波紋放到最後
        System.arraycopy(wave, 0, oneWave, one, oneNowOffSet);

        // one是指 走到此處的波紋的位置 (這個理解方法看個人了)
        int two = viewWidth - twoNowOffSet;
        // 把未走過的波紋放到最前面 進行重新拼接
        System.arraycopy(wave, twoNowOffSet, twoWave, 0, two);
        // 把已走波紋放到最後
        System.arraycopy(wave, 0, twoWave, two, twoNowOffSet);


    }

最後大家看下完整代碼

package com.exam1ple.myshuibo2;

import java.util.Arrays;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DrawFilter;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.icu.text.TimeZoneFormat.ParseOption;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;

public class MyUi2 extends View {

    // 波紋顏色
    private static final int WAVE_PAINT_COLOR = 0x880000aa;

    // 第一個波紋移動的速度
    private int oneSeep = 7;

    // 第二個波紋移動的速度
    private int twoSeep = 10;

    // 第一個波紋移動速度的像素值
    private int oneSeepPxil;
    // 第二個波紋移動速度的像素值
    private int twoSeepPxil;

    // 存放原始波紋的每個y坐標點
    private float wave[];

    // 存放第一個波紋的每一個y坐標點
    private float oneWave[];

    // 存放第二個波紋的每一個y坐標點
    private float twoWave[];

    // 第一個波紋當前移動的距離
    private int oneNowOffSet;
    // 第二個波紋當前移動的
    private int twoNowOffSet;

    // 振幅高度
    private int amplitude = 20;

    // 畫筆
    private Paint mPaint;

    // 創建畫布過濾
    private DrawFilter mDrawFilter;

    // view的寬度
    private int viewWidth;

    // view高度
    private int viewHeight;

    // xml布局構造方法
    public MyUi2(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    // 初始化
    private void init() {
        // 創建畫筆
        mPaint = new Paint();
        // 設置畫筆顏色
        mPaint.setColor(WAVE_PAINT_COLOR);
        // 設置繪畫風格為實線
        mPaint.setStyle(Style.FILL);
        // 抗鋸齒
        mPaint.setAntiAlias(true);
        // 設置圖片過濾波和抗鋸齒
        mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);

        // 第一個波的像素移動值 換算成手機像素值讓其在各個手機移動速度差不多
        oneSeepPxil = dpChangPx(oneSeep);

        // 第二個波的像素移動值
        twoSeepPxil = dpChangPx(twoSeep);
    }

    // 繪畫方法
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.setDrawFilter(mDrawFilter);

        oneNowOffSet =oneNowOffSet+oneSeepPxil;

        twoNowOffSet = twoNowOffSet+twoSeepPxil;

        if (oneNowOffSet>=viewWidth) {
            oneNowOffSet = 0;
        }
        if (twoNowOffSet>=viewWidth) {
            twoNowOffSet = 0;
        }
        reSet();

        Log.e("fmy", Arrays.toString(twoWave));

        for (int i = 0; i < viewWidth; i++) {

            canvas.drawLine(i, viewHeight, i, viewHeight-400-oneWave[i], mPaint);
            canvas.drawLine(i, viewHeight, i, viewHeight-400-twoWave[i], mPaint);
        }

        postInvalidate();
    }

    public void reSet() {
        // one是指 走到此處的波紋的位置 (這個理解方法看個人了)
        int one = viewWidth - oneNowOffSet;
        // 把未走過的波紋放到最前面 進行重新拼接
        System.arraycopy(wave, oneNowOffSet, oneWave, 0, one);
        // 把已走波紋放到最後
        System.arraycopy(wave, 0, oneWave, one, oneNowOffSet);

        // one是指 走到此處的波紋的位置 (這個理解方法看個人了)
        int two = viewWidth - twoNowOffSet;
        // 把未走過的波紋放到最前面 進行重新拼接
        System.arraycopy(wave, twoNowOffSet, twoWave, 0, two);
        // 把已走波紋放到最後
        System.arraycopy(wave, 0, twoWave, two, twoNowOffSet);


    }

    // 大小改變
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        // 獲取view的寬高
        viewHeight = h;
        viewWidth = w;

        // 初始化保存波形圖的數組
        wave = new float[w];
        oneWave = new float[w];
        twoWave = new float[w];

        // 設置波形圖周期
        float zq = (float) (Math.PI * 2 / w);

        // 設置波形圖的周期
        for (int i = 0; i < viewWidth; i++) {
            wave[i] = (float) (amplitude * Math.sin(zq * i));
        }


    }

    // dp換算成px 為了讓移動速度在各個分辨率的手機的都差不多
    public int dpChangPx(int dp) {
        DisplayMetrics metrics = new DisplayMetrics();
        ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(metrics);
        return (int) (metrics.density * dp + 0.5f);
    }

}

以上源代碼:`
源碼奉上各位

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