Android UI編程進階——使用SurfaceViewt和Canvas實現動態時鐘,androidcanvas

來源:互聯網
上載者:User

Android UI編程進階——使用SurfaceViewt和Canvas實現動態時鐘,androidcanvas
概述:

    很多時候我們想要自己寫一些類似時鐘、羅盤的控制項,卻又找不到合適的Demo。我想這時你可能索性就直接片了。在Android有Canvas和Paint這麼好的畫師的情況下,還是選擇使用圖片,的確是有一些尷尬了。下面我就利用一步一步實現自訂時鐘來對這個問題做一個講解。


錯誤樣本:

    這裡我有一個“錯誤”的樣本。這裡的錯誤其實應該是要打上雙引號的,因為它不是真的錯誤,只是在某些時候,它是不適當的。下面就讓我們先來學習一下這個樣本,瞭解一下這個樣本中哪些是不適合使用的技術。


展示:

 

看了上面的兩張運行我們可以看到很正常的兩張運行圖,不過這不是全部。錯誤資訊下面再進行展示和分析。在這裡我就來解釋一下為什麼說這個樣本不是全錯,只是不恰當的原因。因為如果我們的需求是不用變化的圖形,例如一些多邊形的展示等,不需要即時去重新整理介面,OK,這個樣本沒有任何問題,而且使用簡單。針對這一點,我想也是有必要附上代碼來展示一下實現過程。


靜態畫圖代碼:

public class CustomCanvasView extends View {    private static final String TAG = CustomCanvasView.class.getName();    private Paint paint;        private int mRadius;    private Canvas mCanvas;        private int mHours;    private int mMinutes;    private int mSeconds;        private Thread mThread;        public CustomCanvasView(Context context, int radius) {        super(context);        paint = new Paint();        paint.setColor(Color.RED);        paint.setStrokeJoin(Paint.Join.ROUND);        paint.setStrokeCap(Paint.Cap.ROUND);        paint.setStrokeWidth(5);                mRadius = radius;    }    // 在這裡我們將測試canvas提供的繪製圖形方法    @Override    protected void onDraw(Canvas canvas) {        mCanvas = canvas;                drawCompass(mCanvas);                refreshClock();    }    private void refreshClock() {        mThread = new Thread() {            @Override            public void run() {                try {                    while (true) {                        handler.sendEmptyMessage(0x123);                        sleep(1000);                    }                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        };        mThread.start();    }        Handler handler = new Handler() {        public void handleMessage(Message msg) {            Calendar c = Calendar.getInstance();            mHours = c.getTime().getHours();            mMinutes = c.getTime().getMinutes();            mSeconds = c.getTime().getSeconds();                        invalidate();                        c = null;        };    };        /**     * 繪製羅盤     * 2015-2-3     */    private void drawCompass(Canvas canvas) {        paint.setAntiAlias(true);        paint.setStyle(Style.STROKE);        canvas.translate(canvas.getWidth() / 2, mRadius + 300); // 平移羅盤        canvas.drawCircle(0, 0, mRadius, paint); // 畫圓圈        // 使用path繪製路徑文字        canvas.save();                drawLabel(canvas);                canvas.restore();        drawDividing(canvas);        drawMinuteHand(canvas, 0);                canvas = null;    }        /**     * 繪製羅盤內側的標籤文本     * 2015-2-4     */    private void drawLabel(Canvas canvas) {        canvas.translate(-155, -155);        Path path = new Path();        path.addArc(new RectF(0, 0, mRadius + 100, mRadius + 100), -180, 180);        Paint citePaint = new Paint(paint);        citePaint.setTextSize(30);        citePaint.setStrokeWidth(1);        canvas.drawTextOnPath("http://blog.csdn.net/lemon_tree", path, 35, 0, citePaint);                path = null;        citePaint = null;        canvas = null;    }        /**     * 繪製刻度     * 2015-2-4     */    private void drawDividing(Canvas canvas) {        Paint divdPaint = new Paint(paint); // 小刻度畫筆對象        divdPaint.setStrokeWidth(1);        divdPaint.setTextSize(20);                float y = mRadius;        int count = 60; // 總刻度數        canvas.rotate(35 * 360 / count, 0f, 0f);                for (int i = 0; i < count; i++) {            if (i % 5 == 0) {                canvas.drawLine(0f, y, 0, y + 20f, paint);                canvas.drawText(String.valueOf(i / 5 + 1), -4f, y + 55f, divdPaint);            } else {                canvas.drawLine(0f, y, 0f, y + 15f, divdPaint);            }            canvas.rotate(360 / count, 0f, 0f); // 旋轉畫紙        }                divdPaint = null;        canvas = null;    }        /**     * 繪製分針     * 2015-2-4     */    private void drawMinuteHand(Canvas canvas, int second) {        Paint tmpPaint = new Paint(paint);        tmpPaint.setStrokeWidth(2);        tmpPaint.setTextSize(30);                tmpPaint.setColor(Color.GRAY);        tmpPaint.setStrokeWidth(4);        canvas.drawCircle(0, 0, 10, tmpPaint);        tmpPaint.setStyle(Style.FILL);        tmpPaint.setColor(Color.YELLOW);                canvas.drawCircle(0, 0, 5, tmpPaint);                canvas.rotate(mSeconds * 6, 0f, 0f);        canvas.drawLine(0, 20, 0, -135, paint);                tmpPaint = null;        canvas = null;    }}


錯誤記錄檔展示及原因分析:

說實話上面兩張圖看上去真的很棒,可是如果你下載了我的源碼並運行之後,你可能就會發現,在你的指標走了大概20秒的時候,程式就掛了。查看日誌就會發現如下錯誤資訊:


是不是有一種又是該死的OOM問題的感覺,說實話我也是這種感覺。這可能是因為invalidate()的時候沒有清理回收資源的問題,而且這裡的自訂控制項是繼承View,沒有採用雙緩衝技術,致使程式崩潰。而此處的資源回收我也做了一些努力,可是問題依舊存在。於是我就開始找尋另一條路徑來解決問題——SurfaceView。

----------------------------------------- Split -------------------------------------------

正確樣本:前置知識學習——髒矩形:

    所謂髒矩形重新整理,意為僅重新整理有新變化的部分所在的矩形地區,而其他沒用的部分就不去重新整理,以此來減少資源浪費。我們可以通過在擷取Canvas畫布時,為其指派一個參數來聲明我們需要畫布哪個局部,這樣就可以只獲得這個部分的控制權.(參考來自:http://www.linuxidc.com/Linux/2012-02/54367.htm)本例中,使用的是全域重新整理。

前置知識學習——雙緩衝:

    關於雙緩衝的概念,這裡引用一下百度百科的說明(點擊進入)。

    如果要按照我的理解來通俗地講一遍的話,我想應該是這個樣子的:有一個暗房,裡面有一個功能深厚的畫師,他負責繪製圖畫。暗房對外提供了一個小視窗,這個小視窗是用來展示畫師畫出來的圖畫。這個暗房裡還有一個畫師的助理,他負責把畫師畫出來的圖畫以一定速度展示在這個小視窗上(這邊的一定速度肯定是比畫師繪畫的速度要慢一些)。


執行個體示範:運行展示:

 

看到以上的運行是不感覺很炫?(註:上面的GIF看上去有些怪,那是因為本人沒截好的原因,運行程式的時候會流暢很多)寫出來的時候,我也感覺比用圖片實現的要好很多。接下來就來慢慢學習一下實現它的過程吧。


首先要做的事

1.extends SurfaceView

2.implements SurfaceHolder.Callback

3.自訂一個Thread


第二步:邏輯功能實現

基於上一個不恰當的版本,這裡對上面的邏輯功能進行一些引用。

繪製秒針:

/**     * 繪製秒針     * 2015-2-4     */    private void drawSecondHand(Canvas canvas) {        Paint handPaint = new Paint(mPaint);        handPaint.setStrokeWidth(2);        handPaint.setStyle(Style.FILL);        int angle = (mSeconds + 25) * 6; // 計算角度        canvas.rotate(angle, 0f, 0f);        canvas.drawLine(0, 20, 0, -135, mPaint);    }

繪製分針:

/**     * 繪製分針     * 2015-2-4     */    private void drawMinuteHand(Canvas canvas) {        Paint handPaint = new Paint(mPaint);        handPaint.setStrokeWidth(2);        handPaint.setStyle(Style.FILL);        canvas.save();        int angle = (mMinutes + 25) * 6; // 計算角度        canvas.rotate(angle, 0f, 0f);        canvas.drawLine(0, 20, 0, -110, mPaint);        canvas.restore();    }
從秒針到分針代碼明顯多了幾行,而這多出來的幾行代碼有什麼作用呢?

在繪製分針的時候我們可以看到這樣一句:canvas.rotate(angle, 0f, 0f);它的作用是將畫布旋轉angle度,而如果我們在繪製分針的時候不對畫布作一個狀態儲存,那下次在繪製時針的時候將是旋轉之後所做的邏輯,為了避免這些不必要的麻煩,我們需要對其先儲存後再複原處理。


繪製時針:

/**     * 繪製時針     * 2015-2-4     */    private void drawHourHand(Canvas canvas) {        Paint handPaint = new Paint(mPaint);        handPaint.setStyle(Style.FILL);        handPaint.setStrokeWidth(8);        canvas.save();        int angle = (((mHours % 12) * 5 + 25) * 6) + (mMinutes * 6 * 5 / 60); // 計算角度        canvas.rotate(angle, 0f, 0f);        canvas.drawLine(0, 20, 0, -90, handPaint);        canvas.restore();    }
時針的繪製和分針幾乎一致,唯一要注意的是繪製時針時角度的計算。如果你這裡只按小時數來計算,那它永遠都是指向大刻度。永遠不會指向兩個大刻度之間的部分,為瞭解決這個問題,我們需要加上分鐘數一起計算。即加了n分鐘下時針又位移了多少角度。

自訂Thread

使用SurfaceView需要用到一個鎖的機制。也就是說我這邊繪圖的時候,不允許被打擾,有一個獨佔的概念。可以通過以下代碼實現:

class DrawThread extends Thread {        private SurfaceHolder holder;        public boolean isRun;                public DrawThread(SurfaceHolder holder) {            this.holder = holder;            isRun = true;        }                @Override        public void run() {            while (isRun) {                Canvas canvas = null;                try {                    synchronized (holder) {                        canvas = holder.lockCanvas(null);                        canvas.drawColor(Color.BLACK);                        drawClock(canvas);                        holder.unlockCanvasAndPost(canvas); // 解鎖畫布,提交畫好的映像                                                Thread.sleep(1000);                    }                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }
大家可以看到在我上完鎖之後,對畫布有一行canvas.drawColor(Color.BLACK);的代碼操作。我想你應該是明白為什麼的。對!就是清屏!如果沒有這一句代碼,上一次繪製的圖形沒有被清除,這讓整個介面感覺起來很淩亂。下面就讓我們一起來感受一下在沒有清屏且只有一根指標的情況下,Canvas動態繪製出來的圖形。

  大家可以明顯看到時鐘內側的那一行Label,白色的部分在一點一點地加深,這就有力地說明了是因為上一次圖形的殘留導致的。


好了,利用SurfaceView和Canvas對自訂時鐘的學習就到這裡了,如果你還有一些不太明白的地方,歡迎前往我的上一篇部落格《Android自訂控制項前置基礎知識學習(一)——Canvas》進行學習,或以評論的方式與我進行交流。



聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.