日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android UI编程进阶——使用SurfaceViewt和Canvas实现动态时钟

發(fā)布時(shí)間:2025/3/20 Android 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android UI编程进阶——使用SurfaceViewt和Canvas实现动态时钟 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

概述:

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


錯誤示例:

? ? 這里我有一個“錯誤”的示例。這里的錯誤其實(shí)應(yīng)該是要打上雙引號的,因?yàn)樗皇钦娴腻e誤,只是在某些時(shí)候,它是不適當(dāng)?shù)摹O旅婢妥屛覀兿葋韺W(xué)習(xí)一下這個示例,了解一下這個示例中哪些是不適合使用的技術(shù)。


效果圖展示:

?

看了上面的兩張運(yùn)行效果圖我們可以看到很正常的兩張運(yùn)行圖,不過這不是全部。錯誤信息下面再進(jìn)行展示和分析。在這里我就來解釋一下為什么說這個示例不是全錯,只是不恰當(dāng)?shù)脑颉R驗(yàn)槿绻覀兊男枨笫遣挥米兓膱D形,例如一些多邊形的展示等,不需要實(shí)時(shí)去刷新界面,OK,這個示例沒有任何問題,而且使用簡單。針對這一點(diǎn),我想也是有必要附上代碼來展示一下實(shí)現(xiàn)過程。


靜態(tài)畫圖代碼:

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提供的繪制圖形方法@Overrideprotected void onDraw(Canvas canvas) {mCanvas = canvas;drawCompass(mCanvas);refreshClock();}private void refreshClock() {mThread = new Thread() {@Overridepublic 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;}/*** 繪制羅盤內(nèi)側(cè)的標(biāo)簽文本* 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; // 總刻度數(shù)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); // 旋轉(zhuǎn)畫紙}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;} }


錯誤日志展示及原因分析:

說實(shí)話上面兩張圖看上去真的很棒,可是如果你下載了我的源碼并運(yùn)行之后,你可能就會發(fā)現(xiàn),在你的指針走了大概20秒的時(shí)候,程序就掛了。查看日志就會發(fā)現(xiàn)如下錯誤信息:


是不是有一種又是該死的OOM問題的感覺,說實(shí)話我也是這種感覺。這可能是因?yàn)閕nvalidate()的時(shí)候沒有清理回收資源的問題,而且這里的自定義控件是繼承View,沒有采用雙緩沖技術(shù),致使程序崩潰。而此處的資源回收我也做了一些努力,可是問題依舊存在。于是我就開始找尋另一條路徑來解決問題——SurfaceView。

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

正確示例:

前導(dǎo)知識學(xué)習(xí)——臟矩形:

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

前導(dǎo)知識學(xué)習(xí)——雙緩沖:

? ? 關(guān)于雙緩沖的概念,這里引用一下百度百科的說明(點(diǎn)擊進(jìn)入)。

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


實(shí)例示范:

運(yùn)行效果圖展示:

?

看到以上的運(yùn)行效果圖是不感覺很炫?(注:上面的GIF看上去有些怪,那是因?yàn)楸救私貓D沒截好的原因,運(yùn)行程序的時(shí)候會流暢很多)寫出來的時(shí)候,我也感覺比用圖片實(shí)現(xiàn)的要好很多。接下來就來慢慢學(xué)習(xí)一下實(shí)現(xiàn)它的過程吧。


首先要做的事

1.extends SurfaceView

2.implements?SurfaceHolder.Callback

3.自定義一個Thread


第二步:邏輯功能實(shí)現(xiàn)

基于上一個不恰當(dāng)?shù)陌姹?#xff0c;這里對上面的邏輯功能進(jìn)行一些引用。

繪制秒針:

/*** 繪制秒針* 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; // 計(jì)算角度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; // 計(jì)算角度canvas.rotate(angle, 0f, 0f);canvas.drawLine(0, 20, 0, -110, mPaint);canvas.restore();} 從秒針到分針代碼明顯多了幾行,而這多出來的幾行代碼有什么作用呢?

在繪制分針的時(shí)候我們可以看到這樣一句:canvas.rotate(angle, 0f, 0f);它的作用是將畫布旋轉(zhuǎn)angle度,而如果我們在繪制分針的時(shí)候不對畫布作一個狀態(tài)保存,那下次在繪制時(shí)針的時(shí)候?qū)⑹切D(zhuǎn)之后所做的邏輯,為了避免這些不必要的麻煩,我們需要對其先保存后再復(fù)原處理。


繪制時(shí)針

/*** 繪制時(shí)針* 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); // 計(jì)算角度canvas.rotate(angle, 0f, 0f);canvas.drawLine(0, 20, 0, -90, handPaint);canvas.restore();} 時(shí)針的繪制和分針幾乎一致,唯一要注意的是繪制時(shí)針時(shí)角度的計(jì)算。如果你這里只按小時(shí)數(shù)來計(jì)算,那它永遠(yuǎn)都是指向大刻度。永遠(yuǎn)不會指向兩個大刻度之間的部分,為了解決這個問題,我們需要加上分鐘數(shù)一起計(jì)算。即加了n分鐘下時(shí)針又偏移了多少角度。

自定義Thread

使用SurfaceView需要用到一個鎖的機(jī)制。也就是說我這邊繪圖的時(shí)候,不允許被打擾,有一個獨(dú)占的概念。可以通過以下代碼實(shí)現(xiàn):

class DrawThread extends Thread {private SurfaceHolder holder;public boolean isRun;public DrawThread(SurfaceHolder holder) {this.holder = holder;isRun = true;}@Overridepublic 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);的代碼操作。我想你應(yīng)該是明白為什么的。對!就是清屏!如果沒有這一句代碼,上一次繪制的圖形沒有被清除,這讓整個界面感覺起來很凌亂。下面就讓我們一起來感受一下在沒有清屏且只有一根指針的情況下,Canvas動態(tài)繪制出來的圖形。

?? 大家可以明顯看到時(shí)鐘內(nèi)側(cè)的那一行Label,白色的部分在一點(diǎn)一點(diǎn)地加深,這就有力地說明了是因?yàn)樯弦淮螆D形的殘留導(dǎo)致的。


好了,利用SurfaceView和Canvas對自定義時(shí)鐘的學(xué)習(xí)就到這里了,如果你還有一些不太明白的地方,歡迎前往我的上一篇博客《Android自定義控件前導(dǎo)基礎(chǔ)知識學(xué)習(xí)(一)——Canvas》進(jìn)行學(xué)習(xí),或以評論的方式與我進(jìn)行交流。



總結(jié)

以上是生活随笔為你收集整理的Android UI编程进阶——使用SurfaceViewt和Canvas实现动态时钟的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。