溫馨提示×
密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何在Android中使用SurfaceView制作一個(gè)天氣動(dòng)畫(huà)效果

發(fā)布時(shí)間:2020-11-26 17:05:30 來(lái)源:億速云 閱讀:214 作者:Leah 欄目:移動(dòng)開(kāi)發(fā)

如何在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ì)億速云的支持。

向AI問(wèn)一下細(xì)節(jié)

免責(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)容。

AI