(轉載)【Android】ViewGroup全面分析

轉載自:http://www.cnblogs.com/lqminn/archive/2013/01/23/2866543.htmlhtml

 

一個Viewgroup基本的繼承類格式以下:node

複製代碼
 1 import android.content.Context;
 2 import android.view.ViewGroup;
 3 
 4 public class MyViewGroup extends ViewGroup{
 5 
 6     public MyViewGroup(Context context) {
 7         super(context);
 8         // TODO Auto-generated constructor stub
 9     }
10 
11     @Override
12     protected void onLayout(boolean changed, int l, int t, int r, int b) {
13         // TODO Auto-generated method stub
14         
15     }
16 }
複製代碼

如上所示,onLayout這個方法是必需要求實現的(後面具體講解)android

假設如今以下使用這個類:app

複製代碼
 1 package com.example.myviewgroup;
 2 
 3 import android.os.Bundle;
 4 import android.widget.ImageView;
 5 import android.app.Activity;
 6 import android.graphics.Color;
 7 
 8 public class MainActivity extends Activity {
 9     MyViewGroup group;
10     ImageView imageView;
11 
12     @Override
13     public void onCreate(Bundle savedInstanceState) {
14         super.onCreate(savedInstanceState);
15         
16         group = new MyViewGroup(MainActivity.this);
17         imageView = new ImageView(this);
18         imageView.setBackgroundResource(R.drawable.ic_launcher);
19         group.addView(imageView);
20         group.setBackgroundColor(Color.GREEN);
21         setContentView(group);
22     } 
23 }
複製代碼

你會發現界面上什麼都沒有,只是一片綠色,也就是說,子元素根本就沒有被繪製上去。注意到上面有一個要求重載的方法onLayout(),重載以下:框架

複製代碼
1     @Override
2     protected void onLayout(boolean changed, int l, int t, int r, int b) {
3         // TODO Auto-generated method stub
4         for(int index = 0; index < getChildCount(); index++){
5             View v = getChildAt(index);
6             v.layout(l, t, r, b);
7         }
8     
複製代碼

這個時候圖像就能顯示出來了。看代碼應該能基本理解緣由,咱們給每個child都設定了它的現實範圍,使用的方法是layout,固然這裏只是顯示了一個View,這裏只是基本。上面傳進去的四個參數分別表明着ViewGroup在整個界面上的上下左右邊框,也就是說,它框定了ViewGroup的可視化範圍,咱們要作的就是在這個範圍裏面安排咱們的子View。再繼續,假設咱們這樣使用自定義的ViewGroup:ide

複製代碼
 1 package com.example.myviewgroup;
 2 
 3 import android.os.Bundle;
 4 import android.widget.ImageView;
 5 import android.widget.LinearLayout;
 6 import android.widget.TextView;
 7 import android.app.Activity;
 8 import android.graphics.Color;
 9 
10 public class MainActivity extends Activity {
11     LinearLayout layout;
12     
13     MyViewGroup group;
14     TextView textView;
15     ImageView imageView;
16     @Override
17     public void onCreate(Bundle savedInstanceState) {
18         super.onCreate(savedInstanceState);
19         
20         layout = new LinearLayout(this);
21         group = new MyViewGroup(this);
22         imageView = new ImageView(this);
23         textView = new TextView(this);
24         
25         imageView.setBackgroundResource(R.drawable.ic_launcher);
26         textView.setText("Hello");
27         
28         layout.setOrientation(LinearLayout.VERTICAL);
29         layout.setBackgroundColor(Color.WHITE);
30         
31         layout.addView(imageView);
32         layout.addView(textView);
33         group.addView(layout, new LinearLayout.LayoutParams(100, 100));
34         group.setBackgroundColor(Color.GREEN);
35         setContentView(group);
36     } 
37 }
複製代碼

咱們會發現,整個界面又和之前同樣,只顯示一片綠色了,組件又不見了,你能夠嘗試改變layout的背景顏色,會發現最後顯示的界面顏色也變化了,因此能夠斷定,咱們這樣子寫,只是顯示了最最外層的代碼,並無觸發整個佈局去繪製她本身的子View(這裏指的是imageView和textView)。前面說到onLayout方法提供整個組件的可視範圍以便於子View佈局,那麼子View的大小如何肯定以及當子View是一個ViewGroup的時候怎麼觸發它去繪製本身的子View呢?這涉及ViewGroup的另一個方法:佈局

1     @Override
2     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
3         // TODO Auto-generated method stub
4         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
5     }

這個方法來自View,而不是ViewGroup的,文檔解釋以下:ui

Measure the view and its content to determine the measured width and the measured height. This method is invoked by measure(int, int) and should be overriden by subclasses to provide accurate and efficient measurement of their contents.

通俗解釋一下:這個方法實際上是用來丈量View自己以及它本身的尺寸的!什麼意思呢?咱們先看看傳入的參數是什麼。傳入的參數是兩個int,可是實際上這兩個int大有文章,是兩個int的&值,解釋以下:this

兩個參數分別表明寬度和高度的MeasureSpec,android2.2文檔中對於MeasureSpec中的說明是: 一個MeasureSpec封裝了從父容器傳遞給子容器的佈局需求.每個MeasureSpec表明了一個寬度,或者高度的說明.一個MeasureSpec是一個大小跟模式的組合值.一共有三種模式.spa

(1)UPSPECIFIED:父容器對於子容器沒有任何限制,子容器想要多大就多大.

(2) EXACTLY:父容器已經爲子容器設置了尺寸,子容器應當服從這些邊界,不論子容器想要多大的空間.

(3) AT_MOST:子容器能夠是聲明大小內的任意大小.

暫時先這樣解釋着,後面再去細說。總之,這兩個參數傳進來的是本View(ViewGroup)顯示的長和寬的值和某個模式的&值,具體取出模式或者值的方法以下:

1      int widthMode = MeasureSpec.getMode(widthMeasureSpec); 
2         int heightMode = MeasureSpec.getMode(heightMeasureSpec); 
3            
4         int widthSize = MeasureSpec.getSize(widthMeasureSpec); 
5         int heightSize = MeasureSpec.getSize(heightMeasureSpec); 

而合成則可使用下面的方法:

1 MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST)

OK,上面是一些介紹,到這裏可能比較混亂,整理一下:

若是讓你在一個界面上繪製一個矩形,爲了準確的畫出這個矩形,你必須知道兩件事情:1)矩形的位置(暫定爲左上角的座標);2)尺寸(長和寬),Android繪製圖形的時候也要知道這兩件事情,前面已經介紹了幾個方法了,如今把它們聯繫起來(你能夠想象,你用一個layoutA做爲contentView,而後在layoutA裏面要加一個button),Android會怎麼去作呢?最正規的解釋固然源自Android官方文檔:http://developer.android.com/guide/topics/ui/how-android-draws.html

首先看一下View樹的樣子:

咱們的界面基本上就是以這樣子的方式組織展示的。

When an Activity receives focus, it will be requested to draw its layout. The Android framework will handle the procedure for drawing, but the Activity must provide the root node of its layout hierarchy.

(當一個Activity獲取焦點的時候,它就會被要求去畫出它的佈局。Android框架會處理繪畫過程,可是Activity必須提供佈局的根節點,在上面的圖上,咱們能夠理解爲最上面的ViewGroup,而實際上還有一個更深的root)

Drawing begins with the root node of the layout. It is requested to measure and draw the layout tree. Drawing is handled by walking the tree and rendering each View that intersects the invalid region. In turn, each View group is responsible for requesting each of its children to be drawn (with the draw() method) and each View is responsible for drawing itself. Because the tree is traversed in-order, this means that parents will be drawn before (i.e., behind) their children, with siblings drawn in the order they appear in the tree.

(繪畫開始於佈局的根節點,要求測量而且畫出整個佈局樹。繪畫經過遍歷整個樹來完成,不可見的區域的View被放棄。每一個ViewGroup負責要求它的子View去繪畫,每一個子View則負責去繪畫本身。由於佈局樹是順序遍歷的,這意味着父View在子View以前被畫出來(這個符合常理,後面解釋))。

註解:假設一個TextView設置爲(FILL_PAREMT, FILL_PARENT),則很明顯必須先畫出父View的尺寸,才能去畫出這個TextView,並且從上至下也就是先畫父View再畫子View,顯示的時候才正常,不然父View會擋住子View的顯示。

Drawing the layout is a two pass process: a measure pass and a layout pass. The measuring pass is implemented in measure(int, int) and is a top-down traversal of the View tree. Each View pushes dimension specifications down the tree during the recursion. At the end of the measure pass, every View has stored its measurements. The second pass happens in layout(int, int, int, int) and is also top-down. During this pass each parent is responsible for positioning all of its children using the sizes computed in the measure pass.

(佈局繪畫涉及兩個過程:測量過程和佈局過程。測量過程經過measure方法實現,是View樹自頂向下的遍歷,每一個View在循環過程當中將尺寸細節往下傳遞,當測量過程完成以後,全部的View都存儲了本身的尺寸。第二個過程則是經過方法layout來實現的,也是自頂向下的。在這個過程當中,每一個父View負責經過計算好的尺寸放置它的子View。)

註解:這和前面說的同樣,一個過程是用來丈量尺寸的,一個過程是用來擺放位置的。

When a View's measure() method returns, its getMeasuredWidth() and getMeasuredHeight() values must be set, along with those for all of that View's descendants. A View's measured width and measured height values must respect the constraints imposed by the View's parents. This guarantees that at the end of the measure pass, all parents accept all of their children's measurements. A parent View may call measure() more than once on its children. For example, the parent may measure each child once with unspecified dimensions to find out how big they want to be, then call measure() on them again with actual numbers if the sum of all the children's unconstrained sizes is too big or too small (i.e., if the children don't agree among themselves as to how much space they each get, the parent will intervene and set the rules on the second pass).

(當一個View的measure()方法返回的時候,它的getMeasuredWidth和getMeasuredHeight方法的值必定是被設置好的。它全部的子節點一樣被設置好。一個View的測量寬和測量高必定要遵循父View的約束,這保證了在測量過程結束的時候,全部的父View能夠接受子View的測量值。一個父View或許會屢次調用子View的measure()方法。舉個例子,父View會使用不明確的尺寸去丈量看看子View到底須要多大,當子View總的尺寸太大或者過小的時候會再次使用實際的尺寸去調用onmeasure().)

The measure pass uses two classes to communicate dimensions. The ViewGroup.LayoutParams class is used by Views to tell their parents how they want to be measured and positioned. The base LayoutParams class just describes how big the View wants to be for both width and height. For each dimension, it can specify one of:

  • an exact number
  • FILL_PARENT, which means the View wants to be as big as its parent (minus padding)
  • WRAP_CONTENT, which means that the View wants to be just big enough to enclose its content (plus padding).

不解釋。

There are subclasses of LayoutParams for different subclasses of ViewGroup. For example, RelativeLayout has its own subclass of LayoutParams, which includes the ability to center child Views horizontally and vertically.

MeasureSpecs are used to push requirements down the tree from parent to child. A MeasureSpec can be in one of three modes:

  • UNSPECIFIED: This is used by a parent to determine the desired dimension of a child View. For example, a LinearLayout may call measure() on its child with the height set to UNSPECIFIED and a width of EXACTLY240 to find out how tall the child View wants to be given a width of 240 pixels.
  • EXACTLY: This is used by the parent to impose an exact size on the child. The child must use this size, and guarantee that all of its descendants will fit within this size.
  • AT_MOST: This is used by the parent to impose a maximum size on the child. The child must guarantee that it and all of its descendants will fit within this size.

這裏前面已經提到過,也很少說,注意紅色部分,也就是說能夠經過設置高爲一個肯定值(經過EXACTLY)來看看子View在這個寬度下會怎麼肯定本身的高度。

OKOK,再休息一下。上面的問題能夠獲得解決了,往重載的ViewGroup裏面添加Layout子View的時候,咱們須要重載以下:

複製代碼
1 @Override
2     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
3             caculateWidthAndPadding(MeasureSpec.getSize(widthMeasureSpec));
4         for(int index = 0; index < getChildCount(); index++){
5 
6                 child.measure(MeasureSpec.makeMeasureSpec(childSize, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(childSize, MeasureSpec.AT_MOST));
7         }
8         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
9     }
複製代碼

固然,具體的measure裏面傳入的參數你能夠本身決定,我在這裏根據widthMeasureSpec計算出一個子View的寬度(childSize),而後告訴全部的childView,你使用的最大尺寸就是childSize,不能超過(經過childSize),這個方法則會觸發子View的onMeasure()方法,去設置子View的佈局,由此咱們能夠能夠看到onMeasure這個方法的做用:

1)在這個方法裏面會循環調用子View的measure方法,不停的往下觸發子View去丈量本身的尺寸;

2)ViewGroup繼承於View,onMeasure方法在View類中的源碼以下:

1   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2         setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
3                 getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
4     }

關於getDefaultSize方法就很少說了,看看setMeasureDimension,源碼以下:

複製代碼
 1     /**
 2      * <p>This mehod must be called by {@link #onMeasure(int, int)} to store the
 3      * measured width and measured height. Failing to do so will trigger an
 4      * exception at measurement time.</p>
 5      *
 6      * @param measuredWidth the measured width of this view
 7      * @param measuredHeight the measured height of this view
 8      */
 9     protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
10         mMeasuredWidth = measuredWidth;
11         mMeasuredHeight = measuredHeight;
12 
13         mPrivateFlags |= MEASURED_DIMENSION_SET;
14     }
複製代碼

看到木有,它設置了本身的寬和高!咱們在重載View的時候,若是重載了onMeasure方法,就必定要調用setMeasureDimension方法,不然會拋出異常,而重載View'Group的時候,則只須要調用super.OnMeasure便可。

 

最後整理一下:

1)測量過程------>onMeasure(),傳入的參數是本View的可見長和寬,經過這個方法循環測量全部View的尺寸而且存儲在View裏面;

2)佈局過程------>onLayout(),傳入的參數是View可見區域的上下左右四邊的位置,在這個方法裏面能夠經過layout來放置子View;

 

補充:getWidth()和getMeasuredWidth()的區別

getWidth(): View在設定好佈局後,整個View的寬度

getMeasuredWidth():對View上的內容進行測量後獲得的View內容佔據的寬度。

很簡單,getWidth()就是View顯示以後的width,而getMeasuredWidth,從前面的源代碼就能夠看出來實際上是在measure裏面傳入的參數,具體是否同樣徹底要看程序最後的計算。

相關文章
相關標籤/搜索