編輯:關於Android編程
今天我們來利用Android自定義控件實現一個比較有趣的效果:滑動水波紋。先來看看最終效果圖:

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

圖二
照例來說,還是一個自定義控件,這裡我們直接讓這個控件撐滿整個屏幕(對自定義控件不熟悉的可以參看我之前的一篇文章:Android自定義控件系列二:自定義開關按鈕(一))。觀察這個效果,發現應該需要重寫onTouchEvent和onDraw方法,通過在onTouchEvent中獲取觸摸的坐標,然後以這個坐標值為圓心來繪制我們需要的圖形,這個繪制過程就是調用的onDraw方法。
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();
}
...
}
/**
* onMeasure方法,確定控件大小,這裡使用默認的
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
/**
* 畫出需要的圖形的方法,這個方法比較關鍵
*/
protected void onDraw(Canvas canvas) {
canvas.drawCircle(xDown, yDown, radius, paint);
}
其中的參數xDown和yDown是成員變量,代表按下時的x和y坐標,這個坐標所對應的點就是要繪制的圓環的圓心;radius參數也是成員變量,代表要繪制的圓環的半徑;
看到這裡還需要一個paint,是Paint類型的畫筆對象,這裡先將其定義成一個成員變量,由於onDraw方法在第一次自定義控件顯示的時候就會被調用,所以這個paint需要我們在兩參的構造函數中就進行初始化,否則會報出空指針異常;那麼我們這裡另外寫一個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);
}
在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() {
@Override
public 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);
}
public WaterWave(Context context, AttributeSet attrs) {
super(context, attrs);
alpha = 0;
radius = 0;
initPaint();
}
至此,我們的第一步就基本完成了
從面圖二中,我們不難發現,不論如何點擊,屏幕上都只會同時存在一個圓圈的效果,這是因為我們每次點擊的時候,都重新設置了圓心,而且所有圓形的參數都是成員變量,都是共享的;不僅如此,如果在上一個圓圈沒有消失的時候,就再次點擊,會讓新出現的圓圈變大的速度大大增加,這是因為使用handler.sendEmptyMessageDelayed(0,50)方法的原因,第二次點擊時會重復觸發這個方法,使得前後兩次點擊的handler.sendEmptyMessageDelayed()重疊生效,讓實際間隔遠遠小於50毫秒,所以刷新速度快了很多
那麼我們現在就要解決上面兩個小問題,實現如下圖的效果:

解決這兩個小問題的思路:
方法就是新建一個內部類Wave,用於存放每個圓圈的參數,每一個圓圈都對應一個Wave對象,然後在onDraw方法裡面,同時重繪所有的圓圈視圖;那麼這裡就還需要一個List集合waveList,用於存放所有的wave對象,方便遍歷。
這裡可以設置一個成員變量 boolean isStart;來標志是不是第一次按下;因為我們在第一次按下的時候,肯定是希望開始定時刷新,調用handler.sendEmptyMessageDelayed,讓圓環的狀態不斷變化。但是對於之後的點擊,我們其實只希望它立刻被刷新一次,並被加入到waveList集合中,而並不需要發送一個handler的信息來調用handler.sendEmptyMessageDelayed。所以在一開始的時候我們將其設置為true,而在第一次點擊時候將其設置為false,那麼在什麼時候將其設置為false呢,這裡牽涉到第三個問題:
我們希望在圓環的透明度值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 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());
}
/**
* onMeasure方法,確定控件大小,這裡使用默認的
*/
@Override
protected 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() {
@Override
public 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;
}
}
效果圖就是跟圖一的一樣了,主要做幾個小地方:
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);
Android開發之電話撥號器和短信發送器實現方法
本文實例講述了Android開發之電話撥號器和短信發送器實現方法。分享給大家供大家參考,具體如下:電話撥號器實現原理:用戶輸入電話號碼,當點擊撥打的時候,由監聽對象捕獲,
Android播放動畫的方法示例
今天開始陸續整理一下一些常規的Android常用開發實用程序。 第一季:Android播放動畫的方法示例 1. 通常動畫都是gif圖像,推薦使用easygifanimat
安卓圖片加載之使用universalimageloader加載圓形圓角圖片
前言話說這universalimageloader加載圖片對搞過2年安卓程序都是用爛了再熟悉不過了,就是安卓新手也是百度就會有一大堆東西出來,今天為什麼這裡還要講使用un
Android實現果凍滑動效果的控件
前言在微信是的處理方法是讓用戶滑動,但最終還是回滾到最初的地方,這樣的效果很生動(畢竟成功還是取決於細節)。那麼在安卓我們要怎麼弄呢。下面為大家介紹一下JellyScro
[Android] 獲取WebView的頁面標題(Title)-----WebChromeClient.onReceivedTitle()方法的重寫
應用開發中需要獲取WebView當前頁面的標題,可能通過對WebChro