Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android自定義View原理分析

android自定義View原理分析

編輯:關於Android編程

自定義view

一.View的MeasureSpec

1.MeasureSpec包SpecMode和SpecSize。其中SpecMode包括

UNSPECIFIED:父容器不對該view做限制,要多大給多大,一般用於系統內部

EXACTLY:父容器已經得到該view的確切大小,對應於match_parent和給出確定大小值。

AT_MOST:父容器給出該view可能的最大尺寸,對應於wrap_content

2.一個View的大小由其MeasureSpec確定,而其MeasureSpec一般由該View的LayoutParams和父容器確定(父容器提供調用子view的measure()並傳遞過去MeasureSpec)。這裡說一般是因為我們可以重寫view的onMeasure()從而自己設置view的MeasureSpec

一個View的MeasureSpec的確定過程

childLayoutParams

parentSpecMode

EXACTLY

AT_MOST

UNSPECIFIED

dp/px

EXACTLY+childsize

EXACTLY+childSize

EXACTLY+childSize

Match_parent

EXACTLY+parentSize

AT_MOST+parentSize

UNSPECIFIED+0

Wrap_content

AT_MOST+parentSize

AT_MOST+parentSize

UNSPECIFIED+0

 

二.measure,layout,draw等簡要分析

\

1. 從函數內部實現角度分析

view:

measure():調用onMeasure()

onMeasure(MeasureSpec):根據參數設置本view的寬高(調用setMeasureDimension ()方法)

layout(int):根據參數設置本view的位置

onLayout():空方法

draw():主要做四件事1.畫背景2.畫自己即調用自己的onDraw()3. 畫子view即 調用dispatchDraw()4.畫裝飾

onDraw():空方法

 

ViewGroup:

measure():同view

onMeasure:同view

layout():同view(在ViewGroup中被標記為final)

onLayout():同view

draw():同view

onDraw():同view

measureChild(): 獲取到子view再根據ViewGroup的MeasureSpec和子view的 LayoutParams計算其MeasureSpec,然後調用子view的measure (MeasureSpec)

 

可以看到ViewGroup在這裡函數的實現上都是直接繼承view的

 

2.從函數被賦予(期望)的功能的角度分析

view:

measure():調用onMeasure(),final無法重寫

onMeasure(MeasureSpec):設置自己的寬高

layout(int):設置自己的位置

onLayout():空方法,子view不需要實現

draw():1.畫背景,2.調用onDraw(),3.畫修飾

onDraw():畫view自己圖案。

 

ViewGroup:

measure():調用onMeasure(),final無法被重寫

onMeasure:1.遍歷調用子view的measure()2.設置自己的寬高

layout():在ViewGroup中被標記為final

onLayout():設置子view的位置即遍歷調用子view的layout()

draw():final不可重寫,作用1.畫背景,2.調用onDraw(),3.調用dispatchDraw() 畫子view,4.畫修飾

onDraw():我們可以重寫該方法,當不要在該方法裡去調用子view的draw(),因為 draw()裡面已經這樣做了,該方法的唯一作用是,如果我們想在ViewGroup裡畫一 些東西,比如背景,線條等,才來重寫該方法畫畫。

 

三.measure祥解

ViewGroup的 measure

ViewGroup的measure()由外部調用,並且傳入外界設置的寬高(MeasureSpec)。measure()是final的,所以不可重寫。接著measure()會調用ViewGroup自己的onMeasure(),同時把measure()傳進來的寬高傳給它。ViewGroup是不會自動調用子view的measure()的,需要在ViewGroup的onMeasure()裡自己調用子view的measure(),並且傳進去期望的寬高。ViewGroup並沒有重寫onMeasure()

1.ViewGroup得到自己的寬高:通過measure()傳進來的參數,在onMeasure()中得到

2.ViewGroup設置自己的寬高:通過重寫onMeasure()

3.ViewGroup要得到所有子view的寬高:可以通過獲取所有子view的layoutParams來計算。

4.ViewGroup設置子view的寬高:通過調用子view的measure()並傳進去參數。當然,ViewGroup對子view設置的寬高只是子View的一個參考值,子view完全可以自己來定義寬高。

雖然ViewGroup沒有重寫view的onMeasure()方法,但是它提供了measureChild()和measureChildren()來對子view繼續測量,其實內部就是獲取到子view再計算其MeasureSpec(通過子view的LayoutParams,可以是通過xml得到)

,然後調用子view的measure()。

\

 

子View的measure

子view的measure()方法由外界調用,一般為其父容器,並且傳進來寬高(MeasureSpec)。measure()是final,不能被重寫。接著measure()會調用自己的onMeasure(),同時傳進去寬高。從而確定view的寬高。在這裡,我們可以重寫onMeasure(),來設置view的寬高。

1.子View得到自己的寬高:通過measure()傳進來的參數,在onMeasure()中得到

2.子View設置自己的寬高:通過重寫onMeasure()

 

注意事項

1.重寫view,如果要在xml布局文件裡使用,並且允許使用wrap_content屬性,則必須在重寫onMeasure()時設定wrap_content的大小,否則就相當於match_parent(原因:當設置view的屬性為wrap_content時,其SpecMode為AT_MOST,specSize為parentSize,邏輯上跟match_parent一樣)做法如下:

\

其中mWidth和mHeight為原先設定的大小。

 

2.當view的onMeasure()執行完後,可以通過調用getMeasuredWidth/Height()來得到測量的結果。應該注意,Activity的生命周期和view的周期是不同步的,要先獲得view的准確測量結果,可以通過Activity的onWindowFocusChanged()方法回調中獲取,或者通過對view設置post。

 

 

3.view裡面有一個重要的方法setWillNotDraw(boolean),當View不需要畫任何東西時(一般是ViewGroup)可以吧它設置為true。view默認設置為false,ViewGroup默認設置為true

 

4.子view在onMeasure()裡設置的寬高最好跟layout()裡的t-b,r-l對應。雖然我們可以不這樣做,當為避免混亂,最好做到一致,在不一致的情況下以layout()裡的為准。getMeasureWidth/Height()獲取的是onMeasure()裡設置的寬高,getWidth/Height()獲取的是最終的寬高但必須結果layout()。

 

5.一個view原始的onMeasure()和layout()必須被調用過,父ViewGroup的dispatchDraw()才會調用該view的onDraw()

6.自定義view的xx(Context context)構造函數為Java中創建view對象時使用

xx(Context context,AttributeSet attrs)為在xml布局文件中使用

 

 

 

 

四.具體自定義過程及細節

Android的頂級view是DecorView(是一個FrameLayout),包括了整個屏幕。DecorView包含了一個LinearLayout,該LinearLayout的上面是titleBar,下面是一個id為content的view,就是我們在Activity中調用setContentView()時設置的view的位置。該view的寬為屏幕的寬度,mode為EXACTLY。高為屏幕除去titleBar後的高度,mode為EXACTLY

 

自定義view

1.重寫onMeasure()

\

其中參數是MeasureSpec格式mode+size(具體查看表)該參數是根據父容器的MeasureSpec和該子view自身在xml裡設置的寬高參數得到的(當然前提得是父容器是調用measureChild()來觸發子view的onMeasure()的;或者父容器有根據自己的MeasureSpec和子view在xml裡的設置改變傳遞給子view的MeasureSpec)

view在onMeasure()裡要做的事情就是設置自己的寬高即調用setMeasuredDimension(int width ,int height)或者直接super.onMeasure()。而view的高寬是要根據父容器傳給它的MeasureSpec來計算的。當SpecMode為EXACTLY時,直接把寬高設置為SpecSize就好。當SpecMode為AT_MOST時,此時的SpecSize為view所能設置的寬高的最大值,view一般在這種情況下回吧自己的尺寸設置為原先設置的默認尺寸。

 

2.重寫layout()

子view的layout()一般不需要做太多修改,因為父容器已經給它設置好需要的位置。

 

3.重寫onDraw()

子view的onDraw()功能和ViewGroup的不同,子view在這裡要把自己的內容圖案都畫出來。

 

 

 

自定義ViewGroup

1.重寫onMeasure()

\

其中參數是MeasureSpec格式mode+size(具體查看表)該參數是根據父容器的MeasureSpec和自身在xml裡設置的寬高參數得到的(當然前提是父容器是調用measureChild()來觸發子view的onMeasure()的;或者父容器有根據自己的MeasureSpec和子view在xml裡的設置改變傳遞給子view的MeasureSpec)

 

ViewGroup在onMeasure()裡面主要要做倆件事:

 

第一件事是觸發所有子view的onMeasure():

在onMeasure()裡面ViewGroup要先觸發所有子view的onMeasure(),可以通過兩種方法1.調用measureChildren(),然後等子view的onMeasure()執行後就可以調用子view的函數入getMeasureWidth/Height()等來獲取測量結果。2.自己調用子view的measure(),然後傳進去MeasureSpec參數,從而觸發子view的onMeasure(),這裡要注意的是在傳給子view MeasureSepc的時候要根據ViewGroup自己的MeasureSpec和子view的LayoutParams設置好傳遞給子view的MeasureSpec(即measureChild()的原理)。

一般情況下推薦使用第1種方法。

 

第二件事是設置自己的寬和高:

設置自己的寬高可以調用setMeasuredDimension(int width ,int height);而設置寬高需要根據父容器傳進來的MeasureSpec(包含了父容器和自身xml布局設置的影響)和所有子view的參數。當ViewGroup在xml布局裡設置為Match_parent或者具體的數值時最好辦,可以直接確定寬高;當ViewGroup在xml布局裡設置為wrap_content時需要特殊處理(注意ViewGroup和View處理wrap_content是不同的),具體看該ViewGroup的功能。比如LinearLayout(vertical)在處理wrap_content的時候是獲取所有子view的高度和margin,相加的值和parentSize比,然後選擇小的那個設置為自己的高度。

 

2.重寫onLayout()

ViewGroup的onLayout()是一個空實現,我們必須重寫它。onLayout()其作用就是調用所有子view的layout()來設置子view的位置即左上角的坐標和右下角的坐標。在設置子view的位置的時候需要根據在子view的layoutParams和ViewGroup的特點。要做到子view的寬=|l - r|,高=|t - b|

 

3.重寫onDraw()

在ViewGroup中,onDraw()要做的事就是畫自己的一些背景,線條之類的。不要在onDraw()裡面其調用子view的draw(),因為在ViewGroup的draw()裡已經遍歷了子view的draw()了。

 

 

 

 

 

 

 

五.view的自定義屬性步驟

1.在res/values裡創建attrs.xml文件,再在裡面聲明屬性,如下

\

其中sutext為屬性名,string為屬性的特性,而declare-styleable則聲明了一個屬性集,其name要與自定義的view的名字相同。

2.在布局文件中使用自定義屬性

首先要聲明命名空間

在Android studio中統一用如下格式,su可自定義

\

eclipse中用以下格式

\

後面為包名。

聲明命名空間後就可以使用了

\

3.在view中獲取自定義屬性

\

 

 

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved