編輯:關於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開發,本身並不是一個可以走得多遠的方向,它只是一個平台,提供了許多封裝好的API,讓大家能夠快速開發出針對特定業務的應
最近研究HyBrid的兩種方式:一、直接原生WebView1)初始化WebView: //啟動javascript webView = (WebView)
輸入要查詢的城市名稱,點擊查詢按鈕後,依次出現七天的天氣情況。出現時有動畫效果二、實現過程(一)獲取天氣預報數據1、首先搞定天氣預報數據來源的問題,提高天氣預報服務的有很
其實清除緩存是有兩種的,一種是清除手機rom裡面的緩存,一種是清除手機sd卡裡面的緩存,我們今天主要講的就是第一種 ps:這裡來一個知識掃盲,就是手機裡面的rom和r