如何在Android中使用SurfaceView制作一個(gè)天氣動(dòng)畫(huà)效果?很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來(lái)學(xué)習(xí)下,希望你能有所收獲。
首先是最終實(shí)現(xiàn)的效果圖:
初識(shí) SurfaceView
SurfaceView 直接繼承自 View,View 必須在 UI 線程中繪制,而 SurfaceView 不同于 View,它可以在非 UI 線程中繪制并顯示在界面上,這意味著你可以自己新開(kāi)一個(gè)線程,然后把繪制渲染的代碼放在該線程中。
Surface 是 Z 軸排序的,SurfaceView 的 Z 軸位置小于它的宿主 Window,代表它總是在自己所在 Window 的后面,既然在后面,那么是怎么顯示的呢?SurfaceView 在其 Window 中打出一個(gè)“孔”(其實(shí)就是在其宿主 Window 上設(shè)置了一塊透明區(qū)域來(lái)使其能夠顯示),意味著他的兄弟節(jié)點(diǎn)的 View 會(huì)覆蓋它,例如你可以在 SurfaceView 上方放置按鈕,文本等控件。
要想訪問(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)建以及銷毀),這里 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 銷毀時(shí)調(diào)用(SurfaceView的窗口不可見(jiàn)時(shí)) public void surfaceDestroyed(SurfaceHolder holder); }
我們的繪制代碼需要在 surfaceCreated 和 surfaceDestroyed 之間執(zhí)行,否則無(wú)效,SurfaceHolder.Callback
的回調(diào)方法是執(zhí)行在 UI 線程中的,繪制線程需要我們自己手動(dòng)創(chuàng)建。
View 和 SurfaceView 的使用場(chǎng)景
View 適合那些與用戶交互并且渲染時(shí)間不是很長(zhǎng)的控件,因?yàn)?View 的繪制和用戶交互都處在 UI 線程中。
SurfaceView 適合迅速的更新界面或者渲染時(shí)間比較長(zhǎng)以至于影響到用戶體驗(yàn)的場(chǎng)景。
使用 SurfaceView(實(shí)現(xiàn))
這里我們和自定義 View 類似,寫(xiě)一個(gè)類 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 銷毀時(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)我們的繪制線程:
private class DrawThread extends Thread { // 用來(lái)停止線程的標(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 { // 停止線程 break; } SystemClock.sleep(1); } } } } }
從上面的代碼可以看出 SurfaceView 的更新流程具體為:
// 鎖定畫(huà)布并獲得 canvas canvas = mHolder.lockCanvas(); // 在 canvas 上進(jìn)行繪制 mType.onDraw(canvas); // 解除鎖定并提交更改 mHolder.unlockCanvasAndPost(canvas);
繪制線程代碼量不多,因?yàn)榫唧w的繪制代碼在 mType.onDraw(canvas)
中,mType 是我們自己定義的一個(gè)接口,代表一種天氣類型:
public interface WeatherType { void onDraw(Canvas canvas); void onSizeChanged(Context context, int w, int h); }
這樣要想實(shí)現(xiàn)不同的天氣類型,只要實(shí)現(xiàn)這個(gè)接口重寫(xiě) onDraw 和 onSizeChanged 方法即可,這里我們實(shí)現(xiàn)的是下雨的效果,所以實(shí)現(xiàn)了一個(gè) RainTypeImpl 類:
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; } } }
代碼不難,基本都有注釋,RainHolder 對(duì)象代表一個(gè)雨滴,每繪制一次然后改變雨滴的位置,然后準(zhǔn)備下一次繪制,來(lái)實(shí)現(xiàn)雨滴的移動(dòng)。
BaseType 類是我們的一個(gè)抽象基類,實(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)); } }
看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝您對(duì)億速云的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。