天天看點

Android View系統分析之二View與ViewGroup,androidviewgroup 目錄

在Android View系統分析之從setContentView說開來(一)一文中,我們從setContentView開始闡述了Android中的視圖層次,從設定内容布局到整個視圖層次的建立的過程。并且對View和ViewGroup的關系進行了簡單的介紹,今天我們繼續來深入的了解Android中的View和ViewGroup。

我們在定義一個布局時,在它的頂層通常都是使用LinearLayout或者RelativeLayout等元件來包裝一些子控件,例如TextView, Button, ImageView等。例如:

為什麼是這樣呢?如果不用RelativeLayout包裹可不可以呢?

在Android View系統分析之從setContentView說開來(一)中的ViewGroup章節中我們說了,Android中的視圖樹是按照如下結構來組織的,即對頂層的是一個ViewGroup,然後其子節點可以是ViewGroup、View,隻有ViewGroup下能夠包含子節點,View則是葉子節點。如圖 : 

View是螢幕上所有可見元素的根類,其中ViewGroup也是View的子類。View的官方說明如下 :

This class represents the basic building block for user interface components. A View occupies a rectangular area on the screen and is responsible for drawing and event handling. View is the base class for widgets, which are used to create interactive UI components (buttons, text fields, etc.). The ViewGroup subclass is the base class for layouts, which are invisible containers that hold other Views (or other ViewGroups) and define their layout properties.

即一個View類的對象是一個包含了一定區域的可以在螢幕上繪制、并且處理事件的UI元件,所有可以放在螢幕上顯示的都是View的子類。

而ViewGroup的子類則負責這些View類型對象的布局,即規定這些View放在螢幕的哪些地方。

是以,通常情況下我們都需要使用RelativeLayout,LinearLayout等ViewGroup來組織它的子節點,以限制子View的大小、顯示位置等。當然也可以不用RelativeLayout來包裹,但是這樣的話布局中隻可以放置一個View或者ViewGroup元素,因為沒有ViewGroup的組織,布局xml中隻能有一個元素。例如 : 

在我們通過Activity的setContentView設定了Activity的頁面内容以後,随着Activity的onResume方法的調用,整個Activity的内容就會顯示到螢幕上。那麼在顯示之前,整個視圖過程經曆了哪些階段呢?

簡單來說,View樹解析完以後到顯示的過程,主要會經曆如下幾個過程。

即ViewRoot首先會發送一個周遊視圖樹的消息,然後會調用performTraversals()函數來周遊整個視圖樹,并且調用DecorView的measure()、layout()、draw()方法,這三個過程分别為測量View的大小、計算View的顯示過程、繪制内容,它們隻是一個模闆,具體的實作都是在onMeasure()、onLayout()、onDraw()中。周遊視圖樹中每個子View中的measure()、layout()、draw()這幾個過程,如果子View是ViewGroup類型,那麼又會周遊調用這個ViewGroup的所有子View的這三個過程,直到周遊完整個視圖樹為止。經曆了這三個過程,UI系統就知道了View的大小和它所在的位置坐标以後,我們就可以把内容繪制到相應的地方,然後内容就顯示出來了,就是這麼回事!

對于View類型,其主要的過程就是measure()和draw(),并沒有layout()布局這個過程。在View中的onLayout()都是空實作,為什麼是這樣呢?

因為View類型的UI元素并不是一個View容器,隻有ViewGroup類型的才是,而ViewGroup才負責View的布局和管理,是以布局操作隻用在ViewGroup中。由ViewGroup來确定子View放在哪裡,是以也就是為什麼我們在xml中要用ViewGroup類型的RelativeLayout、LinearLayout等元件來包裹子元素的原因。

UI系統對View進行的第一個重要的操作就是View的丈量,即測量這個View的大小。在measure()方法調用的onMeasure(int widthMeasureSpec, intheightMeasureSpec)函數中實作具體的丈量操作,onMeasure函數中含有兩個參數,分别為寬度的丈量規格、高度的丈量規格,它們分别代表了寬度和高度的限制,由它的上一層ViewGroup傳遞過來,我們在xml中寫的layout_width和layout_height就是對應這兩個參數。注意

: 預設情況下onMeasure函數隻支援match_parent的規格,是以,如果你自定義View時需要支援wrap_content,那麼你必須覆寫onMeasure方法來實作。 一個View的大小由它設定的MeasureSpec規格和它的父控件共同決定,MeasureSpec是一個32位的int值,其中高2位為規格的模式,低30位為View的大小。規格模式由分為三種,分别為EXACTLY、AT_MOST、UNSPECIFIED,下面我們看看這幾種規格的含義。

View的大小是一個确切的數值,表示父視圖希望子視圖的大小應該是由MeasureSpec解析出來的specSize的值來決定的,系統預設會按照這個規則來設定子視圖的大小。例如使用者設定的layout_width為match_parent和具體的數值( 例如 100dp ) ,那麼這個規格的模式就是exactly的。

表示父容器指定了一個大小,

view 的大小不能大于這個值。對應的layout參數為wrap_content。

就表示目前視圖沒有指定它的大小測量模式,這時候就使用從規格參數中讀出的size值。開發人員可以将視圖按照自己的意願設定成任意的大小,沒有任何限制。這種情況比較少見,不太會用到。

  View的繪制相對就容易了,就是用Canvas繪制各種圖形呗。drawText、drawBitmap、drawArc、drawRect等。View的draw()函數是一個模闆方法,在其中會依次繪制背景、自身内容( onDraw函數中實作 ) 、以及繪制子View( ViewGroup 類型的才有,在dispatchDraw中實作 ) 、繪制邊界、繪制滾動條,代碼如下 :

關于Canvas的資料,可以參考Android--使用Canvas繪圖、利用Canvas繪制各種圖形。

先看看ViewGroup的官方說明 :

A ViewGroup is a special view that can contain other views (called children.) The view group is the base class for layouts and views containers. This class also defines the ViewGroup.LayoutParams class which serves as the base class for layouts parameters.

ViewGroup組織和管理它的子View,其實主要就是對子View進行布局(規定它們放在在哪裡)以及疊代所有子元素讓它們繪制自身,并且使用LayoutParams對象對子元素的大小、邊距等進行限制。我們在xml中定義一個View或者ViewGroup時使用的layout_width、layout_height就是LayoutParams對象的屬性。同樣,ViewGroup對于View的管理也是丈量、布局、繪制三個過程,隻是這三個過程更為複雜一些。每個過程都會周遊所有的子View,然後對它的子View也進行這三個過程。這樣,所有的子View就會在這三個過程都執行完畢之後,就會得到如下結果

1、經曆了onMeasure階段,如果該ViewGroup是wrap_cotent的,那麼由所有子View的的大小,否則根據丈量規格設定自身的大小;2、計算出各個子View應該在的坐标;3、繪制自身後,将所有子View繪制在相應的位置上。

ViewGroup的丈量就是周遊所有的child view, 然後對每個child view進行丈量操作,如果ViewGroup自身的大小是wrap_content的話會根據每個child view的大小計算出自身的大小,否則根據自身的 寬高規格設定自身的大小。注意 : 預設情況下onMeasure函數隻支援match_parent的規格,是以,如果你自定義View時需要支援wrap_content,那麼你必須覆寫onMeasure方法來實作。

ViewGroup類提供了幾個方法,友善我們對子View進行丈量,如下 : 

ViewGroup的布局就是對所有子View的布局,即指定子View放置的規則。在該函數中周遊所有子View,然後調用每個子View的layout(int

left, int top, int right, int bottom)方法進行布局。例如豎直排列的LinearLayout對于子元素的布局操作核心代碼如下 : 

ViewGroup的繪制操作不需要我們來進行處理,ViewGroup的預設onDraw()是繼承自View的,是以也是空實作。但是繪制子View的函數确實實作了的,即dispatchDraw方法。在View的draw模闆方法中,dispatchDraw就是負責繪制子View的。它的核心代碼如下

其中drawChild就是繪制子view的函數了。

自定義一般情況下隻需要覆寫View類的onDraw方法繪制自身内容,但是如上文中關于View的丈量提到的,如果該View需要支援wrap_content,那麼還需要覆寫onMeasure方法實作。在很多情況下,我們還需要自定義View的屬性,那麼隻需要在res/values中添加一個attrs.xml,在裡面添加一個名字為你的自定義View類名的declare-styleable項,然後添加你的自定義的屬性item。下面我們來自定義一個簡單的TextView。

自定義的MyTextView屬性, res/values/attrs.xml中定義。

這個自定義屬性的名字即為我們的自定義View的類名MyTextView, 這裡隻定義了兩個屬性,即text和textSize。在使用自定義View的屬性時,我們需要引入該自定義View所在的包名,在xml中引入包名是這樣的:

例如, android的包名是android,那麼它的命名空間就是,

呵,這個我們可見多了,每個布局用的xml中全都用這個。其實原理是這樣的,res/values/attrs.xml中内容會被編譯成R類,而R的完整路徑就是工程的包名.R,注意,是工程的包名,而不是自定義View所在的包,是以你引入了工程的包名,Android系統就可以找到對應的R類,進而找到你的自定義屬性,而你的自定義屬性名又與你的自定義View類名一緻,這樣也就對應上了。

Android View系統分析之二View與ViewGroup,androidviewgroup 目錄

例如MyTextView所在的包為com.example.touch_event.viewsystem,但是我們的工程的包卻是com.example.touch,是以R所在的路徑就是com.example.touch.R,是以我在引入自定義屬性的命名空間是需要引入的是com.example.touch,而不是com.example.touch_event.viewsystem。我們來試用一下吧:

main.xml : 

我們引入了命名空間mytv, 用法為  xmlns:mytv="http://schemas.android.com/apk/res/com.example.touch"。并且需要注意,MyTextView的layout_width和layout_height我們都是設定為wrap_content的。然後使用mytv:text設定文本内容,使用mytv:textSize來設定文本大小。

運作以後效果如下圖: 

可以看到,我們的View正常運作起來了。但是我們設定的layout_width和layout_height都是設定為wrap_content的,怎麼就變成了match_parent了呢?其實上文已經多次提到,View的預設onMeasure都隻是支援match_parent,如果需要wrap_content,你需要覆寫onMeasure來實作。那我們給MyTextView加上自己的onMeasure吧。

最後運作看效果 : 

效果不怎樣,但總歸是支援wrap_content了,細節就先不管了。

自定義ViewGroup主要覆寫的方法也就是兩個,即onMeasure和onLayout,其中隻有onLayout是必須實作的,覆寫onMeasure使其支援wrap_content,覆寫onLayout以實作子View的布局規則。直接看個例子吧。

main.xml的定義 : 

效果圖 :

圖1 ( 寬、高都為match_parent) 圖2( 寬、高都為wrap_parent)

依然是很醜陋,不過不要緊,大緻原理清晰就行。

您好,問題不奇怪,TextView的尺寸是會變小。這樣解釋:

控件有兩類非常重要的屬性,坐标:x,y; 尺寸:width,height.

控件其實是一些矩形框,這兩類屬性确定了後,就可以在Canvas上畫出這個矩形了。清楚這一點後,

就要知道android 怎麼确定X,Y和寬,高。

X,Y:是控件在父控件中的坐标

寬高沒什麼好講的,就是矩形的寬和高,

android通過View 的onLayout()确定控件在父控件中XY;通過onMeasure()确定控件寬高,想象一下一個控件樹(xml 布局檔案),從根節點開始,根節點XY和寬高通過視窗螢幕大小确定,它确定了後,依次調用其子節點的onLayout(),onMeasure()來确定子節點在父節點中的坐标和尺寸。就是android LayoutInflater整個過程了。(其他視窗系統的這個過程基本一樣)

了解這個後,就應該知道,控件的坐标和父控件有關;子控件尺寸,如果子控件有fill_parent這樣的屬性,那麼它的尺寸也和父控件有關了。

不知道你的具體代碼,不好說清。一般來說,viewgroup外面個scrollview,viewgroup設定 warp_content就應該可以的,

繼續閱讀