自定義View/ViewGroup基本步驟
- 選擇和設(shè)置構(gòu)造方法区转;
- 重寫(xiě)onMeasure()方法;
- 重寫(xiě)onDraw()方法;
- 重寫(xiě)onLayout()方法吧享;
- 自定義屬性;
- 重寫(xiě)其他事件的方法(滑動(dòng)監(jiān)聽(tīng)等)譬嚣。
下面我們就來(lái)進(jìn)行自定義View的繪制流程钢颂,在進(jìn)行繪制的開(kāi)始先看一下View常用的方法:
一、自定義View
1. 構(gòu)造器
新建一個(gè)MyView類繼承自View類拜银,選擇構(gòu)造器殊鞭,這里有四個(gè)構(gòu)造器遭垛,區(qū)別是參數(shù)的不同,一般用前三個(gè)構(gòu)造器操灿,參數(shù)不一樣分別對(duì)應(yīng)不同的創(chuàng)建方式:
只有一個(gè)Context參數(shù)的構(gòu)造方法
public MyView(Context context) {
super(context);
}
通常是通過(guò)代碼初始化控件時(shí)使用锯仪,即當(dāng)我們?cè)贘AVA代碼中直接通過(guò)new關(guān)鍵在創(chuàng)建這個(gè)控件時(shí),就會(huì)調(diào)用這個(gè)方法.
兩個(gè)參數(shù)(Context上下文和AttributeSet屬性集)的構(gòu)造方法
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
兩個(gè)參數(shù)的構(gòu)造方法通常對(duì)應(yīng)布局文件中控件被映射成對(duì)象時(shí)調(diào)用(需要解析屬性)趾盐,即當(dāng)我們需要在自定義控件中獲取屬性時(shí)庶喜,就默認(rèn)調(diào)用這個(gè)構(gòu)造方法。AttributeSet對(duì)象就是這個(gè)控件中定義的所有屬性谤碳±?ǎ可以通過(guò)AttributeSet對(duì)象的getAttributeCount()方法獲取屬性的個(gè)數(shù),通過(guò)getAttributeName()方法獲取到某條屬性的名稱蜒简,通過(guò)getAttributeValue()方法獲取到某條屬性的值瘸羡。
注意:不管有沒(méi)有使用自定義屬性,都會(huì)默認(rèn)調(diào)用這個(gè)構(gòu)造方法
三個(gè)參數(shù)(Context上下文搓茬、AttributeSet屬性集和defStyleAttr自定義屬性)的構(gòu)造方法
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
這個(gè)構(gòu)造方法不會(huì)默認(rèn)調(diào)用犹赖,必須要手動(dòng)調(diào)用,這個(gè)構(gòu)造方法和兩個(gè)參數(shù)的構(gòu)造方法的唯一區(qū)別就是這個(gè)構(gòu)造方法給我們默認(rèn)傳入了一個(gè)默認(rèn)屬性集卷仑。defStyleAttr指向的是自定義屬性的<declare-styleable>標(biāo)簽中定義的自定義屬性集峻村,我們?cè)趧?chuàng)建TypedArray對(duì)象時(shí)需要用到defStyleAttr。
一般情況下锡凝,我們會(huì)將這三個(gè)構(gòu)造方法串聯(lián)起來(lái)層層調(diào)用粘昨,讓最終的業(yè)務(wù)處理都集中在三個(gè)參數(shù)的構(gòu)造方法。我們讓一參的構(gòu)造方法引用兩參的構(gòu)造方法窜锯,兩參的構(gòu)造方法引用三參的構(gòu)造方法张肾。示例代碼如下:
public MyView(Context context) {
this(context,null);
}
public MyView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
//業(yè)務(wù)代碼
}
2. 重寫(xiě)三個(gè)方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
2.1 OnMeasurea()方法
onMeasure()方法主要負(fù)責(zé)測(cè)量,決定控件本身或其子控件所占的寬高锚扎。我們可以通過(guò)onMeasure()方法提供的參數(shù)widthMeasureSpec和heightMeasureSpec來(lái)分別獲取控件寬度和高度的測(cè)量模式和測(cè)量值(測(cè)量 = 測(cè)量模式 + 測(cè)量值)吞瞪。
其中widthMeasureSpec和heightMeasureSpec雖然只是int類型的值,但它們是通過(guò)MeasureSpec類進(jìn)行了編碼處理的驾孔,其中封裝了測(cè)量模式和測(cè)量值芍秆,因此我們可以分別通過(guò)MeasureSpec.getMode(xMeasureSpec)和MeasureSpec. getSize(xMeasureSpec)來(lái)獲取到控件或其子View的測(cè)量模式和測(cè)量值。
測(cè)量模式分為以下三種情況:
- EXACTLY:當(dāng)寬高值設(shè)置為具體值時(shí)使用翠勉,如100dip妖啥、match_parent等,此時(shí)取出的size是精確的尺寸对碌;
- AT_MOST:當(dāng)寬高值設(shè)置為wrap_content時(shí)使用迹栓,此時(shí)取出的size是控件最大可獲得的空間;
- UNSPECIFIED:當(dāng)沒(méi)有指定寬高值時(shí)使用(很少見(jiàn))。
也就是說(shuō)如果我們的自定義控件在布局文件中克伊,只需要設(shè)置指定的具體寬高酥郭,或者M(jìn)ATCH_PARENT 的情況,我們可以不用重寫(xiě)onMeasure方法愿吹。但如果自定義控件需要設(shè)置包裹內(nèi)容WRAP_CONTENT 不从,我們需要重寫(xiě)onMeasure方法,為控件設(shè)置需要的尺寸犁跪;默認(rèn)情況下WRAP_CONTENT 的處理也將填充整個(gè)父控件椿息。
onMeasure方法最后需要調(diào)用setMeasuredDimension方法來(lái)保存測(cè)量的寬高值。
onMeasure()方法中常用的方法:
- getChildCount():獲取子View的數(shù)量坷衍;
- getChildAt(i):獲取第i個(gè)子控件寝优;
- subView.getLayoutParams().width/height:設(shè)置或獲取子控件的寬或高;
- measureChild(child, widthMeasureSpec, heightMeasureSpec):測(cè)量子View的寬高枫耳;
- child.getMeasuredHeight/width():執(zhí)行完measureChild()方法后就可以通過(guò)這種方式獲取子View的寬高值乏矾;
- getPaddingLeft/Right/Top/Bottom():獲取控件的四周內(nèi)邊距;
- setMeasuredDimension(width, height):重新設(shè)置控件的寬高迁杨。如果寫(xiě)了這句代碼钻心,就需要?jiǎng)h除“super. onMeasure(widthMeasureSpec, heightMeasureSpec);”這行代碼。
注意:onMeasure()方法可能被調(diào)用多次铅协,這是因?yàn)榭丶械膬?nèi)容或子View可能對(duì)分配給自己的空間“不滿意”捷沸,因此向父空間申請(qǐng)重新分配空間。
2.2 OnLayout()方法
onLayout()方法負(fù)責(zé)布局狐史,大多數(shù)情況是在自定義ViewGroup中才會(huì)重寫(xiě)痒给,主要用來(lái)確定子View在這個(gè)布局空間中的擺放位置。 onLayout(boolean changed, int left, int top, int right, int bottom)方法有5個(gè)參數(shù)骏全,其中changed表示這個(gè)控件是否有了新的尺寸或位置侈玄;left、top吟温、right、bottom分別表示這個(gè)View相對(duì)于父布局的左/上/右/下方的位置突颊。
以下是onLayout()方法中常用的方法:
- getChildCount():獲取子View的數(shù)量鲁豪;
- getChildAt(i):獲取第i個(gè)子View
- getWidth/Height():獲取onMeasure()中返回的寬度和高度的測(cè)量值;
- child.getLayoutParams():獲取到子View的LayoutParams對(duì)象律秃;
- child.getMeasuredWidth/Height():獲取onMeasure()方法中測(cè)量的子View的寬度和高度值爬橡;
- getPaddingLeft/Right/Top/Bottom():獲取控件的四周內(nèi)邊距;
- child.layout(l, t, r, b):設(shè)置子View布局的上下左右邊的坐標(biāo)棒动。
2.3 OnDraw()方法
onDraw()方法負(fù)責(zé)繪制糙申,即如果我們希望得到的效果在Android原生控件中沒(méi)有現(xiàn)成的支持,那么我們就需要自己繪制我們的自定義控件的顯示效果船惨。要學(xué)習(xí)onDraw()方法柜裸,我們就需要學(xué)習(xí)在onDraw()方法中使用最多的兩個(gè)類:Paint和Canvas缕陕。
注意:每次自定義View/ViewGroup時(shí)都會(huì)調(diào)用onDraw()方法。
Paint類
Paint畫(huà)筆對(duì)象疙挺,這個(gè)類中包含了如何繪制幾何圖形扛邑、文字和位圖的樣式和顏色信息,指定了如何繪制文本和圖形铐然。畫(huà)筆對(duì)象有很多設(shè)置方法蔬崩,大體上可以分為兩類:圖形繪制和文本繪制。
Paint類中有如下方法:
1搀暑、圖形繪制:
1) setArgb(int a, int r, int g, int b):設(shè)置繪制的顏色沥阳,a表示透明度,r自点、g桐罕、b表示顏色值;
2) setAlpha(int a):設(shè)置繪制的圖形的透明度樟氢;
3) setColor(int color):設(shè)置繪制的顏色冈绊;
4) setAntiAlias(boolean a):設(shè)置是否使用抗鋸齒功能,抗鋸齒功能會(huì)消耗較大資源埠啃,繪制圖形的速度會(huì)減慢死宣;
5) setDither(boolean b):設(shè)置是否使用圖像抖動(dòng)處理,會(huì)使圖像顏色更加平滑飽滿碴开,更加清晰毅该;
6) setFileterBitmap(Boolean b):設(shè)置是否在動(dòng)畫(huà)中濾掉Bitmap的優(yōu)化,可以加快顯示速度潦牛;
7) setMaskFilter(MaskFilter mf):設(shè)置MaskFilter來(lái)實(shí)現(xiàn)濾鏡的效果眶掌;
8) setColorFilter(ColorFilter cf):設(shè)置顏色過(guò)濾器,可以在繪制顏色時(shí)實(shí)現(xiàn)不同顏色的變換效果巴碗;
9) setPathEffect(PathEffect pe):設(shè)置繪制的路徑的效果朴爬;
10) setShader(Shader s):設(shè)置Shader繪制各種漸變效果;
11) setShadowLayer(float r, int x, int y, int c):在圖形下面設(shè)置陰影層橡淆,r為陰影角度召噩,x和y為陰影在x軸和y軸上的距離,c為陰影的顏色逸爵;
12) setStyle(Paint.Style s):設(shè)置畫(huà)筆的樣式:FILL實(shí)心具滴;STROKE空心;FILL_OR_STROKE同時(shí)實(shí)心與空心师倔;
13) setStrokeCap(Paint.Cap c):當(dāng)設(shè)置畫(huà)筆樣式為STROKE或FILL_OR_STROKE時(shí)构韵,設(shè)置筆刷的圖形樣式;
14) setStrokeJoin(Paint.Join j):設(shè)置繪制時(shí)各圖形的結(jié)合方式;
15) setStrokeWidth(float w):當(dāng)畫(huà)筆樣式為STROKE或FILL_OR_STROKE時(shí)疲恢,設(shè)置筆刷的粗細(xì)度凶朗;
16) setXfermode(Xfermode m):設(shè)置圖形重疊時(shí)的處理方式;</pre>
2冈闭、文本繪制:
1) setTextAlign(Path.Align a):設(shè)置繪制的文本的對(duì)齊方式俱尼;
2) setTextScaleX(float s):設(shè)置文本在X軸的縮放比例,可以實(shí)現(xiàn)文字的拉伸效果萎攒;
3) setTextSize(float s):設(shè)置字號(hào)遇八;
4) setTextSkewX(float s):設(shè)置斜體文字,s是文字傾斜度耍休;
5) setTypeFace(TypeFace tf):設(shè)置字體風(fēng)格刃永,包括粗體、斜體等羊精;
6) setUnderlineText(boolean b):設(shè)置繪制的文本是否帶有下劃線效果斯够;
7) setStrikeThruText(boolean b):設(shè)置繪制的文本是否帶有刪除線效果;
8) setFakeBoldText(boolean b):模擬實(shí)現(xiàn)粗體文字喧锦,如果設(shè)置在小字體上效果會(huì)非常差读规;
9) setSubpixelText(boolean b):如果設(shè)置為true則有助于文本在LCD屏幕上顯示效果;</pre>
Canvas類
Canvas即畫(huà)布燃少,其上可以使用Paint畫(huà)筆對(duì)象繪制很多東西束亏。
Canvas對(duì)象中可以繪制:
1) drawArc():繪制圓弧阵具;
2) drawBitmap():繪制Bitmap圖像碍遍;
3) drawCircle():繪制圓圈;
4) drawLine():繪制線條阳液;
5) drawOval():繪制橢圓怕敬;
6) drawPath():繪制Path路徑;
7) drawPicture():繪制Picture圖片帘皿;
8) drawRect():繪制矩形东跪;
9) drawRoundRect():繪制圓角矩形;
10) drawText():繪制文本鹰溜;
11) drawVertices():繪制頂點(diǎn)虽填。
Canvas對(duì)象的其他方法:
1) canvas.save():把當(dāng)前繪制的圖像保存起來(lái),讓后續(xù)的操作相當(dāng)于是在一個(gè)新圖層上繪制奉狈;
2) canvas.restore():把當(dāng)前畫(huà)布調(diào)整到上一個(gè)save()之前的狀態(tài);
3) canvas.translate(dx, dy):把當(dāng)前畫(huà)布的原點(diǎn)移到(dx, dy)點(diǎn)涩惑,后續(xù)操作都以(dx, dy)點(diǎn)作為參照仁期;
4) canvas.scale(x, y):將當(dāng)前畫(huà)布在水平方向上縮放x倍,豎直方向上縮放y倍;
5) canvas.rotate(angle):將當(dāng)前畫(huà)布順時(shí)針旋轉(zhuǎn)angle度.
2.4 其他方法
generateLayoutParams()
generateLayoutParams()方法用在自定義ViewGroup中跛蛋,用來(lái)指明子控件之間的關(guān)系熬的,即與當(dāng)前的ViewGroup對(duì)應(yīng)的LayoutParams。我們只需要在方法中返回一個(gè)我們想要使用的LayoutParams類型的對(duì)象即可赊级。在generateLayoutParams()方法中需要傳入一個(gè)AttributeSet對(duì)象作為參數(shù)押框,這個(gè)對(duì)象是這個(gè)ViewGroup的屬性集,系統(tǒng)根據(jù)這個(gè)ViewGroup的屬性集來(lái)定義子View的布局規(guī)則理逊,供子View使用橡伞。
例如,在自定義流式布局中晋被,我們只需要關(guān)心子控件之間的間隔關(guān)系兑徘,因此我們需要在generateLayoutParams()方法中返回一個(gè)new MarginLayoutParams()即可。
onTouchEvent()
onTouchEvent()方法用來(lái)監(jiān)測(cè)用戶手指操作羡洛。我們通過(guò)方法中MotionEvent參數(shù)對(duì)象的getAction()方法來(lái)實(shí)時(shí)獲取用戶的手勢(shì)挂脑,有UP、DOWN和MOVE三個(gè)枚舉值欲侮,分別表示用于手指抬起崭闲、按下和滑動(dòng)的動(dòng)作。每當(dāng)用戶有操作時(shí)威蕉,就會(huì)回掉onTouchEvent()方法刁俭。
onScrollChanged()
如果我們的自定義View / ViewGroup是繼承自ScrollView / HorizontalScrollView等可以滾動(dòng)的控件,就可以通過(guò)重寫(xiě)onScrollChanged()方法來(lái)監(jiān)聽(tīng)控件的滾動(dòng)事件忘伞。這個(gè)方法中有四個(gè)參數(shù):l和t分別表示當(dāng)前滑動(dòng)到的點(diǎn)在水平和豎直方向上的坐標(biāo)薄翅;oldl和oldt分別表示上次滑動(dòng)到的點(diǎn)在水平和豎直方向上的坐標(biāo)。我們可以通過(guò)這四個(gè)值對(duì)滑動(dòng)進(jìn)行處理氓奈,如添加屬性動(dòng)畫(huà)等翘魄。
invalidate()
invalidate()方法的作用是請(qǐng)求View樹(shù)進(jìn)行重繪,即draw()方法舀奶,如果視圖的大小發(fā)生了變化暑竟,還會(huì)調(diào)用layout()方法。
一般會(huì)引起invalidate()操作的函數(shù)如下:
1) 直接調(diào)用invalidate()方法育勺,請(qǐng)求重新draw()但荤,但只會(huì)繪制調(diào)用者本身;
2) 調(diào)用setSelection()方法涧至,請(qǐng)求重新draw()腹躁,但只會(huì)繪制調(diào)用者本身;
3) 調(diào)用setVisibility()方法南蓬,會(huì)間接調(diào)用invalidate()方法纺非,繼而繪制該View哑了;
4) 調(diào)用setEnabled()方法,請(qǐng)求重新draw()烧颖,但不會(huì)重新繪制任何視圖弱左,包括調(diào)用者本身。
postInvalidate()
功能與invalidate()方法相同炕淮,只是postInvalidate()方法是異步請(qǐng)求重繪視圖拆火。
postInvalidate()內(nèi)部調(diào)用的是postInvalidateDelayed(),可以用于控件的定時(shí)刷新涂圆。
requestLayout()
requestLayout()方法只是對(duì)View樹(shù)進(jìn)行重新布局layout過(guò)程(包括measure()過(guò)程和layout()過(guò)程)们镜,不會(huì)調(diào)用draw()過(guò)程,即不會(huì)重新繪制任何視圖乘综,包括該調(diào)用者本身憎账。
requestFocus()
請(qǐng)求View樹(shù)的draw()過(guò)程,但只會(huì)繪制需要重繪的視圖卡辰,即哪個(gè)View或ViewGroup調(diào)用了這個(gè)方法胞皱,就重繪哪個(gè)視圖。
自定義View / ViewGroup時(shí)調(diào)用的各種函數(shù)的順序九妈,如下圖所示:
在這些方法中:
onMeasure()會(huì)在初始化之后調(diào)用一到多次來(lái)測(cè)量控件或其中的子控件的寬高反砌;
onLayout()會(huì)在onMeasure()方法之后被調(diào)用一次,將控件或其子控件進(jìn)行布局萌朱;
onDraw()會(huì)在onLayout()方法之后調(diào)用一次宴树,也會(huì)在用戶手指觸摸屏幕時(shí)被調(diào)用多次,來(lái)繪制控件晶疼。
二酒贬、自定義屬性
1. 為什么要自定義屬性
我們?cè)谑褂孟到y(tǒng)控件的時(shí)候,利用控件或布局的系統(tǒng)屬性進(jìn)行設(shè)置得到我們需要的顯示效果翠霍,在使用自定義View的時(shí)候锭吨,就要使用自定義屬性來(lái)進(jìn)行設(shè)置。我們的自定義View是繼承自View的寒匙,查看系統(tǒng)的自定義屬性文件attrs.xml零如,看一下常用的控件是怎么定義的。系統(tǒng)定義的所有屬性我們可以在\sdk\platforms\Android-xx\data\res\values目錄下找到锄弱。
<declare-styleable name="View">
<attr name="id" format="reference" />
<attr name="background" format="reference|color" />
<attr name="padding" format="dimension" />
...
<attr name="focusable" format="boolean" />
...
</declare-styleable>
<declare-styleable name="TextView">
<attr name="text" format="string" localization="suggested" />
<attr name="hint" format="string" />
<attr name="textColor" />
<attr name="textColorHighlight" />
<attr name="textColorHint" />
...
</declare-styleable>
<declare-styleable name="ViewGroup_Layout">
<attr name="layout_width" format="dimension">
<enum name="fill_parent" value="-1" />
<enum name="match_parent" value="-1" />
<enum name="wrap_content" value="-2" />
</attr>
<attr name="layout_height" format="dimension">
<enum name="fill_parent" value="-1" />
<enum name="match_parent" value="-1" />
<enum name="wrap_content" value="-2" />
</attr>
</declare-styleable>
<declare-styleable name="LinearLayout_Layout">
<attr name="layout_width" />
<attr name="layout_height" />
<attr name="layout_weight" format="float" />
<attr name="layout_gravity" />
</declare-styleable>
<declare-styleable name="RelativeLayout_Layout">
<attr name="layout_centerInParent" format="boolean" />
<attr name="layout_centerHorizontal" format="boolean" />
<attr name="layout_centerVertical" format="boolean" />
...
</declare-styleable>
上面的標(biāo)簽考蕾,都有<declare-styleable name = "xxxx"> ,里面還有一堆的子標(biāo)簽会宪,子標(biāo)簽就表示這是這個(gè)XXX類的屬性肖卧,但是并不是每個(gè)控件都能使用所有屬性,比如LinearLayout可以使用layout_weight屬性掸鹅,但是在RelativeLayout中就不能使用塞帐」瞪常可以看出自定義屬性可以為我們自定義的View添加一些我們需要的屬性,達(dá)到我們需要的效果壁榕。
2. 怎么自定義屬性
根據(jù)上面的源碼可以看出,這兩種的區(qū)別就是attr標(biāo)簽后面帶不帶format屬性赎瞎,如果帶format的就是在定義屬性牌里,如果不帶format的就是在使用已有的屬性,name的值就是屬性的名字务甥,format是限定當(dāng)前定義的屬性能接受什么值牡辽。而系統(tǒng)定義的屬性一般引用都用android:XXX引用。
如果我們要定義一個(gè)text屬性敞临,所以我們可以有兩種定義我們的方式
常規(guī)方式:
<resources>
<declare-styleable name="MyTextView">
<attr name=“text" format="string" />
</declare-styleable>
</resources>
引用系統(tǒng)已經(jīng)定義好的:
<resources>
<declare-styleable name="MyTextView">
<attr name=“android:text"/>
</declare-styleable>
</resources>
為什么還要引自系統(tǒng)屬性呢态辛,因?yàn)樽远x的MyTextView是繼承的View,而android:text是TextView的特殊屬性即自己定義的屬性挺尿,所以這里必須要引用一下奏黑。其中declare-stylable標(biāo)簽只是為了給自定義屬性分類。一個(gè)項(xiàng)目中可能又多個(gè)自定義控件编矾,但只能又一個(gè)attr.xml文件熟史,因此我們需要對(duì)不同自定義控件中的自定義屬性進(jìn)行分類,這也是為什么declare-stylable標(biāo)簽中的name屬性往往定義成自定義控件的名稱窄俏。
3. format的屬性值
在自定義屬性的時(shí)候用format來(lái)規(guī)定屬性的類型蹂匹,format一共支持以下11種類型
- string:字符串類型;
- integer:整數(shù)類型凹蜈;
- float:浮點(diǎn)型限寞;
- dimension:尺寸,后面必須跟dp仰坦、dip履植、px、sp等單位缎岗;
- Boolean:布爾值静尼;
- reference:引用類型,傳入的是某一資源的ID传泊,必須以“@”符號(hào)開(kāi)頭鼠渺;
- color:顏色,必須是“#”符號(hào)開(kāi)頭眷细;
- fraction:百分比拦盹,必須是“%”符號(hào)結(jié)尾;
- enum:枚舉類
- flag:位或運(yùn)算
- 混合類型:屬性定義時(shí)可以指定多種類型值溪椎,用 “|” 隔開(kāi)
三普舆、 自定義View實(shí)踐之繪制時(shí)鐘
public class ClockView extends View{
private Calendar time;
private float centerX,centerY,radius;
private float width,height;
/**
* 繪制表盤(pán)及時(shí)鐘指針的畫(huà)筆
*/
private Paint paintPoint;
private Paint paintCircle;
private Paint paintHour;
private Paint paintMinute;
private Paint paintSecond;
public ClockView(Context context) {
super(context,null);
initView();
}
public ClockView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs,0);
initView();
}
public ClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
width = getMeasuredWidth();
height = getMeasuredHeight();
centerX = width/2;
centerY = height/2;
radius = Math.min(width/2,height/2);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//繪制外圓
drawCircle(centerX,centerY,radius,canvas);
//繪制小時(shí)點(diǎn)
int points = 0;
int pointCount = 12;
while (points++ < pointCount){
drawPoints(centerX,centerY/6,8,canvas);
canvas.rotate(30f,centerX,centerY);
}
//繪制圓心
canvas.drawCircle(centerX,centerY,10,paintPoint);
canvas.save ();
drawHand (canvas);
postInvalidateDelayed (1000);
}
public void initView(){
/**
* 繪制外圓表盤(pán)
*/
paintCircle = new Paint();
paintCircle.setStyle(Paint.Style.FILL);
paintCircle.setAntiAlias(true);
paintCircle.setColor(Color.WHITE);
/**
* 繪制小時(shí)圓點(diǎn)恬口,共十二個(gè)
*/
paintPoint = new Paint();
paintPoint.setStyle(Paint.Style.FILL);
paintPoint.setAntiAlias(true);
paintPoint.setColor(Color.GRAY);
paintHour = new Paint();
paintHour.setAntiAlias(true);
paintHour.setStrokeWidth(10f);
paintHour.setStyle(Paint.Style.FILL);
paintHour.setColor(Color.BLACK);
paintMinute = new Paint();
paintMinute.setAntiAlias(true);
paintMinute.setStrokeWidth(8f);
paintMinute.setStyle(Paint.Style.FILL);
paintMinute.setColor(Color.BLACK);
paintSecond = new Paint();
paintSecond.setAntiAlias(true);
paintSecond.setStrokeWidth(2f);
paintSecond.setStyle(Paint.Style.FILL);
paintSecond.setColor(Color.RED);
}
public void drawCircle(float x, float y, float radius, Canvas canvas){
canvas.drawCircle(x,y,radius,paintCircle);
}
public void drawPoints(float x, float y, float radius, Canvas canvas){
canvas.drawCircle(x,y,radius,paintPoint);
}
public void drawHand(Canvas canvas){
time = Calendar.getInstance();
float hour = time.get(Calendar.HOUR);
float minute = time.get(Calendar.MINUTE);
float second = time.get(Calendar.SECOND);
float angleHour = hour * (360 / 12)+minute/60*30;
//時(shí)針轉(zhuǎn)過(guò)的角度
float angleMinute = minute*6;
//分針轉(zhuǎn)過(guò)的角度
float angleSecond = second*6;
//秒針轉(zhuǎn)過(guò)的角度
//繪制時(shí)針
canvas.save ();
canvas.rotate (angleHour,centerX,centerY);
canvas.drawLine (centerX,centerY,centerX,centerY/2,paintHour);
canvas.restore ();
canvas.save ();
canvas.rotate (angleMinute,centerX,centerY);
canvas.drawLine (centerX,centerY,centerX,centerY/3,paintMinute);
canvas.restore ();
canvas.save ();
canvas.rotate (angleSecond,centerX,centerY);
canvas.drawLine (centerX,centerY,centerX,centerY/4,paintSecond);
canvas.restore ();
}
}