聲明:原創(chuàng)作品,轉(zhuǎn)載請(qǐng)注明出處http://www.itdecent.cn/p/c2abd6226897
在上篇文章中,我向大家介紹了如何通過自定義View一步步畫出一個(gè)漂亮的圓形時(shí)鐘。如果你還沒看的話,我不建議你接著往下看,因?yàn)檫@篇文章是接著上篇的文章,如果直接看的話可能會(huì)不知所云,所以還是建議你先看一下我之前的這篇文章Android自定義控件之圓形時(shí)鐘。如果看過上篇文章的話,我們知道這個(gè)時(shí)鐘還是有幾個(gè)問題的。第一,很明顯就是時(shí)鐘只是靜態(tài)的還不能動(dòng)。第二,秒針、分針和時(shí)針之間沒有聯(lián)系,即分針和時(shí)針的位置應(yīng)該和秒針有關(guān)的。
我們先來看看效果,
靜態(tài)的:

動(dòng)態(tài)的:

首先我們來分析如何讓秒針動(dòng)起來。不知道大家熟不熟悉逐幀動(dòng)畫,不熟悉也沒關(guān)系,大家小時(shí)候上學(xué)一定有這樣的經(jīng)歷:在一本厚厚的書上,在每一頁(yè)的同一位置,畫有略有不同的圖案,然后撥動(dòng)整本書,之后便會(huì)奇跡般的呈現(xiàn)出一幅動(dòng)畫。其實(shí)這就是逐幀動(dòng)畫的原理,我們看到的動(dòng)畫是由一幅幅圖像組成,之所以我們感覺不出來,是因?yàn)檫@些圖像閃的太快啦,就拿前面書本的例子,如果你將書撥動(dòng)的越快,那么你看到的動(dòng)畫就越流暢,相反,如果速度很慢的話,就會(huì)明顯看到紙張上的圖案。那么可能有人要問了,這個(gè)速度到底快到什么程度,我們才能感覺到是一幅動(dòng)畫呢?一般來講,我們?nèi)庋勰芊直娴膸瑪?shù)是24幀,什么意思呢?還拿這個(gè)例子講解,如果一秒鐘,你一共撥動(dòng)了24頁(yè)或者更多,那么你就能看到一幅流暢的動(dòng)畫,完全感覺不到紙張的存在;如果頁(yè)數(shù)不到24頁(yè),那么我們的肉眼就能看到一張張紙翻過。我們指針的運(yùn)動(dòng)其實(shí)也是同樣的道理。秒針走完一圈是60秒,而一圈是360度,那么我們可以算出一秒鐘,其實(shí)就是360度/60秒 = 6度。也就是說,每經(jīng)過一秒鐘,我們將秒針的角度加上6度,然后重新調(diào)用onDraw方法重繪一次秒針。這樣通過不斷的重繪,我們的指針也就動(dòng)起來了。那么如何準(zhǔn)確的控制這一秒呢?這里我們用到了定時(shí)器Timer。代碼如下:
private float mSecondDegree;//秒針的度數(shù)
private Timer mTimer = new Timer();
private TimerTask task = new TimerTask() {
@Override
public void run() {//具體的定時(shí)任務(wù)邏輯
if (mSecondDegree == 360) {//因?yàn)閳A一圈為360度,所以走滿一圈角度清零
mSecondDegree = 0;
}
mSecondDegree = mSecondDegree + 6;
postInvalidate();
}
};
/**
*開啟定時(shí)器
*/
public void start() {
mTimer.schedule(task,0,1000);
}
@Override
protected void onDraw(Canvas canvas) {
mPaint.setStrokeWidth(2);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 3, mPaint);
mPaint.setStrokeWidth(5);
canvas.drawPoint(getWidth() / 2, getHeight() / 2, mPaint);
mPaint.setStrokeWidth(1);
canvas.translate(getWidth() / 2, getHeight() / 2);
for (int i = 0; i < 360; i++) {
if (i % 30 == 0) {//長(zhǎng)刻度
canvas.drawLine(getWidth() / 3 - 25, 0,
getWidth() / 3, 0, mPaint);
} else if (i % 6 == 0) {//中刻度
canvas.drawLine(getWidth() / 3 - 14, 0,
getWidth() / 3, 0, mPaint);
} else {//短刻度
canvas.drawLine(getWidth() / 3 - 9, 0,
getWidth() / 3, 0, mPaint);
}
canvas.rotate(1);
}
canvas.save();
mPaint.setTextSize(25);
mPaint.setStyle(Paint.Style.FILL);
for (int i = 0; i < 12; i++) {
if (i == 0) {
drawNum(canvas, i * 30, 12 + "", mPaint);
} else {
drawNum(canvas, i * 30, i + "", mPaint);
}
}
canvas.restore();
//秒針
canvas.save();
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(2);
canvas.rotate(mSecondDegree);
canvas.drawLine(0, 0, 0,
-190, mPaint);
canvas.restore();
//分針
canvas.save();
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(4);
canvas.rotate(30);
canvas.drawLine(0, 0, 0,
-130, mPaint);
canvas.restore();
//時(shí)針
canvas.save();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(7);
canvas.rotate(90);
canvas.drawLine(0, 0, 0,
-90, mPaint);
canvas.restore();
在代碼中我們看到,我們先創(chuàng)建了一個(gè)Timer,又創(chuàng)建了一個(gè)定時(shí)任務(wù)TimerTask,然后重寫里面的run()方法,這個(gè)run方法中其實(shí)就是我們每隔一秒要處理的事情,這里代碼也很簡(jiǎn)單,就是每隔一秒鐘,我們就把秒針的度數(shù)加上6度,然后調(diào)用postInvalidate();調(diào)用這個(gè)方法就會(huì)執(zhí)行onDraw方法讓畫布重繪,當(dāng)然invalidate()也會(huì)調(diào)用onDraw方法,兩者區(qū)別就是,invalidate()要在主線程調(diào)用,而postInvalidate()在子線程中調(diào)用,我們開啟了一個(gè)定時(shí)器,相當(dāng)于開啟了一個(gè)子線程,所以這里要用postInvalidate()方法。我們的onDraw方法中代碼基本和上篇文章一樣,而且講的也非常詳細(xì)了,這里就不在贅述了,具體可以戳這里Android自定義控件之圓形時(shí)鐘。唯一不同的就是在畫秒針的地方, 我們多了這句代碼: canvas.rotate(mSecondDegree);即在畫秒針之前我們讓畫布旋轉(zhuǎn)了mSecondDegree度,這里的mSecondDegree就是我們?cè)诙〞r(shí)任務(wù)中計(jì)算得來的。最后我們就可以啟動(dòng)這個(gè)定時(shí)器啦,啟動(dòng)也很簡(jiǎn)單,只需要調(diào)用定時(shí)器Timer的schedule方法,這里我們傳入三個(gè)參數(shù),第一個(gè)就是我們的定時(shí)任務(wù)task,第二個(gè)表示啟動(dòng)定時(shí)器后多少毫秒開始工作,傳入0代表,一調(diào)用schedule這個(gè)方法就立即開啟定時(shí)任務(wù);第三個(gè)參數(shù)就是任務(wù)的執(zhí)行間隔,單位也是毫秒,由于是秒針,所以每秒要重繪一次,這里自然就是1000毫秒啦。好了接下來我們?cè)贛ainActivity中調(diào)用start方法來啟動(dòng)這個(gè)定時(shí)器就可以了。
public class MainActivity extends AppCompatActivity {
private TimeView time_view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
time_view = (TimeView)findViewById(R.id.time_view);
time_view.start();
}
}
我們來看一下效果圖:

怎么樣,秒針動(dòng)起來了?。∈遣皇怯悬c(diǎn)小興奮。。雖然這是簡(jiǎn)單的一小步驟,但確實(shí)我們學(xué)習(xí)知識(shí)的一大步,我們從畫靜態(tài)的圖形,過渡到能做出動(dòng)畫了。好了秒針是動(dòng)起來,但別忘了還有分針和時(shí)針這兩家伙,我們?cè)撊绾巫屗鼈円矂?dòng)起來呢?相信聰明的你一定學(xué)會(huì)了舉一反三了。我們?cè)谏厦嫠愠?,每一秒鐘,秒針是要?度,那么我們只要算出每秒鐘,分針和時(shí)針要加多少度,問題就迎刃而解了。那么具體怎么算呢?先來看分針,我們知道,分鐘走一圈是60分鐘吧,而圓的一圈是360度,那么一分鐘,其實(shí)就是分針走了360度/60分鐘 = 6度,而一分鐘等于60秒,所以對(duì)應(yīng)一秒鐘就是,6度/60秒 = 0.1度/秒,即每隔一秒鐘就讓分針的度數(shù)加0.1度。這樣我們就知道了分針每秒要加的度數(shù)。接下里看一下時(shí)針,同理,時(shí)針走一圈是12小時(shí),那么每小時(shí)就走360度/12小時(shí),而每小時(shí)等于3600秒,所以每秒鐘也就是360度/(12*3600秒) = 1/120度/秒。這樣,每隔一秒鐘,分針和時(shí)針要加相應(yīng)的度數(shù)也都已經(jīng)求出來啦,代碼相信你也一定會(huì)了,和秒針一樣的邏輯,
private TimerTask task = new TimerTask() {
@Override
public void run() {
if (mSecondDegree == 360) {
mSecondDegree = 0;
}
if (mMinDegree == 360) {
mMinDegree = 0;
}
if (mHourDegree == 360) {
mHourDegree = 0;
}
mSecondDegree = mSecondDegree + 6;//秒針
mMinDegree = mMinDegree + 0.1f;//分針
mHourDegree = mHourDegree + 1.0f/240;//時(shí)針
postInvalidate();
}
};
在onDraw方法中,畫布旋轉(zhuǎn)的具體度數(shù)就由定時(shí)任務(wù)中算出來的度數(shù)。
//分針
canvas.save();
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(4);
canvas.rotate(mMinDegree);//定時(shí)任務(wù)中算出分針的度數(shù)
canvas.drawLine(0, 0, 0,
-130, mPaint);
canvas.restore();
//時(shí)針
canvas.save();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(7);
canvas.rotate(mHourDegree);//定時(shí)任務(wù)中算出時(shí)針的度數(shù)
canvas.drawLine(0, 0, 0,
-90, mPaint);
canvas.restore();
這樣我們的三個(gè)指針就都可以動(dòng)起來了,當(dāng)然分針和時(shí)針動(dòng)的不是很明顯,你可以過上一段時(shí)間在來看一下。
好了,時(shí)鐘總算是動(dòng)起來了,不過還有個(gè)問題,就是我們無法給它設(shè)置時(shí)間。接下來,我們來看看如何給他設(shè)置時(shí)間。設(shè)置時(shí)間說白了就是給每個(gè)指針的角度設(shè)置一個(gè)具體值。首先我們?cè)賮砝硪槐殛P(guān)系:秒針一秒鐘走6度(一圈60秒共走了360度);分針一鐘也是走6度(一圈60分鐘走共走了360度);而時(shí)針一小時(shí)走30度(一圈12小時(shí)共走了360度)。所以我們就可以根據(jù)具體的時(shí)間來求出各指針的角度。比如我們要設(shè)置時(shí)間:1點(diǎn)30分30秒,那么根據(jù)上述關(guān)系求時(shí)針的角度為1*30 = 30度;分針的角度為30*6 = 180度;秒針的角度為30*6=180度;
自定義TimeView里的代碼如下:
public void setTime(int hour, int min, int second) {
mMinDegree = min * 6f;
mHourDegree = hour * 30f;
mSecondDegree = second * 6f;
invalidate();//重繪控件
}
然后我們?cè)贛ainActivity中調(diào)用上面的方法
public class MainActivity extends AppCompatActivity {
private TimeView time_view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
time_view = (TimeView)findViewById(R.id.time_view);
time_view.setTime(1,30, 30);
time_view.start();
}
}
運(yùn)行后看一下效果:

額。。是不是看著特變扭,沒錯(cuò)細(xì)心的你一定看出來了,分針在30分的時(shí)候,時(shí)鐘卻還是在1點(diǎn)整,秒針都走了30多秒了,分針確還停在30分鐘的位置上。所以我們上面角度計(jì)算的還是有點(diǎn)問題,我們知道30分30秒其實(shí)就是30.5分鐘,而我們計(jì)算時(shí)僅僅只算了30分鐘的角度,少了那0.5分鐘。所以我們還是得把傳入的秒轉(zhuǎn)換為分鐘,即mMinDegree = (min + second * 1.0f/60f) *6f;同理時(shí)針的角度和分針秒針都有關(guān),我們得把傳入的分和秒也都轉(zhuǎn)換為小時(shí)再計(jì)算它的角度,即mHourDegree = (hour + min * 1.0f/60f + second * 1.0f/3600f)*30f;
修改后的代碼:
public void setTime(int hour, int min, int second) {
if (hour >= 24 || hour < 0 || min >= 60 || min < 0 || second >= 60 || second < 0) {
Toast.makeText(getContext(), "時(shí)間不合法",
Toast.LENGTH_SHORT).show();
return;
}
if (hour >= 12) {//這里我們采用24小時(shí)制
mIsNight = true;//添加一個(gè)變量,用于記錄是否為下午。
mHourDegree = (hour + min * 1.0f/60f + second * 1.0f/3600f - 12)*30f;
} else {
mIsNight = false;
mHourDegree = (hour + min * 1.0f/60f + second * 1.0f/3600f )*30f;
}
mMinDegree = (min + second * 1.0f/60f) *6f;
mSecondDegree = second * 6f;
invalidate();
}
代碼還是很簡(jiǎn)潔的,這里我們采用的是24小時(shí)制,給時(shí)分秒加了邊界的判斷,然后當(dāng)傳入的小時(shí)大于12時(shí),就讓它減去12小時(shí)計(jì)算它的角度,并且我們定義一個(gè)變量mIsNight,這個(gè)變量用于標(biāo)志是否為下午,當(dāng)傳入的小時(shí)大于12個(gè)小時(shí),使他為true,這個(gè)變量會(huì)在后面獲取時(shí)鐘時(shí)間時(shí)用到。好了我們?cè)僦匦逻\(yùn)行下代碼,效果如下:

這樣三個(gè)指針的位置就比較合理了。
好了,知道了如何設(shè)置時(shí)間后我們?cè)賮砜纯慈绾潍@取當(dāng)前時(shí)間。其實(shí)也很簡(jiǎn)單,上面我們知道時(shí)針走30度時(shí)一個(gè)小時(shí),也就是3600秒,所以一度就是3600秒/30度 = 120秒。我們就可以根據(jù)時(shí)針走的度數(shù),來求出一共是多少秒。比如時(shí)針正好為90度,也就是整3點(diǎn)鐘的位置,那么我們可求出共有3 * 3600秒 = 10800秒。這樣我們定義一個(gè)記錄總秒數(shù)的變量mTotalSecond = mHourDegree * 120。具體代碼如下
public float getTimeTotalSecond() {
if (mIsNight) {//判斷是否為下午,是的話再加12個(gè)小時(shí)
mTotalSecond = mHourDegree * 120 + 12 * 3600;
return mTotalSecond;
} else {
mTotalSecond = mHourDegree * 120;
return mTotalSecond;
}
}
有了總秒數(shù),時(shí)分秒就比較好求了,具體代碼如下:
public int getHour() {//獲取小時(shí)
return (int) (getTimeTotalSecond() / 3600);
}
public int getMin() {//獲取分鐘
return (int) ((getTimeTotalSecond() - getHour() * 3600) / 60);
}
public int getSecond() {//獲取秒鐘
return (int) (getTimeTotalSecond() - getHour() * 3600 - getMin() * 60);
}
這樣我們的時(shí)鐘就可以進(jìn)行設(shè)置和獲取時(shí)間的操作了。有了基本的功能,我們?cè)賮砜匆幌聵邮椒矫妫覀冏远x的控件說到底是拿來用的,不同的人有不同的喜好,比如有的人想將時(shí)鐘邊框的顏色設(shè)置成黑的,有的人就喜歡紅色。所以接下來我們看看,如何在XML布局文件里自由設(shè)置樣式,比如時(shí)鐘邊框的顏色。首先,我們?cè)趘alues文件夾下新建一個(gè)attrs.xml文件,里面的內(nèi)容為
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TimeView">
<attr name="borderColor" format="color"/>//自定義屬性
</declare-styleable>
</resources>
我們可以看到,在資源標(biāo)簽下有一個(gè)declare-styleable標(biāo)簽,名字可以任意取,這里就叫TimeView。然后在這個(gè)標(biāo)簽下有個(gè)attr標(biāo)簽,這個(gè)標(biāo)簽就是我們自定義的屬性,這里就拿邊框顏色為例,名字可以任意起,易讀就可以了,這里就叫borderColor由于 我們定義的屬性和顏色相關(guān),這里的format就是color,format還有很多其他格式,如果是布爾型,那么它就是boolean,如果是長(zhǎng)度的話就是dimension,當(dāng)然還有很多其他格式,大家可以查閱官網(wǎng),這里就不細(xì)講了。然后我們?cè)诔跏甲远x控件的時(shí)候添加如下代碼
private void init(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs,
R.styleable.TimeView);
borderColor = ta.getColor(R.styleable.TimeView_borderColor,
Color.BLACK);//獲取布局文中設(shè)置的顏色,默認(rèn)設(shè)置為黑色
ta.recycle();
}
獲取顏色后,我們就要在畫邊框之前將畫筆設(shè)置成獲取到的顏色代碼如下:
mPaint.setColor(borderColor);//將畫筆顏色設(shè)置成獲取到的顏色
mPaint.setStrokeWidth(2);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 3, mPaint);
mPaint.setColor(Color.BLACK);
接著我們就可以在XML文件中設(shè)置自己喜歡的顏色了,完整的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:background="#fff"
>
<com.example.administrator.timeviewdemo.TimeView
android:layout_gravity="center_horizontal"
android:id="@+id/time_view"
custom:borderColor="#ff0000"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
我們要在根布局加上了這么一句話xmlns:custom="http://schemas.android.com/apk/res-auto",只有加上這句話,你才能使用自已之前定義的屬性,這里的custom可以是任意的,但必須和下面的要保持一致。好了,看一下效果吧:

可以看到,設(shè)置的還是挺成功的,當(dāng)然你還可以添加其他的屬性來豐富你的樣式,這里就不在一一演示了。這里我們的寬高是和父布局一樣大小,如果你覺得太大,可以讓它的寬高變小點(diǎn),比如我們可以給它的寬高都設(shè)置成300dp,我們來看一下效果:

可以看到時(shí)鐘變小了,我們?cè)賹捀叨几臑閣rap_content試試吧:

。。額,好像不起作用,明明是wrap_content,怎么還是和match_parent的效果一樣,這究竟是怎么回事呢?
其實(shí)這就牽扯到自定義view的測(cè)量,我們先來看看一個(gè)控件展示在手機(jī)屏幕上的幾個(gè)過程,或者說是執(zhí)行哪些方法:
- onMeasure-----------告訴系統(tǒng)這個(gè)自定義控件多大
- onLayou -----------告訴系統(tǒng)這個(gè)控件放哪。單獨(dú)的一個(gè)view不需要調(diào)用這個(gè)方法,主要是針對(duì)自定義ViewGroup的,關(guān)于自定義ViewGroup,后續(xù)會(huì)有文章詳細(xì)講解,這里就先不講了。
- onDraw -----------告訴系統(tǒng)這個(gè)控件展示的內(nèi)容,這個(gè)方法在上一篇Android自定義控件之圓形時(shí)鐘講的也是比較詳細(xì)了,這里就不在贅述了。
所以今天我們就來看看這個(gè)onMeasure方法。 我們要想告訴系統(tǒng)這個(gè)控件多大,只用在onMeasure方法中調(diào)用setMeasuredDimension(int width,int height),將你想要設(shè)置的寬高傳入就可以了。比如我們想把寬高都設(shè)置為300,代碼如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(300,300);
}
這樣控件的寬高就是300啦,我們來運(yùn)行下:

不過,如果這樣設(shè)置的話,你自己寫的是很爽,但你讓別人怎么活。。。。。這樣設(shè)置的話,別人就無法再在XML文件中進(jìn)行寬高的設(shè)置。。因?yàn)椴还茉趺丛O(shè)置,最后都被你指定為300不變。。。要是有人用這個(gè)控件的話,估計(jì)都會(huì)開始懷疑人生了。。。那么該怎么辦呢?這個(gè)時(shí)候MeasureSpec這個(gè)類就閃亮登場(chǎng)了。這個(gè)類是一個(gè)32位的int值,高兩位是測(cè)量模式,低30位是測(cè)量尺寸。測(cè)量模式一共有三種:
- EXACTLY
當(dāng)我們?cè)诓季治募兄付▽捀邽榫唧w的值或者指定為match_parent時(shí),系統(tǒng)用的就是這個(gè)模式 - AT_MOST
當(dāng)我們?cè)诓季治募校付▽捀邽閣rap_content時(shí),就是這個(gè)模式。 - UNSPECIFIED
這個(gè)模式比較特殊,就是view想要多大就有多大,一般不怎么用
如果你在自定義view時(shí)不重寫onMeasure這個(gè)方法,那么系統(tǒng)默認(rèn)只支持EXACTLY這個(gè)模式,即你可以在布局文件中,指定控件寬高一個(gè)具體的數(shù)值,也可以讓它match_parent。但是無法識(shí)別wrap_content的,要想讓wrap_content有效,我們就要重寫onMeasure方法,然后給控件指定一個(gè)大小。代碼如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}
//自己寫的測(cè)量寬度的方法
private int measureWidth(int measureSpec) {
int result;
int specSize = MeasureSpec.getSize(measureSpec);
int specMode = MeasureSpec.getMode(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 300;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
可以看到,我們自己寫了一個(gè)方法,用于測(cè)量控件的寬,由于這個(gè)控件是圓形的所以寬高是一樣的,這里就只貼出測(cè)量寬的代碼。我們根據(jù)系統(tǒng)傳入的MeasureSpec類,來獲取測(cè)量尺寸和測(cè)量模式,即得到specSize 和specMode ,如果我們?cè)赬ML文件中指定控件的具體數(shù)值大小,那么獲取到的specSize 就等于這個(gè)具體的值,如果是match_parent,那么這個(gè)值就是父控件的值。然后我們來看一下獲取到的specMode ,如果布局文件指定為match_parent,那么specMode 就等于MeasureSpec.EXACTLY,如果是wrap_content,那么就等于MeasureSpec.AT_MOST。接下來就是判斷獲取的是什么模式,根據(jù)不同的模式來返回具體的測(cè)量值。如果是EXACTLY那么測(cè)量的結(jié)果就是specSize ,如果是AT_MOST,那么我們指定一個(gè)具體的值,然后和specSize 比較,較小者作為測(cè)量的值。這個(gè)測(cè)量的代碼,其實(shí)可以作為一個(gè)模板,這樣onMeasure這個(gè)方法其實(shí)也沒有什么難的地方,以后要重寫onMeasure方法時(shí),套用這個(gè)模板就行了,然后在AT_MOST時(shí),指定自己需要的大小。
好了最后我們寫個(gè)Demo來演示下我們的時(shí)鐘把,首先看一下XML代碼:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:background="#fff"
>
<com.example.administrator.timeviewdemo.TimeView
android:id="@+id/time_view"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_gravity="center_horizontal"
custom:secondPointerColor="#fff"
custom:borderColor="#f12"
custom:borderWidth="3dp"
custom:maxScaleColor="#fff"
custom:minScaleColor="#fff"
custom:midScaleColor="#fff"
custom:maxScaleLength="14dp"
custom:midScaleLength="10dp"
custom:minScaleLength="7dp"
custom:centerPointRadiu="2dp"
custom:centerPointType="circle"
custom:centerPointColor="#fff"
custom:secondPointerLength="80dp"
custom:minPointerLength="50dp"
custom:hourPointerLength="30dp"
custom:minPointerColor="#fff"
custom:hourPointerColor="#fff"
custom:isSecondGoSmooth="false"
custom:textColor="#fff"
custom:textSize="20sp"
custom:circleBackground="#0af"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/hour"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:hint="時(shí)"/>
<EditText
android:id="@+id/min"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:hint="分"/>
<EditText
android:id="@+id/second"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:hint="秒"/>
</LinearLayout>
<Button
android:layout_margin="10dp"
android:id="@+id/set_time"
android:textColor="#fff"
android:background="@color/colorPrimary"
android:layout_gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="設(shè)置時(shí)間"/>
<Button
android:layout_margin="10dp"
android:id="@+id/get_time"
android:textColor="#fff"
android:background="@color/colorPrimary"
android:layout_gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="獲取時(shí)間"/>
</LinearLayout>
布局文件也很簡(jiǎn)單,一個(gè)是我們的自定義控件,可以看到我添加很多別的樣式。然后下面三個(gè)EditText分別可以輸入時(shí)分秒,然后下面兩個(gè)Button,用來設(shè)置和獲取時(shí)間。接下來,看一下MainActivity中的代碼:
public class MainActivity extends AppCompatActivity {
private TimeView time_view;
private EditText hour;
private EditText min;
private EditText second;
private Button set_time;
private Button get_time;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
time_view = (TimeView)findViewById(R.id.time_view);
hour = (EditText)findViewById(R.id.hour);
min = (EditText)findViewById(R.id.min);
second = (EditText)findViewById(R.id.second);
set_time = (Button)findViewById(R.id.set_time);
get_time = (Button)findViewById(R.id.get_time);
set_time.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
time_view.setTime(Integer.parseInt(hour.getText().toString()),
Integer.parseInt(min.getText().toString()),
Integer.parseInt(second.getText().toString()));
}
});
get_time.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this,
time_view.getHour()+":"+time_view.getMin()+":"+time_view.getSecond()+"",
Toast.LENGTH_SHORT).show();
}
});
time_view.setTime(1,30,30);//設(shè)置了默認(rèn)時(shí)間
time_view.start();
}
}
代碼還是很簡(jiǎn)單的,分別給兩個(gè)按鈕添加點(diǎn)擊事件,然后給時(shí)鐘一個(gè)初始的時(shí)間,并調(diào)用start方法,讓它動(dòng)起來。我們來看一下效果吧!

好了,這個(gè)自定義時(shí)鐘到此就告一段落了,通過這篇文章以及上一篇的Android自定義控件之圓形時(shí)鐘簡(jiǎn)單介紹了Android自定義view的一些基礎(chǔ)知識(shí),也算是自己學(xué)習(xí)的一點(diǎn)總結(jié)吧
完整代碼已上傳到github:https://github.com/Lloyd0577/CustomClockForAndroid,歡迎Star、Fork (__)