本文原創, 轉載請註明出處:http://blog.csdn.net/qinjuninghtml
上篇文章<<Android中measure過程、WRAP_CONTENT詳解以及xml佈局文件解析流程淺析(上)>>中,咱們java
瞭解了View樹的轉換過程以及如何設置View的LayoutParams的。本文繼續沿着既定軌跡繼續未完成的job。android
主要知識點以下:
一、MeasureSpc類說明
二、measure過程詳解(揭祕其細節);
三、root View被添加至窗口時,UI框架是如何設置其LayoutParams值得。
程序員
在講解measure過程前,咱們很是有必要理解MeasureSpc類的使用,不然理解起來也只能算是囫圇吞棗。數組
一、MeasureSpc類說明
1.1 SDK 說明以下
A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec
represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and架構
a mode.
即:
MeasureSpc類封裝了父View傳遞給子View的佈局(layout)要求。每一個MeasureSpc實例表明寬度或者高度app
(只能是其一)要求。 它有三種模式:框架
①、UNSPECIFIED(未指定),父元素部隊自元素施加任何束縛,子元素能夠獲得任意想要的大小;異步
②、EXACTLY(徹底),父元素決定自元素的確切大小,子元素將被限定在給定的邊界裏而忽略它自己大小;ide
③、AT_MOST(至多),子元素至多達到指定大小的值。
經常使用的三個函數:
static int getMode(int measureSpec) : 根據提供的測量值(格式)提取模式(上述三個模式之一)
static int getSize(int measureSpec) : 根據提供的測量值(格式)提取大小值(這個大小也就是咱們一般所說的大小)
static int makeMeasureSpec(int size,int mode) : 根據提供的大小值和模式建立一個測量值(格式)
以上摘取自: <<MeasureSpec介紹及使用詳解>>
1.2 MeasureSpc類源碼分析 其爲View.java類的內部類,路徑:\frameworks\base\core\java\android\view\View.java
- public class View implements ... {
- ...
- public static class MeasureSpec {
- private static final int MODE_SHIFT = 30;
-
- private static final int MODE_MASK = 0x3 << MODE_SHIFT;
-
-
- public static final int UNSPECIFIED = 0 << MODE_SHIFT;
-
- public static final int EXACTLY = 1 << MODE_SHIFT;
-
- public static final int AT_MOST = 2 << MODE_SHIFT;
-
-
- public static int makeMeasureSpec(int size, int mode) {
- return size + mode;
- }
-
- public static int getMode(int measureSpec) {
- return (measureSpec & MODE_MASK);
- }
-
- public static int getSize(int measureSpec) {
- return (measureSpec & ~MODE_MASK);
- }
-
- }
- ...
- }
MeasureSpec類的處理思路是:
①、右移運算,使int 類型的高兩位表示模式的實際值,其他30位表示其他30位表明長或寬的實際值----能夠是
WRAP_CONTENT、MATCH_PARENT或具體大小exactly size。
②、經過掩碼MODE_MASK進行與運算 「&」,取得模式(mode)以及長或寬(value)的實際值。
二、measure過程詳解
2.1 measure過程深刻分析
以前的一篇博文<< Android中View繪製流程以及invalidate()等相關方法分析>>,咱們從」二B程序員」的角度簡單 解了measure過程的調用過程。過了這麼多,咱們也該升級了,- - 。如今請開始從」普通程序員」角度去理解這個
過程。咱們重點查看measure過程當中地相關方法。
咱們說過,當UI框架開始繪製時,皆是從ViewRoot.java類開始繪製的。
ViewRoot類簡要說明: 任何顯示在設備中的窗口,例如:Activity、Dialog等,都包含一個ViewRoot實例,該
類主要用來與遠端 WindowManagerService交互以及控制(開始/銷燬)繪製。
Step 1、 開始UI繪製 , 具體繪製方法則是:
- 路徑:\frameworks\base\core\java\android\view\ViewRoot.java
- public final class ViewRoot extends Handler implements ViewParent,View.AttachInfo.Callbacks {
- ...
-
- View mView;
-
-
- private void performTraversals(){
- ...
-
- int childWidthMeasureSpec;
- int childHeightMeasureSpec;
-
-
-
- host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- ...
- }
- ...
- }
這兒,我並無說出childWidthMeasureSpec和childHeightMeasureSpec類的來由(爲了不額外地開銷,等到
第三部分時咱們在來攻克它,如今只須要記住其值MeasureSpec.makeMeasureSpec()構建的。
Step 2 、調用measure()方法去作一些前期準備
measure()方法原型定義在View.java類中,final修飾符修飾,其不能被重載:
- public class View implements ... {
- ...
-
-
-
-
-
-
-
-
-
-
- public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
-
- if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
- widthMeasureSpec != mOldWidthMeasureSpec ||
- heightMeasureSpec != mOldHeightMeasureSpec) {
-
-
-
- mPrivateFlags &= ~MEASURED_DIMENSION_SET;
-
-
-
- onMeasure(widthMeasureSpec, heightMeasureSpec);
-
-
-
- if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
- throw new IllegalStateException("onMeasure() did not set the"
- + " measured dimension by calling" + " setMeasuredDimension()");
- }
-
- mPrivateFlags |= LAYOUT_REQUIRED;
- }
-
- mOldWidthMeasureSpec = widthMeasureSpec;
- mOldHeightMeasureSpec = heightMeasureSpec;
- }
- ...
- }
參數widthMeasureSpec和heightMeasureSpec 由父View構建,表示父View給子View的測量要求。其值地構建
會在下面步驟中詳解。
measure()方法顯示判斷是否須要從新調用設置改View大小,即調用onMeasure()方法,而後操做兩個標識符:
①、重置MEASURED_DIMENSION_SET : onMeasure()方法中,須要添加該標識符,不然,會報異常;
②、添加LAYOUT_REQUIRED : 表示須要進行layout操做。
最後,保存當前的widthMeasureSpec和heightMeasureSpec值。
Step 3 、調用onMeasure()方法去真正設置View的長寬值,其默認實現爲:
-
-
-
-
-
-
-
-
-
-
-
-
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
- getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
- }
-
-
-
-
-
-
-
-
-
-
-
- public static int getDefaultSize(int size, int measureSpec) {
- int result = size;
- int specMode = MeasureSpec.getMode(measureSpec);
- int specSize = MeasureSpec.getSize(measureSpec);
-
-
- switch (specMode) {
- case MeasureSpec.UNSPECIFIED:
- result = size;
- break;
- case MeasureSpec.AT_MOST:
- case MeasureSpec.EXACTLY:
- result = specSize;
- break;
- }
- return result;
- }
-
- protected int getSuggestedMinimumWidth() {
- int suggestedMinWidth = mMinWidth;
-
- if (mBGDrawable != null) {
- final int bgMinWidth = mBGDrawable.getMinimumWidth();
- if (suggestedMinWidth < bgMinWidth) {
- suggestedMinWidth = bgMinWidth;
- }
- }
-
- return suggestedMinWidth;
- }
-
- protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
- mMeasuredWidth = measuredWidth;
- mMeasuredHeight = measuredHeight;
-
- mPrivateFlags |= MEASURED_DIMENSION_SET;
- }
主要功能就是根據該View屬性(android:minWidth和背景圖片大小)和父View對該子View的"測量要求",設置該 View的 mMeasuredWidth 和 mMeasuredHeight 值。
這兒只是通常的View類型地實現方法。通常來講,父View,也就是ViewGroup類型,都須要在重寫onMeasure() 方法,遍歷全部子View,設置每一個子View的大小。基本思想以下:遍歷全部子View,設置每一個子View的大小。僞
代碼錶示爲:
-
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
- super.onMeasure(widthMeasureSpec , heightMeasureSpec)
-
-
-
-
- for(int i = 0 ; i < getChildCount() ; i++){
- View child = getChildAt(i);
-
- child.onMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
- }
Step 二、Step 3 代碼也比較好理解,但問題是咱們示例代碼中widthMeasureSpec、heightMeasureSpec是如何
肯定的呢?父View是如何設定其值的?
要想回答這個問題,咱們看是去源代碼裏找找答案吧。在ViewGroup.java類中,爲咱們提供了三個方法,去設置
每個子View的大小,基本思想也如同咱們以前描述的思想:遍歷全部子View,設置每一個子View的大小。
主要有以下方法:
-
-
-
-
-
-
-
-
- protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
- final int size = mChildrenCount;
- final View[] children = mChildren;
- for (int i = 0; i < size; ++i) {
- final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
- measureChild(child, widthMeasureSpec, heightMeasureSpec);
- }
- }
- }
-
-
-
-
-
-
-
-
-
-
-
- protected void measureChild(View child, int parentWidthMeasureSpec,
- int parentHeightMeasureSpec) {
- final LayoutParams lp = child.getLayoutParams();
-
- final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
- mPaddingLeft + mPaddingRight, lp.width);
-
- final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
- mPaddingTop + mPaddingBottom, lp.height);
-
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
measureChildren()方法:遍歷全部子View,調用measureChild()方法去設置該子View的屬性值。
measureChild() 方法 : 獲取特定子View的widthMeasureSpec、heightMeasureSpec,調用measure()方法
設置子View的實際寬高值。
getChildMeasureSpec()就是獲取子View的widthMeasureSpec、heightMeasureSpec值。
-
-
-
-
-
-
-
-
-
-
-
-
- public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
- int specMode = MeasureSpec.getMode(spec);
- int specSize = MeasureSpec.getSize(spec);
-
- int size = Math.max(0, specSize - padding);
-
- int resultSize = 0;
- int resultMode = 0;
-
- switch (specMode) {
-
-
- case MeasureSpec.EXACTLY:
-
- if (childDimension >= 0) {
- resultSize = childDimension;
- resultMode = MeasureSpec.EXACTLY;
- }
-
- else if (childDimension == LayoutParams.MATCH_PARENT) {
-
- resultSize = size;
- resultMode = MeasureSpec.EXACTLY;
- }
-
- else if (childDimension == LayoutParams.WRAP_CONTENT) {
-
-
- resultSize = size;
- resultMode = MeasureSpec.AT_MOST;
- }
- break;
-
-
-
- case MeasureSpec.AT_MOST:
-
- if (childDimension >= 0) {
-
- resultSize = childDimension;
- resultMode = MeasureSpec.EXACTLY;
- }
-
- else if (childDimension == LayoutParams.MATCH_PARENT) {
-
-
- resultSize = size;
- resultMode = MeasureSpec.AT_MOST;
- }
-
- else if (childDimension == LayoutParams.WRAP_CONTENT) {
-
-
- resultSize = size;
- resultMode = MeasureSpec.AT_MOST;
- }
- break;
-
-
-
- case MeasureSpec.UNSPECIFIED:
-
- if (childDimension >= 0) {
-
- resultSize = childDimension;
- resultMode = MeasureSpec.EXACTLY;
- }
-
- else if (childDimension == LayoutParams.MATCH_PARENT) {
-
-
- resultSize = 0;
- resultMode = MeasureSpec.UNSPECIFIED;
- }
-
- else if (childDimension == LayoutParams.WRAP_CONTENT) {
-
-
- resultSize = 0;
- resultMode = MeasureSpec.UNSPECIFIED;
- }
- break;
- }
-
- return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
- }
爲了便於分析,我將上面的邏輯判斷語句使用列表項進行了說明.
getChildMeasureSpec()方法的主要功能以下:
根據父View的measureSpec值(widthMeasureSpec,heightMeasureSpec)值以及子View的子View內部
LayoutParams屬性值,共同決定子View的measureSpec值的大小。主要判斷條件主要爲MeasureSpec的mode
類型以及LayoutParams的寬高實際值(lp.width,lp.height),見於以上所貼代碼中的列表項: 一、 1.1 ; 1.2 ; 1.3 ;
二、2.1等。
例如,分析列表3:假設當父View爲MeasureSpec.UNSPECIFIED類型,即未定義時,只有當子View的width
或height指定時,其mode才爲MeasureSpec.EXACTLY,否者該View size爲 0 ,mode爲MeasureSpec.UNSPECIFIED時
,即處於未指定狀態。
由此能夠得出, 每一個View大小的設定都事由其父View以及該View共同決定的。但這只是一個指望的大小,每一個
View在測量時最終大小的設定是由setMeasuredDimension()最終決定的。所以,最終肯定一個View的「測量長寬「是
由如下幾個方面影響:
一、父View的MeasureSpec屬性;
二、子View的LayoutParams屬性 ;
三、setMeasuredDimension()或者其它相似設定 mMeasuredWidth 和 mMeasuredHeight 值的方法。
setMeasuredDimension()原型:
-
- protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
- mMeasuredWidth = measuredWidth;
- mMeasuredHeight = measuredHeight;
-
- mPrivateFlags |= MEASURED_DIMENSION_SET;
- }
將上面列表項轉換爲表格爲:

這張表格更能幫助咱們分析View的MeasureSpec的肯定條件關係。
爲了幫助你們理解,下面咱們分析某個窗口使用地xml佈局文件,咱們弄清楚該xml佈局文件中每一個View的
MeasureSpec值的組成。
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/llayout"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
-
- <TextView android:id="@+id/tv"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/hello" />
-
- </LinearLayout>
該佈局文件共有兩個View: ①、id爲llayout的LinearLayout佈局控件 ;
②、id爲tv的TextView控件。
假設LinearLayout的父View對應地widthSpec和heightSpec值皆爲MeasureSpec.EXACTLY類型(Activity窗口
的父View爲DecorView,具體緣由見第三部分說明)。
對LinearLayout而言比較簡單,因爲 android:layout_width="match_parent",所以其width對應地widthSpec
mode值爲MeasureSpec.EXACTLY , size由父視圖大小指定 ; 因爲android:layout_height = "match_parent",
因此其height對應地heightSpec mode值爲MeasureSpec.EXACTLY,size由父視圖大小指定 ;
對TextView而言 ,其父View爲LinearLayout的widthSpec和heightSpec值皆爲MeasureSpec.EXACTLY類型,
因爲android:layout_width="match_parent" , 所以其width對應地widthSpec mode值爲MeasureSpec.EXACTLY,
size由父視圖大小指定 ; 因爲android:layout_width="wrap_content" , 所以其height對應地widthSpec mode值爲
MeasureSpec.AT_MOST,size由父視圖大小指定 。
咱們繼續窺測下LinearLayout類是如何進行measure過程的:
- public class LinearLayout extends ViewGroup {
- ...
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
- if (mOrientation == VERTICAL) {
- measureVertical(widthMeasureSpec, heightMeasureSpec);
- } else {
- measureHorizontal(widthMeasureSpec, heightMeasureSpec);
- }
- }
-
- void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
- mTotalLength = 0;
- float totalWeight = 0;
- int maxWidth = 0;
- ...
- final int count = getVirtualChildCount();
-
- final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- ...
-
- for (int i = 0; i < count; ++i) {
- final View child = getVirtualChildAt(i);
- ...
- LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
-
- totalWeight += lp.weight;
-
- if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
- ...
- } else {
- int oldHeight = Integer.MIN_VALUE;
-
- if (lp.height == 0 && lp.weight > 0) {
- oldHeight = 0;
- lp.height = LayoutParams.WRAP_CONTENT;
- }
-
-
-
-
-
- measureChildBeforeLayout(
- child, i, widthMeasureSpec, 0, heightMeasureSpec,
- totalWeight == 0 ? mTotalLength : 0);
-
-
-
-
-
- final int childHeight = child.getMeasuredHeight();
- final int totalLength = mTotalLength;
- mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
- lp.bottomMargin + getNextLocationOffset(child));
- ...
- }
- final int margin = lp.leftMargin + lp.rightMargin;
- final int measuredWidth = child.getMeasuredWidth() + margin;
- maxWidth = Math.max(maxWidth, measuredWidth);
- ...
- }
-
- ...
- }
- void measureChildBeforeLayout(View child, int childIndex,
- int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
- int totalHeight) {
-
- measureChildWithMargins(child, widthMeasureSpec, totalWidth,
- heightMeasureSpec, totalHeight);
- }
- ...
繼續看看measureChildWithMargins()方法,該方法定義在ViewGroup.java內,基本流程同於measureChild()方法,但添加了對子View Margin的處理,即:android:margin屬性或者android:marginLeft等屬性的處理。
measureChildWithMargins@ViewGroup.java
-
-
-
-
-
-
-
-
-
- protected void measureChildWithMargins(View child,
- int parentWidthMeasureSpec, int widthUsed,
- int parentHeightMeasureSpec, int heightUsed) {
- final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
-
-
- final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
- mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
- + widthUsed, lp.width);
- final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
- mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
- + heightUsed, lp.height);
-
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
measure()過程時,LinearLayout類作了以下事情 :
一、遍歷每一個子View,對其調用measure()方法;
二、子View measure()完成後,須要取得該子View地寬高實際值,繼而作處理(例如:LinearLayout屬性爲
android:widht="wrap_content"時,LinearLayout的實際width值則是每一個子View的width值的累加值)。
2.2 WRAP_CONTENT、MATCH_PARENT以及measure動機揭祕
子View地寬高實際值 ,即child.getMeasuredWidth()值得返回最終會是一個肯定值? 難道WRAP_CONTENT(
其值爲-2) 、MATCH_PARENT(值爲-1)或者說一個具體值(an exactly size > 0)。前面咱們說過,View最終「測量」值的
肯定是有三個部分組成地:
①、父View的MeasureSpec屬性;
②、子View的LayoutParams屬性 ;
③、setMeasuredDimension()或者其它相似設定 mMeasuredWidth 和 mMeasuredHeight 值的方法。
所以,一個View必須以某種合適地方法肯定它地最終大小。例如,以下自定義View:
-
- public Class MyView extends View {
-
-
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
-
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- int heightMode = MeasureSpec.getMode(heightMeasureSpec);
-
- int width = 0 ;
- int height = 0 ;
-
- if(widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED)
- throw new RuntimeException("widthMode or heightMode cannot be UNSPECIFIED");
-
-
- if(widthMode == MeasureSpec.EXACTLY){
- width = 100 ;
- }
-
- else if(widthMode == MeasureSpec.AT_MOST )
- width = 50 ;
-
-
- if(heightMode == MeasureSpec.EXACTLY){
- height = 100 ;
- }
-
- else if(heightMode == MeasureSpec.AT_MOST )
- height = 50 ;
-
- setMeasuredDimension(width , height) ;
- }
- }
該自定義View重寫了onMeasure()方法,根據傳遞過來的widthMeasureSpec和heightMeasureSpec簡單設置了
該View的mMeasuredWidth 和 mMeasuredHeight值。
對於TextView而言,若是它地mode不是Exactly類型 , 它會根據一些屬性,例如:android:textStyle
、android:textSizeandroid:typeface等去肯定TextView類地須要佔用地長和寬。
所以,若是你地自定義View必須手動對不一樣mode作出處理。不然,則是mode對你而言是無效的。
Android框架中提供地一系列View/ViewGroup都須要去進行這個measure()過程地 ,由於在layout()過程當中,父
View須要調用getMeasuredWidth()或getMeasuredHeight()去爲每一個子View設置他們地佈局座標,只有肯定佈局
座標後,才能真正地將該View 繪製(draw)出來,不然該View的layout大小爲0,得不到指望效果。咱們繼續看看
LinearLayout的layout佈局過程:
- public class LinearLayout extends ViewGroup {
- ...
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
-
- if (mOrientation == VERTICAL) {
- layoutVertical();
- } else {
- layoutHorizontal();
- }
- }
-
- void layoutVertical() {
- ...
- final int count = getVirtualChildCount();
- ...
- for (int i = 0; i < count; i++) {
- final View child = getVirtualChildAt(i);
- if (child == null) {
- childTop += measureNullChild(i);
- } else if (child.getVisibility() != GONE) {
-
- final int childWidth = child.getMeasuredWidth();
- final int childHeight = child.getMeasuredHeight();
-
- ...
-
- setChildFrame(child, childLeft, childTop + getLocationOffset(child),
- childWidth, childHeight);
- childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
-
- i += getChildrenSkipCount(child, i);
- }
- }
- }
-
- private void setChildFrame(View child, int left, int top, int width, int height) {
-
- child.layout(left, top, left + width, top + height);
- }
- ...
- }
對一個View進行measure操做地主要目的就是爲了肯定該View地佈局大小,見上面所示代碼。但measure操做
一般是耗時的,所以對自定義ViewGroup而言,咱們能夠自由控制measure、layout過程,若是咱們知道如何layout
一個View,咱們能夠跳過該ViewGroup地measure操做(onMeasure()方法中measure全部子View地),直接去layout
在前面一篇博客<<Android中滑屏初探 ---- scrollTo 以及 scrollBy方法使用說明>>中,咱們自定義了一個 ViewGroup, 而且重寫了onMeasure()和onLayout()方法去分別操做每一個View。就該ViewGroup而言,咱們只須要
重寫onLayout()操做便可,由於咱們知道如何layout每一個子View。以下代碼所示:
-
- public class MultiViewGroup extends ViewGroup {
- private void init() {
-
- LinearLayout oneLL = new LinearLayout(mContext);
- oneLL.setBackgroundColor(Color.RED);
- addView(oneLL);
- ...
- }
- @Override
-
-
-
-
-
-
-
-
-
-
-
-
- }
-
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
-
- Log.i(TAG, "--- start onLayout --");
- int startLeft = 0;
- int startTop = 10;
- int childCount = getChildCount();
- Log.i(TAG, "--- onLayout childCount is -->" + childCount);
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- child.layout(startLeft, startTop,
- startLeft + MultiScreenActivity.screenWidth,
- startTop + MultiScreenActivity.scrrenHeight);
- startLeft = startLeft + MultiScreenActivity.screenWidth ;
-
- }
- }
- }
更多關於自定義ViewGroup無須重寫measure動做的,能夠參考 Android API :
三、root View被添加至窗口時,UI框架是如何設置其LayoutParams值
老子道德經有言:「道生一,一輩子二,二生三,三生萬物。」 UI繪製也就是個遞歸過程。理解其基本架構後,
也就「掌握了一箇中心點」了。在第一節中,咱們沒有說明開始UI繪製時 ,沒有說明mView.measure()參數地由來,
參數也就是咱們本節須要弄懂的「道」 --- root View的 widthMeasureSpec和heightMeasureSpec 是如何肯定的。
對於以下佈局文件: main.xml
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="@string/hello"
- />
- </LinearLayout>
當使用LayoutInflater類解析成View時 ,LinearLayout對象的LayoutParams參數爲null 。具體緣由請參考上篇博文
任何一個View被添加至窗口時,都須要利用WindowManager類去操做。例如,以下代碼:
-
- public void showView()
- {
-
- LayoutInflater layoutInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- View rootView = layoutInflater.inflate(R.layout.main, null);
-
- WindowManager windowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
-
- WindowManager.LayoutParams winparams = WindowManager.LayoutParams();
-
- winparams.x = 0;
- winparams.y = 0;
-
-
- winparams.width = WindowManager.LayoutParams.WRAP_CONTENT;;
- winparams.height = WindowManager.LayoutParams.WRAP_CONTENT;;
-
- windowManager.addView(rootView, winparams);
- }
關於WindowManager的使用請看以下博客 :
關於WindowManager.LayoutParams類說明請看以下博客:
下面,咱們從得到WindowManager對象引用開始,一步步觀察addView()作了一些什麼事情。
Step 1 、得到WindowManager對象服務 ,具體實現類在ContextImpl.java內中
路徑: /frameworks/base/core/java/android/app/ContextImpl.java
- @Override
- public Object getSystemService(String name) {
- if (WINDOW_SERVICE.equals(name)) {
- return WindowManagerImpl.getDefault();
- }
- ...
- }
WindowManager是個接口,具體返回對象則是WindowManagerImpl的單例對象。
Step 2 、 得到WindowManagerImpl的單例對象,以及部分源碼分析
路徑: /frameworks/base/core/java/android/view/WindowManagerImpl.java
- public class WindowManagerImpl implements WindowManager{
-
- public static WindowManagerImpl getDefault()
- {
- return mWindowManager;
- }
-
- public void addView(View view, ViewGroup.LayoutParams params)
- {
- addView(view, params, false);
- }
-
- private void addView(View view, ViewGroup.LayoutParams params, boolean nest)
- { ...
- final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
-
- ViewRoot root;
- View panelParentView = null;
-
- synchronized (this) {
-
- ...
-
-
- root = new ViewRoot(view.getContext());
- root.mAddNesting = 1;
-
- view.setLayoutParams(wparams);
- ...
-
-
- mViews[index] = view;
- mRoots[index] = root;
- mParams[index] = wparams;
- }
-
-
- root.setView(view, wparams, panelParentView);
- }
- ...
-
- private View[] mViews;
- private ViewRoot[] mRoots;
- private WindowManager.LayoutParams[] mParams;
-
-
- private static WindowManagerImpl mWindowManager = new WindowManagerImpl();
- }
WindowManagerImpl類的三個數組集合保存了每一個窗口相關屬性,這樣咱們能夠經過這些屬性去操做特定的
窗口(例如,能夠根據View去更新/銷燬該窗口)。當參數檢查成功時,構建一個ViewRoot對象,而且設置設置root
View 的LayoutParams爲wparams,即WindowManager.LayoutParams類型。最後調用root.setView()方法去通知
系統須要建立該窗口。咱們接下來往下看看ViewRoot類相關操做。
Step 三、
- public final class ViewRoot extends Handler implements ViewParent,View.AttachInfo.Callbacks {
-
- View mView;
- final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
-
- ...
-
-
-
- public void setView(View view, WindowManager.LayoutParams attrs,
- View panelParentView) {
- synchronized (this) {
- if (mView == null) {
- mView = view;
- mWindowAttributes.copyFrom(attrs);
- attrs = mWindowAttributes;
- ...
-
- mAdded = true;
- int res;
-
-
-
-
- requestLayout();
- mInputChannel = new InputChannel();
- try {
-
- res = sWindowSession.add(mWindow, mWindowAttributes,
- getHostVisibility(), mAttachInfo.mContentInsets,
- mInputChannel);
- }
- ...
- view.assignParent(this);
- ...
- }
- }
- }
- }
說明:ViewRoot類繼承了Handler,實現了ViewParent接口
setView()方法地主要功能以下:
一、保存相關屬性值,例如:mView、mWindowAttributes等;
二、調用requestLayout()方法請求UI繪製,因爲ViewRoot是個Handler對象,異步請求;
三、通知WindowManagerService添加一個窗口;
四、註冊一個事件監聽管道,用來監聽:按鍵(KeyEvent)和觸摸(MotionEvent)事件。
咱們這兒重點關注 requestLayout()方法請求UI繪製地流程。
Step 四、異步調用請求UI繪製
-
-
-
- public void requestLayout() {
- checkThread();
- mLayoutRequested = true;
- scheduleTraversals();
- }
-
- public void scheduleTraversals() {
- if (!mTraversalScheduled) {
- mTraversalScheduled = true;
- sendEmptyMessage(DO_TRAVERSAL);
- }
- }
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case DO_TRAVERSAL:
- performTraversals();
- break;
- }
- }
因爲performTraversals()方法比較複雜,咱們側重於第一次設置root View的widhtSpecSize以及
heightSpecSize值。
- private void performTraversals() {
-
- final View host = mView;
-
- mTraversalScheduled = false;
- boolean surfaceChanged = false;
- WindowManager.LayoutParams lp = mWindowAttributes;
-
- int desiredWindowWidth;
- int desiredWindowHeight;
- int childWidthMeasureSpec;
- int childHeightMeasureSpec;
-
- final View.AttachInfo attachInfo = mAttachInfo;
-
- final int viewVisibility = getHostVisibility();
- boolean viewVisibilityChanged = mViewVisibility != viewVisibility
- || mNewSurfaceNeeded;
-
- float appScale = mAttachInfo.mApplicationScale;
-
- WindowManager.LayoutParams params = null;
- if (mWindowAttributesChanged) {
- mWindowAttributesChanged = false;
- surfaceChanged = true;
- params = lp;
- }
- Rect frame = mWinFrame;
- if (mFirst) {
- fullRedrawNeeded = true;
- mLayoutRequested = true;
-
- DisplayMetrics packageMetrics =
- mView.getContext().getResources().getDisplayMetrics();
-
- desiredWindowWidth = packageMetrics.widthPixels;
- desiredWindowHeight = packageMetrics.heightPixels;
- ...
- } else {
- desiredWindowWidth = frame.width();
- desiredWindowHeight = frame.height();
- ...
- }
- ...
- boolean insetsChanged = false;
-
- if (mLayoutRequested) {
- ...
- childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
- childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
-
- host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- }
- ...
- final boolean didLayout = mLayoutRequested;
-
- boolean triggerGlobalLayoutListener = didLayout
- || attachInfo.mRecomputeGlobalAttributes;
- if (didLayout) {
- ...
- host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
- ...
- }
- ...
- if (!cancelDraw && !newSurface) {
- mFullRedrawNeeded = false;
- draw(fullRedrawNeeded);
- ...
- }
-
-
-
-
-
- private int getRootMeasureSpec(int windowSize, int rootDimension) {
- int measureSpec;
- switch (rootDimension) {
- case ViewGroup.LayoutParams.MATCH_PARENT:
-
- measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
- break;
- case ViewGroup.LayoutParams.WRAP_CONTENT:
-
- measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
- break;
- default:
-
- measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
- break;
- }
- return measureSpec;
- }
調用root View的measure()方法時,其參數是由getRootMeasureSpec()設置的,基本思路同咱們前面描述的
差很少。貼出來的代碼只是簡簡單單列出了measure 、layout 、 draw 過程的調用點,裏面有不少邏輯處理,
閱讀起來比較費勁,我也只能算是個囫圇吞棗水平。你們有興趣地能夠看看源碼,加深理解。