1. 程式人生 > >【Android】ViewGroup全面分析

【Android】ViewGroup全面分析

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

複製程式碼
 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這個方法是必須要求實現的(後面具體講解)

假設現在如下使用這個類:

複製程式碼
 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:

複製程式碼
 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的,文件解釋如下:

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的&值,解釋如下:

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

(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  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  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  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  and  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  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裡面傳入的引數,具體是否一樣完全要看程式最後的計算。

相關推薦

AndroidViewGroup全面分析

一個Viewgroup基本的繼承類格式如下: 1 import android.content.Context; 2 import android.view.ViewGroup; 3 4 public class MyViewGroup extends Vi

AndroidRetrofit原始碼分析

Retrofit簡介 retrofit n. 式樣翻新,花樣翻新 vt. 給機器裝置裝配(新部件),翻新,改型 Retrofit 是一個 RESTful 的 HTTP 網路請求框架的封裝。注意這裡並沒有說它是網路請求框架,主要原因在於網路請求的工作並不是 Retrofit

AndroidOkHttp原始碼分析

Android為我們提供了兩種HTTP互動的方式:HttpURLConnection 和 Apache HttpClient,雖然兩者都支援HTTPS,流的上傳和下載,配置超時,IPv6和連線池,已足夠滿足我們各種HTTP請求的需求。但更高效的使用HTTP 可以讓您的應用執行更快、更節省

Android簡單全面的理解Handler機制

前言:Handler機制應該是網上講解最多的一種機制(沒有之一),本篇用通俗易懂的語言來介紹一下Handler機制,讓大家可以更好的理解。 什麼是Handler機制? Handler機制是AndroidSDK提供的一個非常重要的處理非同步訊息

Android原始碼分析 - LRUCache快取實現原理

一、Android中的快取策略 一般來說,快取策略主要包含快取的新增、獲取和刪除這三類操作。如何新增和獲取快取這個比較好理解,那麼為什麼還要刪除快取呢?這是因為不管是記憶體快取還是硬碟快取,它們的快取大小都是有限的。當快取滿了之後,再想其新增快取,這個時候就需要刪除一些舊的快取

Android原始碼分析 - View事件分發機制

事件分發物件 (1)所有 Touch 事件都被封裝成了 MotionEvent 物件,包括 Touch 的位置、時間、歷史記錄以及第幾個手指(多指觸控)等。 (2)事件型別分為 ACTION_DOWN, ACTION_UP,ACTION_MOVE,ACTION_POINTER_D

Android原始碼分析 - Activity啟動流程

啟動Activity的方式 Activity有2種啟動的方式,一種是在Launcher介面點選應用的圖示、另一種是在應用中通過Intent進行跳轉。我們主要介紹與後者相關的啟動流程。 Intent intent = new Intent(this, TestActivity

Android繪圖ViewGroup不執行

ViewGroup預設只負責子View的Measure和Layout,不會執行onDraw方法 可以在構造方法中呼叫super.setWillNotDraw(false),讓ViewGroup開啟繪製

AndroidBinder傳送檔案描述符分析

在進行dumpsys呼叫的時候,dump方法的第一個引數是檔案描述符 BinderProxy.java     publicvoid dump(FileDescriptor fd, String[] args)  通過傳送檔案描述符來讓服務端向給定的檔案寫資料, 等等,仔

android點選touch事件流程分析

1、onTouch和onTouchEvent的區別 public boolean dispatchTouchEvent(MotionEvent event) {         if (mOnTouchListener != null && mOnTouch

Android資料儲存資料庫SQLite(之前有看到的一篇關於SQLite文章,簡單明瞭、覆蓋較全面適合學習)

一:前言 之前介紹過Android中儲存資料的兩種方式:SharedPreferences和File,這篇介紹另一種儲存資料的方式——資料庫SQLite——輕量級資料庫系統。 資料庫:簡單來說可視為電子化的檔案櫃——儲存電子檔案的處所,使用者可以對檔案中的資料執行新增、擷取

Android原始碼分析

一、Android中的快取策略 一般來說,快取策略主要包含快取的新增、獲取和刪除這三類操作。如何新增和獲取快取這個比較好理解,那麼為什麼還要刪除快取呢?這是因為不管是記憶體快取還是硬碟快取,它們的快取大小都是有限的。當快取滿了之後,再想其新增快取,這個時候

Android問題分析G-sensor因資料互動問題導致手機crash

通過工作抓取到的kernel層的log(截選重要部分)如下顯示: <6>[ 7040.264871] SysRq: Show backtrace of all active CPUs <6>[ 7040.264925]Backtrace for cpu 0 (current):

Android浮動視窗層級分析

轉載自:http://www.liuguangli.win/archives/category/uncategorized/android 最近在專案中遇到了這樣的需求:需要在特定的其他應用之上懸浮自己的UI互動(拖動、輸入等複雜的UI互動),和九遊的浮窗類似,不過我們

AndroidAndroid聊天機器人實現

小米 div bottom 曾經 圖靈 .9.png sdn http 歡迎界面 昨天看到一個Android視頻教程講圖靈機器人。那個API接口用起來還是挺方便的,就準備自己動手做一個了。另外自己還使用了高德地圖的API接口用於定位(曾經用過高德的接口,比X度方便) 大

AndroidEclipse自己主動編譯NDK/JNI的三種方法

comm tro mman gnu tex android項目 syn color ng- 【Android】Eclipse自己主動編譯NDK/JNI的三種方法 SkySeraph Sep. 18th 2014 Email:[email protec

androiduses-permission和permission具體解釋

.com 新的 -i weight bsp htm fin article 程序 1.<uses-permission>: 官方描寫敘述: If an application needs access to a feature prote

NOI2015程序自動分析

單個 -- space != name style namespace cstring 說明 題目描述 在實現程序自動分析的過程中,常常需要判定一些約束條件是否能被同時滿足。 考慮一個約束滿足問題的簡化版本:假設x1,x2,x3...代表程序中出現的變量,給定n個形如x

Android自己定義圓形ImageView(圓形頭像 可指定大小)

代碼實現 err float avi rim war tor pos dsm 近期在仿手Q的UI,這裏面常常要用到的就是圓形頭像,看到 在android中畫圓形圖片的幾種辦法 這篇文章,了解了制作這樣的頭像的原理.只是裏面提供的方法另一個不足的地方就是

Android獲取控件的寬和高

height string -a @override parent popu tle post spa 有時候我們須要在Activity的時候獲取控件的寬和高來做一些操作,以下介紹三種獲取寬和高的方式: 1. onWindowFoc