天天看點

Android 利用SurfaceView實作一個簡單的計時器

自學了android有幾個月了,跟着網上的節奏,應該早點寫些部落格來提高自己的水準的。但苦于技術水準始終不自信(也是不過關的結果吧),就一直隻是将自己學習過程中的問題和重要的知識點寫在自己的筆記文檔中。

但,總感覺一個人寫下來成就感還是欠缺了那麼一些,而且有些問題及解答方法抛出來,是有可能得到更多好的回報及解決方案的。于是,本着不作不會死的心态,一步一步在技術成長的道路前行——>這篇部落格就是其中一步!

若部落格中有些技術知識點有誤或者有更優化的解答方案,還望各位小夥伴多多指出。

以下是正題了:

目标:利用SurfaceView實作一個簡單的計時器

圖示:

Android 利用SurfaceView實作一個簡單的計時器

描述:1.利用SurfaceView來實作計時功能,同時不斷将圓弧畫滿;2.點選按鈕可以停止計時;

重點:

1.自定義SurfaceView中針對SurfaceHolder.CallBack的三個方法進行覆寫;

               2.通過surfaceHolder.lockCanvas()在新開的線程中得到canvas對象,進而進行圖形和時間文字的繪制;

3.通過設定flag值,進而控制線上程run()方法中邏輯代碼的執行;

重要部分代碼:

public TestView(Context context) {
        super(context);
        surfaceHolder = getHolder();
        surfaceHolder.addCallback(this);
        countThread = new CountThread(surfaceHolder);
    }

    public TestView(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
        surfaceHolder = getHolder();
        surfaceHolder.addCallback(this);
        countThread = new CountThread(surfaceHolder);
    }
           

以上為自定義的SurfaceView(TestView)的構造函數,做相應的初始化工作。(第二個構造方法在實作過程中沒有覆寫,導緻如果是通過布局檔案引入進Activity中時,則顯示不出View——>也就是一般自定義View一定要覆寫的構造方法)。

在構造方法中初始化了自定義的内部線程類CountThread,用來執行繪制工作。

以下為CountThread類的run()方法執行邏輯:

@Override
        public void run() {
            Canvas canvas = null;
            int pivotX = getResources().getDisplayMetrics().widthPixels / 2;
            RectF rectF = new RectF(pivotX - 300, pivotX - 300, pivotX + 300, pivotX + 300);
            while (!isStop) {
                try {
                    canvas = surfaceHolder.lockCanvas();
                    canvas.drawColor(Color.WHITE);//設定畫布背景為白色
//                    canvas.drawRoundRect(300, 300, 600, 600, 150, 150, paint);//直接使用該行代碼來畫圓是行不通的,因為這個方法要求版本21,我的手機運作android版本是19
                    canvas.drawArc(rectF, -90, endAngle++, false, paint);//-90在這裡不等于270,是以要想從最上方開始畫弧,就得用-90
                    canvas.drawText(countTime(endAngle), pivotX, pivotX, paintText);//顯示計算的時間
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (canvas != null) {//需要對canvas進行非空判斷
                        surfaceHolder.unlockCanvasAndPost(canvas);
                    }
                }
            }
        }
           

主要就是通過Thread.sleep(1000)來停頓一秒,進而通過endAngle來計數。

至此,基本的程式邏輯已經實作。再講該自定義View引入進Activity指定的layout布局檔案中即可進行顯示。

但,這其中也會涉及到相應的問題。譬如:

1.圖示中的按鈕無法顯示出來,隻能看到自定義的SurfaceView的視圖;

我的做法是:将布局設定為FrameLayout即可。因為SurfaceView是浮在視窗的一層,那麼就可以把它看做一個圖層。

2.java.lang.IllegalThreadStateException:Thread already started

該錯誤表明,線程已經存在了。這種錯誤的操作重制是:按下home鍵或者menu鍵會導緻該自定的SurfaceView銷毀,但線程并沒有被銷毀,再次啟動該自定義SurfaceView的時候又去重新啟動該線程。解決的方法是:

@Override
public void surfaceCreated(SurfaceHolder holder) {
    if (!countThread.isAlive()) {//如果線程不存在,則啟動線程——>當應用挂起的時候Thread是存在的,如果不做這個判斷,會報“Thread already started ”錯誤
        countThread.start();//SurfaceView建立時開啟線程
    }
}
           

在surfaceCreated()方法中對該線程是否是在存活中進行判斷。

當然,這裡面還有最重要的一個問題:

當按下home鍵或者menu鍵時,程式是沒有在計時的。那麼這種情況下,我的一個解決方案是,通過開啟一個service來接收程式停止( onStop() )時已經計時的數值,然後傳遞給service記下并計時,當應用程式界面重新回歸螢幕時( onRestart() )則将資料取出并回傳到自定義的SurfaceView的邏輯run()方法中繼續計時。

整個小程式的代碼可以通過以下連結下載下傳:

點選進入下載下傳頁面