您好,登錄后才能下訂單哦!
如何在Android中使用SurfaceView制作一個天氣動畫效果?很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
首先是最終實現的效果圖:
初識 SurfaceView
SurfaceView 直接繼承自 View,View 必須在 UI 線程中繪制,而 SurfaceView 不同于 View,它可以在非 UI 線程中繪制并顯示在界面上,這意味著你可以自己新開一個線程,然后把繪制渲染的代碼放在該線程中。
Surface 是 Z 軸排序的,SurfaceView 的 Z 軸位置小于它的宿主 Window,代表它總是在自己所在 Window 的后面,既然在后面,那么是怎么顯示的呢?SurfaceView 在其 Window 中打出一個“孔”(其實就是在其宿主 Window 上設置了一塊透明區域來使其能夠顯示),意味著他的兄弟節點的 View 會覆蓋它,例如你可以在 SurfaceView 上方放置按鈕,文本等控件。
要想訪問下面的 Surface ,可以通過 Android 提供給我們的 SurfaceHolder 接口。可以調用 SurfaceView 的 getHolder()
來獲取。
SurfaceView 是有生命周期的,我們必須在它生命周期期間進行執行繪制代碼,所以我們需要監聽 SurfaceView 的狀態(例如創建以及銷毀),這里 Android 為我們提供了 SurfaceHolder.Callback
這個接口來可以讓我們方便的監聽 SurfaceView 的狀態。
那么下面看下 SurfaceHolder.Callback
接口
public interface Callback { // SurfaceView 創建時調用(SurfaceView的窗口可見時) public void surfaceCreated(SurfaceHolder holder); // SurfaceView 改變時調用 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height); // SurfaceView 銷毀時調用(SurfaceView的窗口不可見時) public void surfaceDestroyed(SurfaceHolder holder); }
我們的繪制代碼需要在 surfaceCreated 和 surfaceDestroyed 之間執行,否則無效,SurfaceHolder.Callback
的回調方法是執行在 UI 線程中的,繪制線程需要我們自己手動創建。
View 和 SurfaceView 的使用場景
View 適合那些與用戶交互并且渲染時間不是很長的控件,因為 View 的繪制和用戶交互都處在 UI 線程中。
SurfaceView 適合迅速的更新界面或者渲染時間比較長以至于影響到用戶體驗的場景。
使用 SurfaceView(實現)
這里我們和自定義 View 類似,寫一個類 DynamicWeatherView 繼承自 SurfaceView,然后為了監聽 SurfaceView 的狀態,所以我們還需要實現 SurfaceHolder.Callback
接口來監聽 SurfaceView 的狀態,接口的回調具體時機上面也已經介紹過了。
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 創建時調用(可見) @Override public void surfaceCreated(SurfaceHolder holder) { } // SurfaceView 銷毀時調用(不可見) @Override public void surfaceDestroyed(SurfaceHolder holder) { } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } }
上面也提到了,控制 Surface 我們需要 SurfaceHolder 對象,調用 SurfaceView 的 getHolder()
即可獲得,然后為這個 SurfaceHolder 添加一個 SurfaceHolder.Callback
回調,這里就是 DynamicWeatherView 當前對象
private SurfaceHolder mHolder;
mHolder = getHolder(); mHolder.addCallback(this); mHolder.setFormat(PixelFormat.TRANSPARENT);
然后實現我們的繪制線程:
private class DrawThread extends Thread { // 用來停止線程的標記 private boolean isRunning = false; public void setRunning(boolean running) { isRunning = running; } @Override public void run() { Canvas canvas; // 無限循環繪制 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 的更新流程具體為:
// 鎖定畫布并獲得 canvas canvas = mHolder.lockCanvas(); // 在 canvas 上進行繪制 mType.onDraw(canvas); // 解除鎖定并提交更改 mHolder.unlockCanvasAndPost(canvas);
繪制線程代碼量不多,因為具體的繪制代碼在 mType.onDraw(canvas)
中,mType 是我們自己定義的一個接口,代表一種天氣類型:
public interface WeatherType { void onDraw(Canvas canvas); void onSizeChanged(Context context, int w, int h); }
這樣要想實現不同的天氣類型,只要實現這個接口重寫 onDraw 和 onSizeChanged 方法即可,這里我們實現的是下雨的效果,所以實現了一個 RainTypeImpl 類:
public class RainTypeImpl extends BaseType { // 背景 private Drawable mBackground; // 雨滴集合 private ArrayList<RainHolder> mRains; // 畫筆 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); // 這里雨滴的寬度統一為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); // 畫背景 mBackground.draw(canvas); // 畫出集合中的雨點 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); } // 將集合中的點按自己的速度偏移 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 { /** * 雨點 x 軸坐標 */ int x; /** * 雨點 y 軸坐標 */ int y; /** * 雨點長度 */ int l; /** * 雨點移動速度 */ int s; /** * 雨點透明度 */ 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 對象代表一個雨滴,每繪制一次然后改變雨滴的位置,然后準備下一次繪制,來實現雨滴的移動。
BaseType 類是我們的一個抽象基類,實現了 DynamicWeatherView.WeatherType
接口,內部有一些公共方法,具體可以看 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)); } }
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。