自定義View

自定義View/ViewGroup基本步驟

  1. 選擇和設(shè)置構(gòu)造方法区转;
  2. 重寫(xiě)onMeasure()方法;
  3. 重寫(xiě)onDraw()方法;
  4. 重寫(xiě)onLayout()方法吧享;
  5. 自定義屬性;
  6. 重寫(xiě)其他事件的方法(滑動(dòng)監(jiān)聽(tīng)等)譬嚣。

下面我們就來(lái)進(jìn)行自定義View的繪制流程钢颂,在進(jìn)行繪制的開(kāi)始先看一下View常用的方法:


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è)量模式分為以下三種情況:

  1. EXACTLY:當(dāng)寬高值設(shè)置為具體值時(shí)使用翠勉,如100dip妖啥、match_parent等,此時(shí)取出的size是精確的尺寸对碌;
  2. AT_MOST:當(dāng)寬高值設(shè)置為wrap_content時(shí)使用迹栓,此時(shí)取出的size是控件最大可獲得的空間;
  3. 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()方法中常用的方法:

  1. getChildCount():獲取子View的數(shù)量坷衍;
  2. getChildAt(i):獲取第i個(gè)子控件寝优;
  3. subView.getLayoutParams().width/height:設(shè)置或獲取子控件的寬或高;
  4. measureChild(child, widthMeasureSpec, heightMeasureSpec):測(cè)量子View的寬高枫耳;
  5. child.getMeasuredHeight/width():執(zhí)行完measureChild()方法后就可以通過(guò)這種方式獲取子View的寬高值乏矾;
  6. getPaddingLeft/Right/Top/Bottom():獲取控件的四周內(nèi)邊距;
  7. 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()方法中常用的方法:

  1. getChildCount():獲取子View的數(shù)量鲁豪;
  2. getChildAt(i):獲取第i個(gè)子View
  3. getWidth/Height():獲取onMeasure()中返回的寬度和高度的測(cè)量值;
  4. child.getLayoutParams():獲取到子View的LayoutParams對(duì)象律秃;
  5. child.getMeasuredWidth/Height():獲取onMeasure()方法中測(cè)量的子View的寬度和高度值爬橡;
  6. getPaddingLeft/Right/Top/Bottom():獲取控件的四周內(nèi)邊距;
  7. 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è)類:PaintCanvas缕陕。

注意:每次自定義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ù)的順序九妈,如下圖所示:

自定義View流程

在這些方法中:
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種類型

  1. string:字符串類型;
  2. integer:整數(shù)類型凹蜈;
  3. float:浮點(diǎn)型限寞;
  4. dimension:尺寸,后面必須跟dp仰坦、dip履植、px、sp等單位缎岗;
  5. Boolean:布爾值静尼;
  6. reference:引用類型,傳入的是某一資源的ID传泊,必須以“@”符號(hào)開(kāi)頭鼠渺;
  7. color:顏色,必須是“#”符號(hào)開(kāi)頭眷细;
  8. fraction:百分比拦盹,必須是“%”符號(hào)結(jié)尾;
  9. enum:枚舉類
  10. flag:位或運(yùn)算
  11. 混合類型:屬性定義時(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 ();
    }

}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市沼侣,隨后出現(xiàn)的幾起案子祖能,更是在濱河造成了極大的恐慌,老刑警劉巖蛾洛,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件养铸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡轧膘,警方通過(guò)查閱死者的電腦和手機(jī)钞螟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)谎碍,“玉大人鳞滨,你說(shuō)我怎么就攤上這事◇〉恚” “怎么了拯啦?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)熔任。 經(jīng)常有香客問(wèn)我提岔,道長(zhǎng),這世上最難降的妖魔是什么笋敞? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任碱蒙,我火速辦了婚禮,結(jié)果婚禮上夯巷,老公的妹妹穿的比我還像新娘赛惩。我一直安慰自己,他們只是感情好趁餐,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布喷兼。 她就那樣靜靜地躺著,像睡著了一般后雷。 火紅的嫁衣襯著肌膚如雪季惯。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,784評(píng)論 1 290
  • 那天臀突,我揣著相機(jī)與錄音勉抓,去河邊找鬼。 笑死候学,一個(gè)胖子當(dāng)著我的面吹牛藕筋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播梳码,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼隐圾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼伍掀!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起暇藏,我...
    開(kāi)封第一講書(shū)人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蜜笤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后盐碱,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體瘩例,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年甸各,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片焰坪。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡趣倾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出某饰,到底是詐尸還是另有隱情儒恋,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布黔漂,位于F島的核電站诫尽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏炬守。R本人自食惡果不足惜牧嫉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望减途。 院中可真熱鬧酣藻,春花似錦、人聲如沸鳍置。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)税产。三九已至怕轿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辟拷,已是汗流浹背撞羽。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留衫冻,地道東北人放吩。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像羽杰,于是被迫代替她去往敵國(guó)和親渡紫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子到推,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容