Android特效之水波紋的實(shí)現(xiàn)
前言
水波紋特效,想必大家或多或少見(jiàn)過(guò),在我的印象中,大致有如下幾種:
支付寶 "咻咻咻" 式
流量球 "蕩漾" 式
真實(shí)的水波紋效果,基于Bitmap處理式
話(huà)不多說(shuō),先來(lái)看看效果:
填充式水波紋,間距相等

非填充式水波紋,間距相等

非填充式水波紋,間距不斷變大

填充式水波紋,間距不斷變小

想必大家已經(jīng)知道基本的原理了,就是用Canvas來(lái)畫(huà)嘛,但可不是簡(jiǎn)單的畫(huà)哦,請(qǐng)往下看。
分析
這種類(lèi)型的水波紋,其實(shí)無(wú)非就是畫(huà)圓而已,在給定的矩形中,一個(gè)個(gè)圓由最小半徑擴(kuò)大到最大半徑,伴隨著透明度從1.0變?yōu)?.0。我們假定這種擴(kuò)散是勻速的,則一個(gè)圓從創(chuàng)建(透明度為1.0)到消失(透明度為0.0)的時(shí)長(zhǎng)就是定值,那么某一時(shí)刻某一個(gè)圓的半徑以及透明度完全可以由擴(kuò)散時(shí)間(當(dāng)前時(shí)間 - 創(chuàng)建時(shí)間)決定。
實(shí)現(xiàn)
按照上面的分析,我們寫(xiě)出以下Circle類(lèi)來(lái)表示一個(gè)圓:
private class Circle {
private long mCreateTime;
public Circle() {
this.mCreateTime = System.currentTimeMillis();
}
public int getAlpha() {
float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration;
return (int) ((1.0f - percent) * 255);
}
public float getCurrentRadius() {
float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration;
return mInitialRadius + percent * (mMaxRadius - mInitialRadius);
}
}
自然而然,在WaveView中,要有一個(gè)List來(lái)保存當(dāng)前正在顯示的圓:
private List<Circle> mCircleList = new ArrayList<Circle>();
我們定義一個(gè)start方法,用來(lái)啟動(dòng)擴(kuò)散:
public void start() {
if (!mIsRunning) {
mIsRunning = true;
mCreateCircle.run();
}
}
private Runnable mCreateCircle = new Runnable() {
@Override
public void run() {
if (mIsRunning) {
newCircle();
postDelayed(mCreateCircle, mSpeed); // 每隔mSpeed毫秒創(chuàng)建一個(gè)圓
}
}
};
private void newCircle() {
long currentTime = System.currentTimeMillis();
if (currentTime - mLastCreateTime < mSpeed) {
return;
}
Circle circle = new Circle();
mCircleList.add(circle);
invalidate();
mLastCreateTime = currentTime;
}
start方法只是簡(jiǎn)單的創(chuàng)建了一個(gè)圓并添加到了mCircleList中,同時(shí)開(kāi)啟了循環(huán)創(chuàng)建圓的Runnable,然后通知界面刷新,我們?cè)倏纯?code>onDraw方法:
protected void onDraw(Canvas canvas) {
Iterator<Circle> iterator = mCircleList.iterator();
while (iterator.hasNext()) {
Circle circle = iterator.next();
if (System.currentTimeMillis() - circle.mCreateTime < mDuration) {
mPaint.setAlpha(circle.getAlpha());
canvas.drawCircle(getWidth() / 2, getHeight() / 2, circle.getCurrentRadius(), mPaint);
} else {
iterator.remove();
}
}
if (mCircleList.size() > 0) {
postInvalidateDelayed(10);
}
}
onDraw方法遍歷了每一個(gè)Circle,判斷Circle的擴(kuò)散時(shí)間是否超過(guò)了設(shè)定的擴(kuò)散時(shí)間,如果是則移除,如果不是,則計(jì)算Circle當(dāng)前的透明度和半徑并繪制出來(lái)。我們添加了一個(gè)延時(shí)刷新來(lái)不斷重繪界面,以達(dá)到連續(xù)的波紋擴(kuò)散效果。
現(xiàn)在運(yùn)行程序,應(yīng)該能看到圖2中的效果了,不過(guò)有點(diǎn)別扭,按常識(shí),水波的間距是越來(lái)越大的,如何做到呢?
技巧
要讓水波紋的半徑非勻速變大,我們只能去修改Circle.getCurrentRadius()方法了。我們?cè)俅慰纯催@個(gè)方法:
public float getCurrentRadius() {
float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration;
return mInitialRadius + percent * (mMaxRadius - mInitialRadius);
}
percent表示Circle當(dāng)前擴(kuò)散時(shí)間和總擴(kuò)散時(shí)間的一個(gè)百分比,考慮到當(dāng)前擴(kuò)散時(shí)間超過(guò)總擴(kuò)散時(shí)間時(shí)Circle會(huì)被移除,因此percent的實(shí)際區(qū)間為[0, 1],看到[0, 1],我不知道大家想到的是什么,我首先想到的就是差值器(Interpolator),我們可以通過(guò)定義差值器來(lái)實(shí)現(xiàn)對(duì)Circle半徑變化的控制!
我們修改代碼:
private Interpolator mInterpolator = new LinearInterpolator();
public void setInterpolator(Interpolator interpolator) {
mInterpolator = interpolator;
if (mInterpolator == null) {
mInterpolator = new LinearInterpolator();
}
}
private class Circle {
private long mCreateTime;
public Circle() {
this.mCreateTime = System.currentTimeMillis();
}
public int getAlpha() {
float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration;
return (int) ((1.0f - mInterpolator.getInterpolation(percent)) * 255);
}
public float getCurrentRadius() {
float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration;
return mInitialRadius + mInterpolator.getInterpolation(percent) * (mMaxRadius - mInitialRadius);
}
}
這樣,外部使用WaveView時(shí),只需調(diào)用setInterpolator()來(lái)定義不同的插值器即可實(shí)現(xiàn)不同的效果。
圖3效果的代碼:
mWaveView = (WaveView) findViewById(R.id.wave_view);
mWaveView.setDuration(5000);
mWaveView.setStyle(Paint.Style.STROKE);
mWaveView.setSpeed(400);
mWaveView.setColor(Color.parseColor("#ff0000"));
mWaveView.setInterpolator(new AccelerateInterpolator(1.2f));
mWaveView.start();
圖4效果的代碼:
mWaveView = (WaveView) findViewById(R.id.wave_view);
mWaveView.setDuration(5000);
mWaveView.setStyle(Paint.Style.FILL);
mWaveView.setColor(Color.parseColor("#ff0000"));
mWaveView.setInterpolator(new LinearOutSlowInInterpolator());
mWaveView.start();
附上WaveView的所有代碼:
/**
* 水波紋特效
* Created by hackware on 2016/6/17.
*/
public class WaveView extends View {
private float mInitialRadius; // 初始波紋半徑
private float mMaxRadiusRate = 0.85f; // 如果沒(méi)有設(shè)置mMaxRadius,可mMaxRadius = 最小長(zhǎng)度 * mMaxRadiusRate;
private float mMaxRadius; // 最大波紋半徑
private long mDuration = 2000; // 一個(gè)波紋從創(chuàng)建到消失的持續(xù)時(shí)間
private int mSpeed = 500; // 波紋的創(chuàng)建速度,每500ms創(chuàng)建一個(gè)
private Interpolator mInterpolator = new LinearInterpolator();
private List<Circle> mCircleList = new ArrayList<Circle>();
private boolean mIsRunning;
private boolean mMaxRadiusSet;
private Paint mPaint;
private long mLastCreateTime;
private Runnable mCreateCircle = new Runnable() {
@Override
public void run() {
if (mIsRunning) {
newCircle();
postDelayed(mCreateCircle, mSpeed);
}
}
};
public WaveView(Context context) {
this(context, null);
}
public WaveView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
setStyle(Paint.Style.FILL);
}
public void setStyle(Paint.Style style) {
mPaint.setStyle(style);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (!mMaxRadiusSet) {
mMaxRadius = Math.min(w, h) * mMaxRadiusRate / 2.0f;
}
}
public void setMaxRadiusRate(float maxRadiusRate) {
this.mMaxRadiusRate = maxRadiusRate;
}
public void setColor(int color) {
mPaint.setColor(color);
}
/**
* 開(kāi)始
*/
public void start() {
if (!mIsRunning) {
mIsRunning = true;
mCreateCircle.run();
}
}
/**
* 停止
*/
public void stop() {
mIsRunning = false;
}
protected void onDraw(Canvas canvas) {
Iterator<Circle> iterator = mCircleList.iterator();
while (iterator.hasNext()) {
Circle circle = iterator.next();
if (System.currentTimeMillis() - circle.mCreateTime < mDuration) {
mPaint.setAlpha(circle.getAlpha());
canvas.drawCircle(getWidth() / 2, getHeight() / 2, circle.getCurrentRadius(), mPaint);
} else {
iterator.remove();
}
}
if (mCircleList.size() > 0) {
postInvalidateDelayed(10);
}
}
public void setInitialRadius(float radius) {
mInitialRadius = radius;
}
public void setDuration(long duration) {
this.mDuration = duration;
}
public void setMaxRadius(float maxRadius) {
this.mMaxRadius = maxRadius;
mMaxRadiusSet = true;
}
public void setSpeed(int speed) {
mSpeed = speed;
}
private void newCircle() {
long currentTime = System.currentTimeMillis();
if (currentTime - mLastCreateTime < mSpeed) {
return;
}
Circle circle = new Circle();
mCircleList.add(circle);
invalidate();
mLastCreateTime = currentTime;
}
private class Circle {
private long mCreateTime;
public Circle() {
this.mCreateTime = System.currentTimeMillis();
}
public int getAlpha() {
float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration;
return (int) ((1.0f - mInterpolator.getInterpolation(percent)) * 255);
}
public float getCurrentRadius() {
float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration;
return mInitialRadius + mInterpolator.getInterpolation(percent) * (mMaxRadius - mInitialRadius);
}
}
public void setInterpolator(Interpolator interpolator) {
mInterpolator = interpolator;
if (mInterpolator == null) {
mInterpolator = new LinearInterpolator();
}
}
}
總結(jié)
想必大家看完這篇文章會(huì)覺(jué)得原來(lái)插值器還可以這么用。其實(shí),有些時(shí)候我們使用系統(tǒng)提供的API,往往過(guò)于局限其中,有時(shí)候換個(gè)思路,說(shuō)不定會(huì)得到奇妙的效果。以上就是在Android實(shí)現(xiàn)水波紋特效的全部?jī)?nèi)容,希望對(duì)大家開(kāi)發(fā)Android有所幫助。
。
- Android實(shí)現(xiàn)點(diǎn)擊Button產(chǎn)生水波紋效果
- Android仿水波紋流量球進(jìn)度條控制器
- Android項(xiàng)目實(shí)戰(zhàn)手把手教你畫(huà)圓形水波紋loadingview
- Android實(shí)現(xiàn)水波紋特效
- Android實(shí)現(xiàn)水波紋效果
- Android自定義View 實(shí)現(xiàn)水波紋動(dòng)畫(huà)引導(dǎo)效果
- Android 自定義view實(shí)現(xiàn)水波紋動(dòng)畫(huà)效果
- Android實(shí)現(xiàn)自定義華麗的水波紋效果
- Android自定義View控件實(shí)現(xiàn)多種水波紋漣漪擴(kuò)散效果
- Android實(shí)現(xiàn)漸變色水波紋效果
相關(guān)文章
AndroidQ(10)分區(qū)存儲(chǔ)完美適配方法
這篇文章主要介紹了AndroidQ(10)分區(qū)存儲(chǔ)完美適配方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
Android自定義控件實(shí)現(xiàn)九宮格解鎖
這篇文章主要為大家詳細(xì)介紹了Android自定義控件實(shí)現(xiàn)九宮格解鎖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06
Android中ViewFlipper和AdapterViewFlipper使用的方法實(shí)例
ViewFlipper和AdapterViewFlipper是Android自帶的一個(gè)多頁(yè)面管理控件,下面這篇文章主要給大家介紹了關(guān)于Android中ViewFlipper和AdapterViewFlipper使用的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-05-05
詳解Android .9.png “點(diǎn)九”圖片的使用
這篇文章主要為大家詳細(xì)介紹了Android .9.png “點(diǎn)九”圖片的使用方法,感興趣的小伙伴們可以參考一下2016-09-09
Flutter滾動(dòng)組件之ListView使用方法詳解
這篇文章主要為大家詳細(xì)介紹了Flutter滾動(dòng)組件之ListView的使用方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
將cantk runtime嵌入到現(xiàn)有的APP中的方法
今天小編就為大家分享一篇關(guān)于將cantk runtime嵌入到現(xiàn)有的APP中的方法,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12
Android使用Walle實(shí)現(xiàn)多渠道打包功能的實(shí)現(xiàn)示例
這篇文章主要介紹了Android使用Walle實(shí)現(xiàn)多渠道打包功能的實(shí)現(xiàn)示例,幫助大家更好的理解和學(xué)習(xí)使用Android開(kāi)發(fā),感興趣的朋友可以了解下2021-04-04
Android 實(shí)現(xiàn)截屏功能的實(shí)例
這篇文章主要介紹了Android 實(shí)現(xiàn)截屏功能的實(shí)例的相關(guān)資料,這里實(shí)現(xiàn)截屏的實(shí)例在代碼中注釋非常清楚,希望能幫助到大家,需要的朋友可以參考下2017-08-08

