Android特效之水波紋的實現(xiàn)
前言
水波紋特效,想必大家或多或少見過,在我的印象中,大致有如下幾種:
支付寶 "咻咻咻" 式
流量球 "蕩漾" 式
真實的水波紋效果,基于Bitmap處理式
話不多說,先來看看效果:
填充式水波紋,間距相等
非填充式水波紋,間距相等
非填充式水波紋,間距不斷變大
填充式水波紋,間距不斷變小
想必大家已經(jīng)知道基本的原理了,就是用Canvas來畫嘛,但可不是簡單的畫哦,請往下看。
分析
這種類型的水波紋,其實無非就是畫圓而已,在給定的矩形中,一個個圓由最小半徑擴大到最大半徑,伴隨著透明度從1.0變?yōu)?.0。我們假定這種擴散是勻速的,則一個圓從創(chuàng)建(透明度為1.0)到消失(透明度為0.0)的時長就是定值,那么某一時刻某一個圓的半徑以及透明度完全可以由擴散時間(當前時間 - 創(chuàng)建時間)決定。
實現(xiàn)
按照上面的分析,我們寫出以下Circle
類來表示一個圓:
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
中,要有一個List來
保存當前正在顯示的圓:
private List<Circle> mCircleList = new ArrayList<Circle>();
我們定義一個start
方法,用來啟動擴散:
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)建一個圓 } } }; private void newCircle() { long currentTime = System.currentTimeMillis(); if (currentTime - mLastCreateTime < mSpeed) { return; } Circle circle = new Circle(); mCircleList.add(circle); invalidate(); mLastCreateTime = currentTime; }
start
方法只是簡單的創(chuàng)建了一個圓并添加到了mCircleList
中,同時開啟了循環(huán)創(chuàng)建圓的Runnable
,然后通知界面刷新,我們再看看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
方法遍歷了每一個Circle
,判斷Circle
的擴散時間是否超過了設(shè)定的擴散時間,如果是則移除,如果不是,則計算Circle
當前的透明度和半徑并繪制出來。我們添加了一個延時刷新來不斷重繪界面,以達到連續(xù)的波紋擴散效果。
現(xiàn)在運行程序,應(yīng)該能看到圖2中的效果了,不過有點別扭,按常識,水波的間距是越來越大的,如何做到呢?
技巧
要讓水波紋的半徑非勻速變大,我們只能去修改Circle.getCurrentRadius()
方法了。我們再次看看這個方法:
public float getCurrentRadius() { float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration; return mInitialRadius + percent * (mMaxRadius - mInitialRadius); }
percent
表示Circle
當前擴散時間和總擴散時間的一個百分比,考慮到當前擴散時間超過總擴散時間時Circle
會被移除,因此percent
的實際區(qū)間為[0, 1],看到[0, 1],我不知道大家想到的是什么,我首先想到的就是差值器(Interpolator
),我們可以通過定義差值器來實現(xiàn)對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
時,只需調(diào)用setInterpolator()
來定義不同的插值器即可實現(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; // 如果沒有設(shè)置mMaxRadius,可mMaxRadius = 最小長度 * mMaxRadiusRate; private float mMaxRadius; // 最大波紋半徑 private long mDuration = 2000; // 一個波紋從創(chuàng)建到消失的持續(xù)時間 private int mSpeed = 500; // 波紋的創(chuàng)建速度,每500ms創(chuàng)建一個 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); } /** * 開始 */ 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é)
想必大家看完這篇文章會覺得原來插值器還可以這么用。其實,有些時候我們使用系統(tǒng)提供的API,往往過于局限其中,有時候換個思路,說不定會得到奇妙的效果。以上就是在Android實現(xiàn)水波紋特效的全部內(nèi)容,希望對大家開發(fā)Android有所幫助。
。
相關(guān)文章
Flutter 使用fluro的轉(zhuǎn)場動畫進行頁面切換
在實際應(yīng)用中,我們常常會對不同的頁面采取不同的轉(zhuǎn)場動畫,以提高頁面切換過程中的用戶體驗。例如,微信的掃碼后在手機上確認登錄頁面就是從底部彈出的,而大部分頁面的跳轉(zhuǎn)都是從右向左滑入。通過這種形式區(qū)分不同的轉(zhuǎn)場場景,從而給用戶更多的趣味性以提高用戶體驗。2021-06-06Android Retrofit實現(xiàn)多圖片/文件、圖文上傳功能
Retrofit是Square開發(fā)的一個Android和Java的REST客戶端庫。這個庫非常簡單并且具有很多特性,相比其他的網(wǎng)絡(luò)庫,更容易讓初學者快速掌握2017-03-03Android App開發(fā)中Gradle構(gòu)建過程的配置方法
這篇文章主要介紹了Android App開發(fā)中Gradle構(gòu)建過程的配置方法,包括在Gradle中配置manifest的方法,需要的朋友可以參考下2016-06-06