1、概述android
Android自定義View / ViewGroup的步驟大體以下:canvas
1) 自定義屬性;
2) 選擇和設置構造方法;
3) 重寫onMeasure()方法;
4) 重寫onDraw()方法;
5) 重寫onLayout()方法;
6) 重寫其餘事件的方法(滑動監聽等)。
Android自定義屬性主要有定義、使用和獲取三個步驟。app
參考:http://blog.csdn.net/lmj623565791/article/details/45022631/異步
咱們一般將自定義屬性定義在/values/attr.xml文件中(attr.xml文件須要本身建立)。函數
先來看一段示例代碼:佈局
<?xml version="1.0" encoding="utf-8"?> <resources> <attr name="rightPadding" format="dimension" /> <declare-styleable name="CustomMenu"> <attr name="rightPadding" /> </declare-styleable> </resources>
能夠看到,咱們先是定義了一個屬性rightPadding,而後又在CustomMenu中引用了這個屬性。下面說明一下:post
經常使用的format類型:學習
1) string:字符串類型;
2) integer:整數類型;
3) float:浮點型;
4) dimension:尺寸,後面必須跟dp、dip、px、sp等單位;
5) Boolean:布爾值;
6) reference:引用類型,傳入的是某一資源的ID,必須以「@」符號開頭;
7) color:顏色,必須是「#」符號開頭;
8) fraction:百分比,必須是「%」符號結尾;
9) enum:枚舉類型
下面對format類型說明幾點:字體
<resources> <attr name="orientation"> <enum name="horizontal" value="0" /> <enum name="vertical" value="1" /> </attr> <declare-styleable name="CustomView"> <attr name="orientation" /> </declare-styleable> </resources>
使用時經過getInt()方法獲取到value並判斷,根據不一樣的value進行不一樣的操做便可。優化
在XML佈局文件中使用自定義的屬性時,咱們須要先定義一個namespace。Android中默認的namespace是android,所以咱們一般可使用「android:xxx」的格式去設置一個控件的某個屬性,android這個namespace的定義是在XML文件的頭標籤中定義的,一般是這樣的:
xmlns:android="http://schemas.android.com/apk/res/android"
咱們自定義的屬性不在這個命名空間下,所以咱們須要添加一個命名空間。
自定義屬性的命名空間以下:
xmlns:app="http://schemas.android.com/apk/res-auto"
能夠看出來,除了將命名空間的名稱從android改爲app以外,就是將最後的「res/android」改爲了「res-auto」。
注意:自定義namespace的名稱能夠本身定義,不必定非得是app。
在自定義View / ViewGroup中,咱們能夠經過TypedArray獲取到自定義的屬性。示例代碼以下:
public CustomMenu(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomMenu, defStyleAttr, 0); int indexCount = a.getIndexCount(); for (int i = 0; i < indexCount; i++) { int attr = a.getIndex(i); switch (attr) { case R.styleable.CustomMenu_rightPadding: mMenuRightPadding = a.getDimensionPixelSize(attr, 0); break; } } a.recycle(); }
這裏須要說明一下:
當咱們定義一個新的類繼承了View或ViewGroup時,系統都會提示咱們重寫它的構造方法。View / ViewGroup中又四個構造方法能夠重寫,它們分別有1、2、3、四個參數。四個參數的構造方法咱們一般用不到,所以這個章節中咱們主要介紹一個參數、兩個參數和三個參數的構造方法(這裏以CustomMenu控件爲例)。
構造方法的代碼: public CustomMenu(Context context) { …… }
這個構造方法只有一個參數Context上下文。當咱們在JAVA代碼中直接經過new關鍵在建立這個控件時,就會調用這個方法。
構造方法的代碼: public CustomMenu(Context context, AttributeSet attrs) { …… }
這個構造方法有兩個參數:Context上下文和AttributeSet屬性集。當咱們須要在自定義控件中獲取屬性時,就默認調用這個構造方法。AttributeSet對象就是這個控件中定義的全部屬性。
咱們能夠經過AttributeSet對象的getAttributeCount()方法獲取屬性的個數,經過getAttributeName()方法獲取到某條屬性的名稱,經過getAttributeValue()方法獲取到某條屬性的值。
注意:無論有沒有使用自定義屬性,都會默認調用這個構造方法,「使用了自定義屬性就會默認調用三個參數的構造方法」的說法是錯誤的。
構造方法的代碼: public CustomMenu(Context context, AttributeSet attrs, int defStyleAttr) { …… }
這個構造方法中有三個參數:Context上下文、AttributeSet屬性集和defStyleAttr自定義屬性的引用。這個構造方法不會默認調用,必需要手動調用,這個構造方法和兩個參數的構造方法的惟一區別就是這個構造方法給咱們默認傳入了一個默認屬性集。
defStyleAttr指向的是自定義屬性的<declare-styleable>標籤中定義的自定義屬性集,咱們在建立TypedArray對象時須要用到defStyleAttr。
通常狀況下,咱們會將這三個構造方法串聯起來,即層層調用,讓最終的業務處理都集中在三個參數的構造方法。咱們讓一參的構造方法引用兩參的構造方法,兩參的構造方法引用三參的構造方法。示例代碼以下:
public CustomMenu(Context context) { this(context, null); } public CustomMenu(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CustomMenu(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // 業務代碼 }
這樣一來,就能夠保證不管使用什麼方式建立這個控件,最終都會到三個參數的構造方法中處理,減小了重複代碼。
onMeasure()方法中主要負責測量,決定控件自己或其子控件所佔的寬高。咱們能夠經過onMeasure()方法提供的參數widthMeasureSpec和heightMeasureSpec來分別獲取控件寬度和高度的測量模式和測量值(測量 = 測量模式 + 測量值)。
widthMeasureSpec和heightMeasureSpec雖然只是int類型的值,但它們是經過MeasureSpec類進行了編碼處理的,其中封裝了測量模式和測量值,所以咱們能夠分別經過MeasureSpec.getMode(xMeasureSpec)和MeasureSpec. getSize(xMeasureSpec)來獲取到控件或其子View的測量模式和測量值。
測量模式分爲如下三種狀況:
1) EXACTLY:當寬高值設置爲具體值時使用,如100DIP、match_parent等,此時取出的size是精確的尺寸;
2) AT_MOST:當寬高值設置爲wrap_content時使用,此時取出的size是控件最大可得到的空間;
3) UNSPECIFIED:當沒有指定寬高值時使用(不多見)。
onMeasure()方法中經常使用的方法:
1) getChildCount():獲取子View的數量;
2) getChildAt(i):獲取第i個子控件;
3) subView.getLayoutParams().width/height:設置或獲取子控件的寬或高;
4) measureChild(child, widthMeasureSpec, heightMeasureSpec):測量子View的寬高;
5) child.getMeasuredHeight/width():執行完measureChild()方法後就能夠經過這種方式獲取子View的寬高值;
6) getPaddingLeft/Right/Top/Bottom():獲取控件的四周內邊距;
7) setMeasuredDimension(width, height):從新設置控件的寬高。若是寫了這句代碼,就須要刪除「super. onMeasure(widthMeasureSpec, heightMeasureSpec);」這行代碼。
注意:onMeasure()方法可能被調用屢次,這是由於控件中的內容或子View可能對分配給本身的空間「不滿意」,所以向父空間申請從新分配空間。
onDraw()方法負責繪製,即若是咱們但願獲得的效果在Android原生控件中沒有現成的支持,那麼咱們就須要本身繪製咱們的自定義控件的顯示效果。
要學習onDraw()方法,咱們就須要學習在onDraw()方法中使用最多的兩個類:Paint和Canvas。
注意:每次觸摸了自定義View/ViewGroup時都會觸發onDraw()方法。
Paint畫筆對象,這個類中包含了如何繪製幾何圖形、文字和位圖的樣式和顏色信息,指定了如何繪製文本和圖形。畫筆對象右不少設置方法,大致上能夠分爲兩類:一類與圖形繪製有關,一類與文本繪製有關。
Paint類中有以下方法:
一、圖形繪製:
1) setArgb(int a, int r, int g, int b):設置繪製的顏色,a表示透明度,r、g、b表示顏色值;
2) setAlpha(int a):設置繪製的圖形的透明度;
3) setColor(int color):設置繪製的顏色;
4) setAntiAlias(boolean a):設置是否使用抗鋸齒功能,抗鋸齒功能會消耗較大資源,繪製圖形的速度會減慢;
5) setDither(boolean b):設置是否使用圖像抖動處理,會使圖像顏色更加平滑飽滿,更加清晰;
6) setFileterBitmap(Boolean b):設置是否在動畫中濾掉Bitmap的優化,能夠加快顯示速度;
7) setMaskFilter(MaskFilter mf):設置MaskFilter來實現濾鏡的效果;
8) setColorFilter(ColorFilter cf):設置顏色過濾器,能夠在繪製顏色時實現不一樣顏色的變換效果;
9) setPathEffect(PathEffect pe):設置繪製的路徑的效果;
10) setShader(Shader s):設置Shader繪製各類漸變效果;
11) setShadowLayer(float r, int x, int y, int c):在圖形下面設置陰影層,r爲陰影角度,x和y爲陰影在x軸和y軸上的距離,c爲陰影的顏色;
12) setStyle(Paint.Style s):設置畫筆的樣式:FILL實心;STROKE空心;FILL_OR_STROKE同時實心與空心;
13) setStrokeCap(Paint.Cap c):當設置畫筆樣式爲STROKE或FILL_OR_STROKE時,設置筆刷的圖形樣式;
14) setStrokeJoin(Paint.Join j):設置繪製時各圖形的結合方式;
15) setStrokeWidth(float w):當畫筆樣式爲STROKE或FILL_OR_STROKE時,設置筆刷的粗細度;
16) setXfermode(Xfermode m):設置圖形重疊時的處理方式;
二、文本繪製:
1) setTextAlign(Path.Align a):設置繪製的文本的對齊方式;
2) setTextScaleX(float s):設置文本在X軸的縮放比例,能夠實現文字的拉伸效果;
3) setTextSize(float s):設置字號;
4) setTextSkewX(float s):設置斜體文字,s是文字傾斜度;
5) setTypeFace(TypeFace tf):設置字體風格,包括粗體、斜體等;
6) setUnderlineText(boolean b):設置繪製的文本是否帶有下劃線效果;
7) setStrikeThruText(boolean b):設置繪製的文本是否帶有刪除線效果;
8) setFakeBoldText(boolean b):模擬實現粗體文字,若是設置在小字體上效果會很是差;
9) setSubpixelText(boolean b):若是設置爲true則有助於文本在LCD屏幕上顯示效果;
三、其餘方法:
1) getTextBounds(String t, int s, int e, Rect b):將頁面中t文本從s下標開始到e下標結束的全部字符所佔的區域寬高封裝到b這個矩形中;
2) clearShadowLayer():清除陰影層;
3) measureText(String t, int s, int e):返回t文本中從s下標開始到e下標結束的全部字符所佔的寬度;
4) reset():重置畫筆爲默認值。
這裏須要就幾個方法解釋一下:
一、setPathEffect(PathEffect pe):設置繪製的路徑的效果:
常見的有如下幾種可選方案:
1) CornerPathEffect:能夠用圓角來代替尖銳的角;
2) DathPathEffect:虛線,由短線和點組成;
3) DiscretePathEffect:荊棘狀的線條;
4) PathDashPathEffect:定義一種新的形狀並將其做爲原始路徑的輪廓標記;
5) SumPathEffect:在一條路徑中順序添加參數中的效果;
6) ComposePathEffect:將兩種效果組合起來,先使用第一種效果,在此基礎上應用第二種效果。
二、setXfermode(Xfermode m):設置圖形重疊時的處理方式:
關於Xfermode的多種效果,咱們能夠參考下面一張圖:
在使用的時候,咱們須要經過paint. setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XXX))來設置,XXX是上圖中的某種模式對應的常量參數,如DST_OUT。
這16中狀況的具體解釋以下:
1.PorterDuff.Mode.CLEAR:所繪製不會提交到畫布上。
2.PorterDuff.Mode.SRC:顯示上層繪製圖片
3.PorterDuff.Mode.DST:顯示下層繪製圖片
4.PorterDuff.Mode.SRC_OVER:正常繪製顯示,上下層繪製疊蓋。
5.PorterDuff.Mode.DST_OVER:上下層都顯示。下層居上顯示。
6.PorterDuff.Mode.SRC_IN:取兩層繪製交集。顯示上層。
7.PorterDuff.Mode.DST_IN:取兩層繪製交集。顯示下層。
8.PorterDuff.Mode.SRC_OUT:上層繪製非交集部分。
9.PorterDuff.Mode.DST_OUT:取下層繪製非交集部分。
10.PorterDuff.Mode.SRC_ATOP:取下層非交集部分與上層交集部分
11.PorterDuff.Mode.DST_ATOP:取上層非交集部分與下層交集部分
12.PorterDuff.Mode.XOR:異或:去除兩圖層交集部分
13.PorterDuff.Mode.DARKEN:取兩圖層所有區域,交集部分顏色加深
14.PorterDuff.Mode.LIGHTEN:取兩圖層所有,點亮交集部分顏色
15.PorterDuff.Mode.MULTIPLY:取兩圖層交集部分疊加後顏色
16.PorterDuff.Mode.SCREEN:取兩圖層所有區域,交集部分變爲透明色
Canvas即畫布,其上可使用Paint畫筆對象繪製不少東西。
Canvas對象中能夠繪製:
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():繪製頂點。
Canvas對象的其餘方法:
1) canvas.save():把當前繪製的圖像保存起來,讓後續的操做至關因而在一個新圖層上繪製;
2) canvas.restore():把當前畫布調整到上一個save()以前的狀態;
3) canvas.translate(dx, dy):把當前畫布的原點移到(dx, dy)點,後續操做都以(dx, dy)點做爲參照;
4) canvas.scale(x, y):將當前畫布在水平方向上縮放x倍,豎直方向上縮放y倍;
5) canvas.rotate(angle):將當前畫布順時針旋轉angle度。
onLayout()方法負責佈局,大多數狀況是在自定義ViewGroup中才會重寫,主要用來肯定子View在這個佈局空間中的擺放位置。
onLayout(boolean changed, int l, int t, int r, int b)方法有5個參數,其中changed表示這個控件是否有了新的尺寸或位置;l、t、r、b分別表示這個View相對於父佈局的左/上/右/下方的位置。
如下是onLayout()方法中經常使用的方法:
1) getChildCount():獲取子View的數量;
2) getChildAt(i):獲取第i個子View
3) getWidth/Height():獲取onMeasure()中返回的寬度和高度的測量值;
4) child.getLayoutParams():獲取到子View的LayoutParams對象;
5) child.getMeasuredWidth/Height():獲取onMeasure()方法中測量的子View的寬度和高度值;
6) getPaddingLeft/Right/Top/Bottom():獲取控件的四周內邊距;
7) child.layout(l, t, r, b):設置子View佈局的上下左右邊的座標。
generateLayoutParams()方法用在自定義ViewGroup中,用來指明子控件之間的關係,即與當前的ViewGroup對應的LayoutParams。咱們只須要在方法中返回一個咱們想要使用的LayoutParams類型的對象便可。
在generateLayoutParams()方法中須要傳入一個AttributeSet對象做爲參數,這個對象是這個ViewGroup的屬性集,系統根據這個ViewGroup的屬性集來定義子View的佈局規則,供子View使用。
例如,在自定義流式佈局中,咱們只須要關心子控件之間的間隔關係,所以咱們須要在generateLayoutParams()方法中返回一個new MarginLayoutParams()便可。
onTouchEvent()方法用來監測用戶手指操做。咱們經過方法中MotionEvent參數對象的getAction()方法來實時獲取用戶的手勢,有UP、DOWN和MOVE三個枚舉值,分別表示用於手指擡起、按下和滑動的動做。每當用戶有操做時,就會回掉onTouchEvent()方法。
若是咱們的自定義View / ViewGroup是繼承自ScrollView / HorizontalScrollView等能夠滾動的控件,就能夠經過重寫onScrollChanged()方法來監聽控件的滾動事件。
這個方法中有四個參數:l和t分別表示當前滑動到的點在水平和豎直方向上的座標;oldl和oldt分別表示上次滑動到的點在水平和豎直方向上的座標。咱們能夠經過這四個值對滑動進行處理,如添加屬性動畫等。
invalidate()方法的做用是請求View樹進行重繪,即draw()方法,若是視圖的大小發生了變化,還會調用layout()方法。
通常會引發invalidate()操做的函數以下:
1) 直接調用invalidate()方法,請求從新draw(),但只會繪製調用者自己;
2) 調用setSelection()方法,請求從新draw(),但只會繪製調用者自己;
3) 調用setVisibility()方法,會間接調用invalidate()方法,繼而繪製該View;
4) 調用setEnabled()方法,請求從新draw(),但不會從新繪製任何視圖,包括調用者自己。
功能與invalidate()方法相同,只是postInvalidate()方法是異步請求重繪視圖。
requestLayout()方法只是對View樹進行從新佈局layout過程(包括measure()過程和layout()過程),不會調用draw()過程,即不會從新繪製任何視圖,包括該調用者自己。
請求View樹的draw()過程,但只會繪製須要重繪的視圖,即哪一個View或ViewGroup調用了這個方法,就重繪哪一個視圖。
最後,讓咱們來總覽一下自定義View / ViewGroup時調用的各類函數的順序,以下圖所示:
在這些方法中: