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

2. 效果2

效果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);
}
}
此方法實現原理:運用三角函數畫出兩個不同速率正弦函數圖
我們先來復習三角函數吧
正余弦函數方程為:
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);
}
}
以上源代碼:`
源碼奉上各位
Android音頻開發之基礎知識介紹
先來點閒言碎語,前段時間我有一段感悟:Android開發,本身並不是一個可以走得多遠的方向,它只是一個平台,提供了許多封裝好的API,讓大家能夠快速開發出針對特定業務的應
[HyBrid]HyBrid混編初嘗:原生和第三方JsBridge的使用
最近研究HyBrid的兩種方式:一、直接原生WebView1)初始化WebView: //啟動javascript webView = (WebView)
Android項目之天氣預報 的實現分析
輸入要查詢的城市名稱,點擊查詢按鈕後,依次出現七天的天氣情況。出現時有動畫效果二、實現過程(一)獲取天氣預報數據1、首先搞定天氣預報數據來源的問題,提高天氣預報服務的有很
Android 清楚程序緩存
其實清除緩存是有兩種的,一種是清除手機rom裡面的緩存,一種是清除手機sd卡裡面的緩存,我們今天主要講的就是第一種 ps:這裡來一個知識掃盲,就是手機裡面的rom和r