Android利用繪制緩沖實(shí)現(xiàn)代碼雨效果
前言
看過很多代碼雨的前端實(shí)現(xiàn),卻很少看到過Android代碼雨效果的實(shí)現(xiàn),當(dāng)然 open gl es的實(shí)現(xiàn)是有的。一個(gè)主要的原因是,在Android Canvas繪制時(shí),很少有人考慮使用繪制緩沖,我們之前有一篇文章,就實(shí)現(xiàn)了基于繪制緩沖的煙花效果,當(dāng)然,本篇也是利用繪制緩沖來實(shí)現(xiàn)這種效果。
為什么需要繪制緩沖呢?原因如下:
繪制緩沖可以保留上次的繪制數(shù)據(jù)為下次繪制使用。
那,既然使用了繪制緩沖,我們理論上得使用Surface,為什么這么說呢,因?yàn)閟etLayerType開啟緩沖會(huì)影響到繪制效果,其次繪制也比較耗時(shí),因此為了避免這種情況,使用Surface是必要的優(yōu)化手段。
當(dāng)然,相較于上次的《基于繪制緩沖的煙花效果》,這一篇我們的內(nèi)容其實(shí)并不多。
效果預(yù)覽

我們可以看到,下雨的時(shí)候,拖尾部分會(huì)慢慢隱藏,主要技巧是多次覆蓋半透明黑色,在視覺上反應(yīng)的是漸漸變暗的alpha動(dòng)畫,上一篇的煙花效果也使用了這種技巧。
實(shí)現(xiàn)
下面是具體實(shí)現(xiàn),在這個(gè)過程我們會(huì)使用到Bitmap作為可視化緩沖,而我們知道,Surface自帶雙緩沖,但是為什么不用呢?
原因是Surface的雙緩沖會(huì)導(dǎo)致雨滴閃爍,因?yàn)閒ront和back會(huì)交替繪制,導(dǎo)致繪制效果不連續(xù),因此,只能避免這種問題了。
繪制范圍確定
在實(shí)現(xiàn)代碼雨時(shí),我們肯定要初始化畫筆等,但是,一個(gè)需要思考的問題是,如何控制雨滴重復(fù)利用,其次如何知道繪制多大范圍。
首先,我們要處理的一個(gè)問題是,需要下“多少行雨”,在這里我們可以測(cè)量字體,讓屏幕寬度除以字體寬度,但是一般情況下字體寬度可以能不一致,一個(gè)比較合理的方案是,獲取Paint的textSize,當(dāng)然,也可以遍歷字符,獲取最大的寬度或者高度。
// 雨滴行數(shù) int columCount = (int) (getContentWidth() / mPaint.getTextSize());
不過,我們看效果就能發(fā)現(xiàn)一個(gè)現(xiàn)象,文本繪制之后,位置是不變的,只有“雨滴頭”似乎在移動(dòng),雨滴頭也不會(huì)覆蓋到原來的位置,因此,我們需要保存每行雨滴頭的"高度的倍數(shù)"。
drops = new int[columCount];
文案
我們給一段簡(jiǎn)單的文案,重復(fù)繪制即可。
char[] characters = "張三愛李四,Alice love you".toCharArray();
繪制實(shí)現(xiàn)
下面是繪制邏輯的核心代碼
將上一次的效果變暗
canvas.drawColor(argb(0.1f, 0f, 0f, 0f));//加深暗度,使得以前繪制的區(qū)域變暗
讓高度遞增
雨肯定是要從上到下,那么雨滴的高度是需要遞增的,另外,移出界面的當(dāng)然要重置“高度的倍數(shù)”到0,不過這里為了保證隨機(jī)性,我們加點(diǎn)隨機(jī)變量
if (drops[i] * textSize > height && Math.random() > 0.975) {
drops[i] = 0; //重置高度的倍數(shù)
}
drops[i]++;
文本的繪制
文本的繪制比較隨機(jī)了,下面繪制,但要保證高度為:當(dāng)前高度 x textSize,寬度為 i x textSize
int index = (int) Math.floor(Math.random() * characters.length); canvas.drawText(characters, index, 1, i * textSize, drops[i] * textSize, paint);
下面是核心代碼實(shí)現(xiàn)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
canvas = surface.lockHardwareCanvas();
} else {
canvas = surface.lockCanvas(null);
}
drawChars(mBitmapCanvas, mPaint);
canvas.drawBitmap(mBitmapCanvas.getBitmap(), matrix, null);
surface.unlockCanvasAndPost(canvas);
SurfaceView使用
本篇我們使用了SurfaceView,SurfaceView性能好,但是兼容性差,不適合可滑動(dòng)和需要調(diào)整大小的界面,當(dāng)然,我們最需要注意的一點(diǎn)是,使用SurfaceView時(shí),是沒有辦法控制Surface的銷毀的,因此,在回調(diào)函數(shù)中,必須要加鎖。
public void surfaceDestroyed(SurfaceHolder holder) {
Surface drawSurface = surface;
if (drawSurface == null) {
return;
}
synchronized (drawSurface) {
isRunning = false;
}
if (drawThread != null) {
try {
drawThread.interrupt();
} catch (Throwable e) {
e.printStackTrace();
}
}
drawThread = null;
}
性能優(yōu)化
我們雖然使用了lockHardwareCanvas,但是,在大范圍繪制還是有些卡頓,繪制緩沖是有性能損耗的,那么怎么才能優(yōu)化呢?
繪制的優(yōu)化手段當(dāng)然是減少重繪,但是最重要的是減少繪制面積。
在這里,我們可以換一種思路,繪制等比例前縮小Bitmap,繪制后在往Surface繪制時(shí)放大Bitmap,當(dāng)然,這種會(huì)產(chǎn)生鋸齒,不過,解決方法是有的,我們這里使用過濾工具,僅僅相應(yīng)的優(yōu)化即可。
mBitmapPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); mBitmapPaint.setAntiAlias(true); mBitmapPaint.setDither(true);
下面我們改造一下,縮小之后,繪制時(shí)使用Matrix放大
Canvas canvas = null;
if (sizeChanged || drops == null) {
if (mBitmapCanvas != null) {
mBitmapCanvas.recycle();
}
//縮小繪制緩沖面積
mBitmapCanvas = new BitmapCanvas(Bitmap.createBitmap(getWidth() / 2, getHeight() / 2, Bitmap.Config.RGB_565));
int columCount = (int) (mBitmapCanvas.getBitmap().getWidth() / mPaint.getTextSize());
drops = new int[columCount];
Arrays.fill(drops, 1);
sizeChanged = false;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
canvas = surface.lockHardwareCanvas();
} else {
canvas = surface.lockCanvas(null);
}
drawChars(mBitmapCanvas, mPaint);
matrix.reset();
//放大
matrix.setScale(2, 2);
canvas.drawBitmap(mBitmapCanvas.getBitmap(), matrix, mBitmapPaint);
surface.unlockCanvasAndPost(canvas);
經(jīng)過優(yōu)化之后,流暢度當(dāng)然會(huì)大幅提升。
總結(jié)
到這里本篇就結(jié)束了,上一篇,我們實(shí)現(xiàn)Android上繪制緩沖的煙花效果,本篇,我們通過代碼雨加深對(duì)繪制緩沖的理解,其次,也提供了優(yōu)化方法,希望本篇內(nèi)容對(duì)你有所幫助。
下面是源碼:
public class CodeRainView extends SurfaceView implements Runnable, SurfaceHolder.Callback {
private TextPaint mPaint;
private TextPaint mBitmapPaint;
private Surface surface;
private boolean sizeChanged;
private BitmapCanvas mBitmapCanvas;
{
initPaint();
}
public CodeRainView(Context context) {
this(context, null);
}
public CodeRainView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
getHolder().addCallback(this);
}
private void initPaint() {
//否則提供給外部紋理繪制
mPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setTextSize(20);
mBitmapPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
mBitmapPaint.setAntiAlias(true);
mBitmapPaint.setDither(true);
PaintCompat.setBlendMode(mPaint, BlendModeCompat.PLUS);
}
Thread drawThread = null;
char[] characters = "張三愛李四,Alice love you".toCharArray();
int[] drops = null;
private volatile boolean isRunning = false;
private final Object lockSurface = new Object();
Matrix matrix = new Matrix();
@Override
public void run() {
while (true) {
synchronized (surface) {
try {
Thread.sleep(32);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!isRunning || Thread.currentThread().isInterrupted()) {
synchronized (lockSurface) {
if (surface != null && surface.isValid()) {
surface.release();
}
surface = null;
}
break;
}
Canvas canvas = null;
if (sizeChanged || drops == null) {
if (mBitmapCanvas != null) {
mBitmapCanvas.recycle();
}
mBitmapCanvas = new BitmapCanvas(Bitmap.createBitmap(getWidth() / 2, getHeight() / 2, Bitmap.Config.RGB_565));
int columCount = (int) (mBitmapCanvas.getBitmap().getWidth() / mPaint.getTextSize());
drops = new int[columCount];
Arrays.fill(drops, 1);
sizeChanged = false;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
canvas = surface.lockHardwareCanvas();
} else {
canvas = surface.lockCanvas(null);
}
drawChars(mBitmapCanvas, mPaint);
matrix.reset();
matrix.setScale(2, 2);
canvas.drawBitmap(mBitmapCanvas.getBitmap(), matrix, mBitmapPaint);
surface.unlockCanvasAndPost(canvas);
}
}
}
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
this.drawThread = new Thread(this);
this.surface = holder.getSurface();
this.isRunning = true;
this.drawThread.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Surface drawSurface = surface;
if (drawSurface == null) {
return;
}
synchronized (drawSurface) {
isRunning = false;
}
if (drawThread != null) {
try {
drawThread.interrupt();
} catch (Throwable e) {
e.printStackTrace();
}
}
drawThread = null;
}
static class BitmapCanvas extends Canvas {
Bitmap bitmap;
public BitmapCanvas(Bitmap bitmap) {
super(bitmap);
this.bitmap = bitmap;
}
public Bitmap getBitmap() {
return bitmap;
}
public void recycle() {
if (bitmap == null || bitmap.isRecycled()) {
return;
}
bitmap.recycle();
}
}
void drawChars(Canvas canvas, Paint paint) {
canvas.drawColor(argb(0.1f, 0f, 0f, 0f));
paint.setColor(0xFF00FF00);
int height = getHeight();
float textSize = paint.getTextSize();
for (int i = 0; i < drops.length; i++) {
int index = (int) Math.floor(Math.random() * characters.length);
canvas.drawText(characters, index, 1, i * textSize, drops[i] * textSize, paint);
if (drops[i] * textSize > height && Math.random() > 0.975) {
drops[i] = 0;
}
drops[i]++;
}
}
public static int argb(float alpha, float red, float green, float blue) {
return ((int) (alpha * 255.0f + 0.5f) << 24) |
((int) (red * 255.0f + 0.5f) << 16) |
((int) (green * 255.0f + 0.5f) << 8) |
(int) (blue * 255.0f + 0.5f);
}
}
以上就是Android利用繪制緩沖實(shí)現(xiàn)代碼雨效果的詳細(xì)內(nèi)容,更多關(guān)于Android代碼雨的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android 開機(jī)應(yīng)用掃描相關(guān)總結(jié)
本篇文章只是作為指南引導(dǎo)去看PkMS,不會(huì)貼大段代碼進(jìn)行分析,更多是基于方法分析實(shí)現(xiàn)的邏輯,另外就是代碼是基于Android 11,與Android 10之前代碼有比較大的差別。2021-05-05
Android開發(fā)之基于DialogFragment創(chuàng)建對(duì)話框的方法示例
這篇文章主要介紹了Android開發(fā)之基于DialogFragment創(chuàng)建對(duì)話框的方法,結(jié)合實(shí)例形式分析了DialogFragment創(chuàng)建對(duì)話框的具體功能與布局相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-08-08
Android通過LIstView顯示文件列表的兩種方法介紹
過ListView顯示SD卡中的文件列表一共有兩種方法,一是:通過繼承ListActivity顯示;二是:利用BaseAdapter顯示,具體實(shí)現(xiàn)如下,感興趣的朋友可以參考下哈2013-06-06
Android和JavaScript相互調(diào)用的方法
這篇文章主要介紹了Android和JavaScript相互調(diào)用的方法,實(shí)例分析了Android的WebView執(zhí)行JavaScript及JavaScript訪問Android的技巧,需要的朋友可以參考下2015-12-12
Android Dialog 設(shè)置字體大小的具體方法
這篇文章介紹了Android Dialog 設(shè)置字體大小的具體方法,希望能幫助到有同樣需求的朋友,可能我的方法不是最好的,也希望有朋友指點(diǎn)2013-09-09
Android編程實(shí)現(xiàn)GridView控件點(diǎn)擊圖片變暗效果的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)GridView控件點(diǎn)擊圖片變暗效果的方法,簡(jiǎn)單分析了GridView控件事件響應(yīng)及屬性操作的相關(guān)技巧,需要的朋友可以參考下2017-06-06
Android編程實(shí)現(xiàn)全局獲取Context及使用Intent傳遞對(duì)象的方法詳解
這篇文章主要介紹了Android編程實(shí)現(xiàn)全局獲取Context及使用Intent傳遞對(duì)象的方法,結(jié)合實(shí)例形式分析了Android全局Context的獲取及Intent傳遞對(duì)象的具體操作方法,需要的朋友可以參考下2017-08-08
Android studio創(chuàng)建第一個(gè)app
這篇文章主要為大家詳細(xì)介紹了如何使用Android studio創(chuàng)建你的第一個(gè)項(xiàng)目Hello World,感興趣的小伙伴們可以參考一下2016-05-05

