Android自訂控制項系列五:自訂絢麗水波紋效果

來源:互聯網
上載者:User

標籤:android   效果   自訂控制項   水波紋   ondraw   

尊重原創!轉載請註明出處:http://blog.csdn.net/cyp331203/article/details/41114551


今天我們來利用Android自訂控制項實現一個比較有趣的效果:滑動水波紋。先來看看最終:


圖一


效果還是很炫的;飯要一口口吃,路要一步步走,這裡我們將整個過程分成幾步來實現


一、實現單擊出現水波紋單圈效果:



圖二


照例來說,還是一個自訂控制項,這裡我們直接讓這個控制項撐滿整個螢幕(對自訂控制項不熟悉的可以參看我之前的一篇文章:Android自訂控制項系列二:自訂開關按鈕(一))。觀察這個效果,發現應該需要重寫onTouchEvent和onDraw方法,通過在onTouchEvent中擷取觸摸的座標,然後以這個座標值為圓心來繪製我們需要的圖形,這個繪製過程就是調用的onDraw方法。


1、建立一個工程,定義一個WaterWave的類,繼承自View,作為一個自訂控制項;在資訊清單檔中將這個自訂控制項寫出來,直接填滿父表單。


2、在WaterWave類中,實現它的兩參建構函式:
package com.example.waterwavedemo.ui;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.os.Handler;import android.os.Message;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;public class WaterWave extends View {.../* * 1、兩參建構函式 */public WaterWave(Context context, AttributeSet attrs) {super(context, attrs);alpha = 0;radius = 0;initPaint();}...}



3、要使用自訂控制項,那麼一般都需要指定它的大小,這裡我們由於只需要其填滿表單,所以使用預設的onMeasure方法即可:


/** * onMeasure方法,確定控制項大小,這裡使用預設的 */@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// TODO Auto-generated method stubsuper.onMeasure(widthMeasureSpec, heightMeasureSpec);}



4、將這個自訂圖形畫出來,重寫onDraw方法,在這裡由於我們需要畫一個圈,所以這樣寫:


@Override/** * 畫出需要的圖形的方法,這個方法比較關鍵 */protected void onDraw(Canvas canvas) {canvas.drawCircle(xDown, yDown, radius, paint);}



其中的參數xDown和yDown是成員變數,代表按下時的x和y座標,這個座標所對應的點就是要繪製的圓環的圓心;radius參數也是成員變數,代表要繪製的圓環的半徑;


看到這裡還需要一個paint,是Paint類型的畫筆對象,這裡先將其定義成一個成員變數,由於onDraw方法在第一次自訂控制項顯示的時候就會被調用,所以這個paint需要我們在兩參的建構函式中就進行初始化,否則會報出null 指標異常;那麼我們這裡另外寫一個initPaint()方法來初始化我們的paint:

/** * 初始化paint */private void initPaint() {/* * 建立一個畫筆 */paint = new Paint();paint.setAntiAlias(true);paint.setStrokeWidth(width);// 設定是環形方式繪製paint.setStyle(Paint.Style.STROKE);System.out.println("alpha=" + alpha);paint.setAlpha(alpha);System.out.println("得到的透明度:" + paint.getAlpha());paint.setColor(Color.RED);}


5、觸摸定時重新整理

在onDraw方法之後,我們已經可以畫出這個圓環了,但是實際問題是,我們想要實現點擊的時候才在點擊的位置來畫一個圓環,那麼我們肯定需要獲得點擊的時候的座標xDown和yDown,所以肯定需要重寫onTouchEvent方法,另外我們需要在按下的時候,讓透明度是最不透明(alpha=255),在繪製的過程中,讓圓環的半徑(radius)不斷擴大,同時讓透明度不斷減小,直至完全透明(alpha=0),這個不斷變化的過程又需要每隔一段時間重新重新整理狀態和重新繪製圖形,所以我們這裡使用handler來處理:


@Override/** * 觸摸事件的方法 */public boolean onTouchEvent(MotionEvent event) {super.onTouchEvent(event);switch (event.getAction()) {case MotionEvent.ACTION_DOWN:radius = 0;alpha = MAX_ALPHA;width = radius / 4;xDown = (int) event.getX();yDown = (int) event.getY();handler.sendEmptyMessage(0);break;case MotionEvent.ACTION_MOVE:break;case MotionEvent.ACTION_UP:break;default:break;}return true;}



可以看到,我們這裡先只實現了ACTION_DOWN裡面的邏輯,在每一個按下的時候將半徑radius設定為0,透明度alpha設定為完全不透明,而寬度也為0,並且擷取按下的x和y座標,之後就使用handler發送了一個空訊息,讓handler去實現定時重新整理狀態和繪製圖形的工作,我們想讓圓環的透明度alpha撿到0的時候就不再繼續定時自動重新整理了,否則在每一次handleMessage的時候都先重新整理狀態值,然後繪製圖形:


private Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);switch (msg.what) {case 0:flushState();invalidate();if (alpha != 0) {// 如果透明度沒有到0,則繼續重新整理,否則停止重新整理handler.sendEmptyMessageDelayed(0, 50);}break;default:break;}}/** * 重新整理狀態 */private void flushState() {radius += 5;alpha -= 10;if (alpha < 0) {alpha = 0;}// System.out.println("alpha=" + alpha);width = radius / 4;paint.setAlpha(alpha);paint.setStrokeWidth(width);}};




我們可以看到,在handler中,我們重寫了handleMessage方法,在msg.what=0的時候,我們調用flushState()方法來重新整理狀態,和invalidate()方法來繪製圖形,,然後使用handler.sendEmptyMessageDelayed(0, 50);來每隔50毫秒重複一次上面的工作;其中invalidate()是Android提供的,而flushState()則需要我們自己來實現;

按照我們的需求,每一次狀態的重新整理工作flushState(),我們需要做如下幾件事:

(1)讓半徑增加

(2)讓透明度減少,並設定給paint;

(3)環形的寬度增加,並設定給paint

(4)對於透明度而言,最大值是255,但是這裡如果讓透明度減少到0以下,比如說-1,那麼實際上alpha的值不會是-1,而是255+(-1)=254,所以我們還需要加一個判斷條件,防止alpha<0


/** * 重新整理狀態 */private void flushState() {radius += 5;alpha -= 10;if (alpha < 0) {alpha = 0;}// System.out.println("alpha=" + alpha);width = radius / 4;paint.setAlpha(alpha);paint.setStrokeWidth(width);}



6、在兩參的建構函式中添加一些初始化工作:


public WaterWave(Context context, AttributeSet attrs) {super(context, attrs);alpha = 0;radius = 0;initPaint();}



至此,我們的第一步就基本完成了


二、實現多次點擊圓環同時存在,同時重新整理效果:


從面圖二中,我們不難發現,不論如何點擊,螢幕上都只會同時存在一個圓圈的效果,這是因為我們每次點擊的時候,都重新設定了圓心,而且所有圓形的參數都是成員變數,都是共用的;不僅如此,如果在上一個圓圈沒有消失的時候,就再次點擊,會讓新出現的圓圈變大的速度大大增加,這是因為使用handler.sendEmptyMessageDelayed(0,50)方法的原因,第二次點擊時會重複觸發這個方法,使得前後兩次點擊的handler.sendEmptyMessageDelayed()重疊生效,讓實際間隔遠遠小於50毫秒,所以重新整理速度快了很多

那麼我們現在就要解決上面兩個小問題,實現如的效果:


解決這兩個小問題的思路:

1、針對所有水波紋圓圈共用參數的問題:

方法就是建立一個內部類Wave,用於存放每個圓圈的參數,每一個圓圈都對應一個Wave對象,然後在onDraw方法裡面,同時重繪所有的圓圈視圖;那麼這裡就還需要一個List集合waveList,用於存放所有的wave對象,方便遍曆。


2、針對handler.sendEmptyMessageDelayed方法在後續點擊的時候不斷被調用,導致重新整理越來越快的問題。

這裡可以設定一個成員變數 boolean isStart;來標誌是不是第一次按下;因為我們在第一次按下的時候,肯定是希望開始定時重新整理,調用handler.sendEmptyMessageDelayed,讓圓環的狀態不斷變化。但是對於之後的點擊,我們其實只希望它立刻被重新整理一次,並被加入到waveList集合中,而並不需要發送一個handler的資訊來調用handler.sendEmptyMessageDelayed。所以在一開始的時候我們將其設定為true,而在第一次點擊時候將其設定為false,那麼在什麼時候將其設定為false呢,這裡牽涉到第三個問題:


3、對於waveList集合而言,如果一直點擊往集合裡面添加Wave對象,那麼無疑會讓這個集合越來越大,這個是我們不希望看到的,我們希望在圓環的透明度值alpha變為0,也就是完全透明的時候,讓其從waveList中remove掉,讓其能被記憶體回收回收掉,這樣如果點擊幾個點之後停止,點都會自動消失(alpha值減到0),那麼對應的Wave對象也會從waveList被移除,waveList的大小也會變成0,這個時候我們就可以停止handler.sendEmptyMessageDelayed方法繼續被調用,同時可以將isStart重新設為true。那麼isStart何時設為false呢?我們可以在flushState重新整理狀態的時候將其設為false,因為重新整理狀態的時候表明第一次點擊已經按下了。然後在onTouchEvent方法的ACTION_DWON條件下,如果isStart為true才發送handler的訊息,這代表第一次點擊,之後再點擊也不會發送而只是將wave對象添加到waveList中,因為第一次的時候調用flushState已經將isStart置為false了。

由於改動較大,代碼如下:

package com.example.waterwavedemo.ui;import java.util.ArrayList;import java.util.Collections;import java.util.List;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.os.Handler;import android.os.Message;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;public class WaterWave extends View {/** * 波形的List */private List<Wave> waveList;/** * 最大的不透明度,完全不透明 */private static final int MAX_ALPHA = 255;protected static final int FLUSH_ALL = -1;private boolean isStart = true;// /**// * 按下的時候x座標// */// private int xDown;// /**// * 按下的時候y的座標// */// private int yDown;// /**// * 用來表示圓環的半徑// */// private float radius;// private int alpha;/* * 1、兩參建構函式 */public WaterWave(Context context, AttributeSet attrs) {super(context, attrs);waveList = Collections.synchronizedList(new ArrayList<Wave>());}/** * onMeasure方法,確定控制項大小,這裡使用預設的 */@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);}@Override/** * 畫出需要的圖形的方法,這個方法比較關鍵 */protected void onDraw(Canvas canvas) {// 重繪所有圓環for (int i = 0; i < waveList.size(); i++) {Wave wave = waveList.get(i);canvas.drawCircle(wave.xDown, wave.yDown, wave.radius, wave.paint);}}/** * 初始化paint */private Paint initPaint(int alpha, float width) {/* * 建立一個畫筆 */Paint paint = new Paint();paint.setAntiAlias(true);paint.setStrokeWidth(width);// 設定是環形方式繪製paint.setStyle(Paint.Style.STROKE);// System.out.println("alpha=" + alpha);paint.setAlpha(alpha);// System.out.println("得到的透明度:" + paint.getAlpha());paint.setColor(Color.RED);return paint;}private Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);switch (msg.what) {case 0:flushState();invalidate();if (waveList != null && waveList.size() > 0) {handler.sendEmptyMessageDelayed(0, 50);}break;default:break;}}};/** * 重新整理狀態 */private void flushState() {for (int i = 0; i < waveList.size(); i++) {Wave wave = waveList.get(i);if (isStart == false && wave.alpha == 0) {waveList.remove(i);wave.paint = null;wave = null;continue;} else if (isStart == true) {isStart = false;}wave.radius += 5;wave.alpha -= 10;if (wave.alpha < 0) {wave.alpha = 0;}wave.width = wave.radius / 4;wave.paint.setAlpha(wave.alpha);wave.paint.setStrokeWidth(wave.width);}}// private Paint paint;// private float width;@Override/** * 觸摸事件的方法 */public boolean onTouchEvent(MotionEvent event) {super.onTouchEvent(event);switch (event.getAction()) {case MotionEvent.ACTION_DOWN:Wave wave = new Wave();wave.radius = 0;wave.alpha = MAX_ALPHA;wave.width = wave.radius / 4;wave.xDown = (int) event.getX();wave.yDown = (int) event.getY();wave.paint = initPaint(wave.alpha, wave.width);if (waveList.size() == 0) {isStart = true;}System.out.println("isStart=" + isStart);waveList.add(wave);// 點擊之後刷洗一次圖形invalidate();if (isStart) {handler.sendEmptyMessage(0);}break;case MotionEvent.ACTION_MOVE:break;case MotionEvent.ACTION_UP:break;default:break;}return true;}private class Wave {int waveX;int waveY;/** * 用來表示圓環的半徑 */float radius;Paint paint;/** * 按下的時候x座標 */int xDown;/** * 按下的時候y的座標 */int yDown;float width;int alpha;}}


三、實現完全效果(點擊和移動,顏色隨機,圓圈大小變化速度)

就是跟圖一的一樣了,主要做幾個小地方:

1、讓onTouchEvent裡面的ACTION_DOWN和ACTION_MOVE響應同樣的事件,實際上就是去掉ACTION_DOWN的break;然後將處理代碼寫到隨後的ACTION_MOVE中去即可


2、建立一個成員變數數組colors,裡面放自己想要的顏色,然後在initPaint方法的設定color的時候,使用paint.setColor(colors[(int) (Math.random() * (colors.length - 1))]);


3、控制波形的變化趨勢,這個看個人愛好,我是這樣做的:在flushState中:


wave.radius += waveList.size() - i;wave.width = (wave.radius / 3);wave.paint.setStrokeWidth(wave.width);// wave.alpha -= 10;if (wave.alpha < 0) {wave.alpha = 0;}// wave.width = wave.radius / 4;wave.paint.setAlpha(wave.alpha);

至此,就完成了自訂的水波紋效果了。存在的問題就是,如果在模擬器上,快速滑動,會有卡頓,在My PhoneNexus5上,還算流暢,應該跟記憶體無關,後續可能還會做一些最佳化。

Android自訂控制項系列五:自訂絢麗水波紋效果

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.