Android利用SurfaceView實(shí)現(xiàn)下雨的天氣動(dòng)畫(huà)效果
首先是最終實(shí)現(xiàn)的效果圖:

先分析一下雨滴的實(shí)現(xiàn):
- 每個(gè)雨滴其實(shí)就是一條線(xiàn),通過(guò)
canvas.drawLine()繪制 - 線(xiàn)(雨滴)的長(zhǎng)度、寬度、下落速度、透明度以及位置都是在一定范圍內(nèi)隨機(jī)生成
- 每 draw 一次然后改變雨滴的位置然后重繪即可實(shí)現(xiàn)雨滴的下落效果
分析完了,那么可以直接寫(xiě)一個(gè)類(lèi)直接繼承 View ,然后重寫(xiě) onDraw() 嗎?可以看到效果圖中的雨滴的下落速度很快,那么意味著每一幀都要調(diào)用 onDraw() 一次使其重新繪制一次,假如你的 onDraw() 方法里面的渲染代碼稍微有點(diǎn)費(fèi)時(shí),而 View 的 onDraw() 方法調(diào)用是在 UI 線(xiàn)程中,那么繪制出來(lái)的效果就不會(huì)那么流暢,甚至還會(huì)阻塞 UI 線(xiàn)程,所以為了更流暢的效果并且不阻塞 UI 線(xiàn)程,我們這里使用 SurfaceView 來(lái)實(shí)現(xiàn)。
初識(shí) SurfaceView
SurfaceView 直接繼承自 View,View 必須在 UI 線(xiàn)程中繪制,而 SurfaceView 不同于 View,它可以在非 UI 線(xiàn)程中繪制并顯示在界面上,這意味著你可以自己新開(kāi)一個(gè)線(xiàn)程,然后把繪制渲染的代碼放在該線(xiàn)程中。
Surface 是 Z 軸排序的,SurfaceView 的 Z 軸位置小于它的宿主 Window,代表它總是在自己所在 Window 的后面,既然在后面,那么是怎么顯示的呢?SurfaceView 在其 Window 中打出一個(gè)“孔”(其實(shí)就是在其宿主 Window 上設(shè)置了一塊透明區(qū)域來(lái)使其能夠顯示),意味著他的兄弟節(jié)點(diǎn)的 View 會(huì)覆蓋它,例如你可以在 SurfaceView 上方放置按鈕,文本等控件。
要想訪(fǎng)問(wèn)下面的 Surface ,可以通過(guò) Android 提供給我們的 SurfaceHolder 接口??梢哉{(diào)用 SurfaceView 的 getHolder() 來(lái)獲取。
SurfaceView 是有生命周期的,我們必須在它生命周期期間進(jìn)行執(zhí)行繪制代碼,所以我們需要監(jiān)聽(tīng) SurfaceView 的狀態(tài)(例如創(chuàng)建以及銷(xiāo)毀),這里 Android 為我們提供了 SurfaceHolder.Callback 這個(gè)接口來(lái)可以讓我們方便的監(jiān)聽(tīng) SurfaceView 的狀態(tài)。
那么下面看下 SurfaceHolder.Callback 接口
public interface Callback {
// SurfaceView 創(chuàng)建時(shí)調(diào)用(SurfaceView的窗口可見(jiàn)時(shí))
public void surfaceCreated(SurfaceHolder holder);
// SurfaceView 改變時(shí)調(diào)用
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height);
// SurfaceView 銷(xiāo)毀時(shí)調(diào)用(SurfaceView的窗口不可見(jiàn)時(shí))
public void surfaceDestroyed(SurfaceHolder holder);
}
我們的繪制代碼需要在 surfaceCreated 和 surfaceDestroyed 之間執(zhí)行,否則無(wú)效,SurfaceHolder.Callback的回調(diào)方法是執(zhí)行在 UI 線(xiàn)程中的,繪制線(xiàn)程需要我們自己手動(dòng)創(chuàng)建。
具體可看官方文檔:https://developer.android.google.cn/reference/android/view/SurfaceView.html
View 和 SurfaceView 的使用場(chǎng)景
- View 適合那些與用戶(hù)交互并且渲染時(shí)間不是很長(zhǎng)的控件,因?yàn)?View 的繪制和用戶(hù)交互都處在 UI 線(xiàn)程中。
- SurfaceView 適合迅速的更新界面或者渲染時(shí)間比較長(zhǎng)以至于影響到用戶(hù)體驗(yàn)的場(chǎng)景。
使用 SurfaceView(實(shí)現(xiàn))
這里我們和自定義 View 類(lèi)似,寫(xiě)一個(gè)類(lèi) DynamicWeatherView 繼承自 SurfaceView,然后為了監(jiān)聽(tīng) SurfaceView 的狀態(tài),所以我們還需要實(shí)現(xiàn) SurfaceHolder.Callback 接口來(lái)監(jiān)聽(tīng) SurfaceView 的狀態(tài),接口的回調(diào)具體時(shí)機(jī)上面也已經(jīng)介紹過(guò)了。
public class DynamicWeatherView extends SurfaceView implements SurfaceHolder.Callback{
public DynamicWeatherView(Context context) {
this(context, null);
}
public DynamicWeatherView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DynamicWeatherView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
// SurfaceView 創(chuàng)建時(shí)調(diào)用(可見(jiàn))
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
// SurfaceView 銷(xiāo)毀時(shí)調(diào)用(不可見(jiàn))
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
}
上面也提到了,控制 Surface 我們需要 SurfaceHolder 對(duì)象,調(diào)用 SurfaceView 的 getHolder() 即可獲得,然后為這個(gè) SurfaceHolder 添加一個(gè) SurfaceHolder.Callback 回調(diào),這里就是 DynamicWeatherView 當(dāng)前對(duì)象
private SurfaceHolder mHolder;
mHolder = getHolder(); mHolder.addCallback(this); mHolder.setFormat(PixelFormat.TRANSPARENT);
然后實(shí)現(xiàn)我們的繪制線(xiàn)程:
private class DrawThread extends Thread {
// 用來(lái)停止線(xiàn)程的標(biāo)記
private boolean isRunning = false;
public void setRunning(boolean running) {
isRunning = running;
}
@Override
public void run() {
Canvas canvas;
// 無(wú)限循環(huán)繪制
while (isRunning) {
if (mType != null && mViewWidth != 0 && mViewHeight != 0) {
canvas = mHolder.lockCanvas();
if (canvas != null) {
mType.onDraw(canvas);
if (isRunning) {
mHolder.unlockCanvasAndPost(canvas);
} else {
// 停止線(xiàn)程
break;
}
SystemClock.sleep(1);
}
}
}
}
}
從上面的代碼可以看出 SurfaceView 的更新流程具體為:
// 鎖定畫(huà)布并獲得 canvas canvas = mHolder.lockCanvas(); // 在 canvas 上進(jìn)行繪制 mType.onDraw(canvas); // 解除鎖定并提交更改 mHolder.unlockCanvasAndPost(canvas);
繪制線(xiàn)程代碼量不多,因?yàn)榫唧w的繪制代碼在 mType.onDraw(canvas)中,mType 是我們自己定義的一個(gè)接口,代表一種天氣類(lèi)型:
public interface WeatherType {
void onDraw(Canvas canvas);
void onSizeChanged(Context context, int w, int h);
}
這樣要想實(shí)現(xiàn)不同的天氣類(lèi)型,只要實(shí)現(xiàn)這個(gè)接口重寫(xiě) onDraw 和 onSizeChanged 方法即可,這里我們實(shí)現(xiàn)的是下雨的效果,所以實(shí)現(xiàn)了一個(gè) RainTypeImpl 類(lèi):
public class RainTypeImpl extends BaseType {
// 背景
private Drawable mBackground;
// 雨滴集合
private ArrayList<RainHolder> mRains;
// 畫(huà)筆
private Paint mPaint;
public RainTypeImpl(Context context, DynamicWeatherView dynamicWeatherView) {
super(context, dynamicWeatherView);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.WHITE);
// 這里雨滴的寬度統(tǒng)一為3
mPaint.setStrokeWidth(3);
mRains = new ArrayList<>();
}
@Override
public void generate() {
mBackground = getContext().getResources().getDrawable(R.drawable.rain_sky_night);
mBackground.setBounds(0, 0, getWidth(), getHeight());
for (int i = 0; i < 60; i++) {
RainHolder rain = new RainHolder(
getRandom(1, getWidth()),
getRandom(1, getHeight()),
getRandom(dp2px(9), dp2px(15)),
getRandom(dp2px(5), dp2px(9)),
getRandom(20, 100)
);
mRains.add(rain);
}
}
private RainHolder r;
@Override
public void onDraw(Canvas canvas) {
clearCanvas(canvas);
// 畫(huà)背景
mBackground.draw(canvas);
// 畫(huà)出集合中的雨點(diǎn)
for (int i = 0; i < mRains.size(); i++) {
r = mRains.get(i);
mPaint.setAlpha(r.a);
canvas.drawLine(r.x, r.y, r.x, r.y + r.l, mPaint);
}
// 將集合中的點(diǎn)按自己的速度偏移
for (int i = 0; i < mRains.size(); i++) {
r = mRains.get(i);
r.y += r.s;
if (r.y > getHeight()) {
r.y = -r.l;
}
}
}
private class RainHolder {
/**
* 雨點(diǎn) x 軸坐標(biāo)
*/
int x;
/**
* 雨點(diǎn) y 軸坐標(biāo)
*/
int y;
/**
* 雨點(diǎn)長(zhǎng)度
*/
int l;
/**
* 雨點(diǎn)移動(dòng)速度
*/
int s;
/**
* 雨點(diǎn)透明度
*/
int a;
public RainHolder(int x, int y, int l, int s, int a) {
this.x = x;
this.y = y;
this.l = l;
this.s = s;
this.a = a;
}
}
}
代碼不難,基本都有注釋?zhuān)琑ainHolder 對(duì)象代表一個(gè)雨滴,每繪制一次然后改變雨滴的位置,然后準(zhǔn)備下一次繪制,來(lái)實(shí)現(xiàn)雨滴的移動(dòng)。
BaseType 類(lèi)是我們的一個(gè)抽象基類(lèi),實(shí)現(xiàn)了 DynamicWeatherView.WeatherType 接口,內(nèi)部有一些公共方法,具體可以看 Demo 中的代碼。
最后我們的 Activity 代碼:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DynamicWeatherView mDynamicWeatherView = (DynamicWeatherView) findViewById(R.id.dynamic_weather_view);
mDynamicWeatherView.setType(new RainTypeImpl(this, mDynamicWeatherView));
}
}
今后要想實(shí)現(xiàn)不同的天氣類(lèi)型,只需要繼承 BaseType 類(lèi)重寫(xiě)相關(guān)方法即可。
源碼下載:點(diǎn)擊這里
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)各位Android開(kāi)發(fā)者們能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
簡(jiǎn)析Android多種AlertDialog對(duì)話(huà)框效果
這篇文章主要為大家介紹了Android多種AlertDialog對(duì)話(huà)框效果,四種對(duì)話(huà)框效果一一為大家展示,感興趣的小伙伴們可以參考一下2016-01-01
MVVM和MVVMLight框架介紹及在項(xiàng)目中的使用詳解
這篇文章主要為大家介紹了MVVM和MVVMLight的介紹及在項(xiàng)目中的使用詳解有需要的朋友可以借鑒參考下,祝大家除夕快樂(lè)多多進(jìn)步2022-01-01
Android使用Handler實(shí)現(xiàn)定時(shí)器與倒計(jì)時(shí)器功能
Handler的最常見(jiàn)應(yīng)用場(chǎng)景之一便是通過(guò)Handler在子線(xiàn)程中間接更新UI。這篇文章主要介紹了Android使用Handler實(shí)現(xiàn)定時(shí)器與倒計(jì)時(shí)器功能,需要的朋友可以參考下2018-02-02
實(shí)例講解Android應(yīng)用開(kāi)發(fā)中TabHost的使用要點(diǎn)
這篇文章主要介紹了Android應(yīng)用開(kāi)發(fā)中TabHost的使用要點(diǎn),文中以實(shí)例講解了TabHost與Tab的布局方法,需要的朋友可以參考下2016-04-04
Android基于OpenCV實(shí)現(xiàn)霍夫直線(xiàn)檢測(cè)
霍夫變換利用點(diǎn)與線(xiàn)之間的對(duì)偶性,將圖像空間中直線(xiàn)上離散的像素點(diǎn)通過(guò)參數(shù)方程映射為霍夫空間中的曲線(xiàn),并將霍夫空間中多條曲線(xiàn)的交點(diǎn)作為直線(xiàn)方程的參數(shù)映射為圖像空間中的直線(xiàn)。給定直線(xiàn)的參數(shù)方程,可以利用霍夫變換來(lái)檢測(cè)圖像中的直線(xiàn)。本文簡(jiǎn)單講解Android的實(shí)現(xiàn)2021-06-06
Android viewpager 3D畫(huà)廊的實(shí)現(xiàn)方法
ViewPager在開(kāi)發(fā)中的使用頻率非常的高,接下來(lái)通過(guò)本文給大家分享android viewpager 3D畫(huà)廊的實(shí)現(xiàn)方法,需要的朋友參考下吧2017-02-02
Android中多行文本末尾添加圖片排版問(wèn)題的解決方法
這篇文章主要給大家介紹了關(guān)于A(yíng)ndroid中多行文本末尾添加圖片排版問(wèn)題的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07

