View的繪制-layout流程詳解

目錄

作用

根據 measure 測量出來的寬高,確定所有 View 的位置。

具體分析

View 本身的位置是通過它的四個點來控制的:

以下涉及到源碼的部分都是版本27的,為方便理解觀看,代碼有所刪減。

layout 的流程

先通過 measure 測量出 ViewGroup 寬高,ViewGroup 再通過 layout 方法根據自身寬高來確定自身位置。當 ViewGroup 的位置被確定后,就開始在 onLayout 方法中調用子元素的 layout 方法確定子元素的位置。子元素如果是 ViewGroup 的子類,又開始執行 onLayout,如此循環往復,直到所有子元素的位置都被確定,整個 View 樹的 layout 過程就執行完了。

在上一節 《View的繪制-measure流程詳解》中說過,View 的繪制流程是從 ViewRootViewImpl 中的 performMeasure()performLayoutperformDraw 開始的。在執行完 performMeasure() 后,開始執行 performLayout 方法:(以下源碼有所刪減)

//ViewRootViewImpl 類
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {final View host = mView;/*代碼省略*///開始執行 View 的 layout 方法host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());/*代碼省略*/
}
復制代碼

如此,就執行了 View 的 layout 方法:

//View 類
public void layout(int l, int t, int r, int b) {/*代碼省略*/int oldL = mLeft;int oldT = mTop;int oldB = mBottom;int oldR = mRight;//確定 View 的四個點后,調用 setOpticalFrame/setFrame 方法來控制 View 位置。//方法 1--->setOpticalFrame//方法 2--->setFrameboolean changed = isLayoutModeOptical(mParent) ?setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {//執行 onLayout 方法onLayout(changed, l, t, r, b);/*代碼省略*/}/*代碼省略*/
}//方法 1---->setOpticalFrame 
//View 類
private boolean setOpticalFrame(int left, int top, int right, int bottom) {Insets parentInsets = mParent instanceof View ?((View) mParent).getOpticalInsets() : Insets.NONE;Insets childInsets = getOpticalInsets();//內部最終也是調用 setFrame  方法return setFrame(left   + parentInsets.left - childInsets.left,top    + parentInsets.top  - childInsets.top,right  + parentInsets.left + childInsets.right,bottom + parentInsets.top  + childInsets.bottom);
}//方法 2--->setFrame
//View 類
protected boolean setFrame(int left, int top, int right, int bottom) {boolean changed = false;if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {changed = true;/*代碼省略*/int oldWidth = mRight - mLeft;int oldHeight = mBottom - mTop;int newWidth = right - left;int newHeight = bottom - top;//控件的大小和位置有沒有改變boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);// Invalidate our old positioninvalidate(sizeChanged);//對 View 的四個點賦值mLeft = left;mTop = top;mRight = right;mBottom = bottom;mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);mPrivateFlags |= PFLAG_HAS_BOUNDS;if (sizeChanged) {//這里 sizeChange 方法內部調用了 onSizeChanged 方法。//所以當控件的大小和位置改變的時候會回調 onSizeChanged 方法//方法 3--->sizeChangesizeChange(newWidth, newHeight, oldWidth, oldHeight);}/*代碼省略*/}return changed;
}//方法 3--->sizeChange
// View 類
private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {//執行 onSizeChanged 方法onSizeChanged (newWidth, newHeight, oldWidth, oldHeight);if (mOverlay != null) {mOverlay.getOverlayView().setRight(newWidth);mOverlay.getOverlayView().setBottom(newHeight);}rebuildOutline();
}
復制代碼

接下來我們再看 onLayout方法,在 View 中找到 onLayout 方法,會發現這是一個空實現的方法,里面什么也沒有執行,那么我們就在 ViewGroup 中找 onLayout 的實現,發現只是一個抽象方法。

//View 類
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
復制代碼
//ViewGroup 類
@Override
protected abstract void onLayout(boolean changed,int l, int t, int r, int b);
復制代碼

在 View 類中 onLayout 是一個空實現不難理解,因為如果一個控件繼承了 View ,它是沒有子元素的,不需要確定子元素的位置,只需要確定自己的位置就夠了。

在 ViewGroup 中是一個抽象方法,意思也很明顯了,在控件繼承自 ViewGroup 的時候,我們必須重寫 onLayout 方法。因為如LinearLayoutRelativeLayout,他們的布局特性都是不一樣的,需要各自根據自己的特性來進行制定確定子元素位置的規則。

下面以 LinearLayout 為例,分析 onLayout 里面的邏輯。

LinearLayout 的 onLayout 邏輯

//LinearLayout 類
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {if (mOrientation == VERTICAL) {//垂直排列layoutVertical(l, t, r, b);} else {//水平排列layoutHorizontal(l, t, r, b);}
}
復制代碼

layoutVerticallayoutHorizontal執行流程類似,就分析layoutVertical

//LinearLayout 類
void layoutVertical(int left, int top, int right, int bottom) {final int paddingLeft = mPaddingLeft;int childTop;int childLeft;// Where right end of child should gofinal int width = right - left;int childRight = width - mPaddingRight;// Space available for childint childSpace = width - paddingLeft - mPaddingRight;final int count = getVirtualChildCount();/*省略代碼*/for (int i = 0; i < count; i++) {//遍歷子 Viewfinal 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();final LinearLayout.LayoutParams lp =(LinearLayout.LayoutParams) child.getLayoutParams();if (hasDividerBeforeChildAt(i)) {childTop += mDividerHeight;}//加上 子元素的 topMargin 屬性值childTop += lp.topMargin;//設置子元素的 位置//方法 1 ----->setChildFramesetChildFrame(child, childLeft, childTop + getLocationOffset(child),childWidth, childHeight);/*加上子元素的高度、bottomMargin、偏移量,就是下一個子元素的 初始 top。如此,子元素就從上到下排列,符合我們所知的 LinearLayout 的特性*/childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);i += getChildrenSkipCount(child, i);}}
}//方法 1 ----->setChildFrame
//LinearLayout 類
private void setChildFrame(View child, int left, int top, int width, int height) {//最終又調用了子元素的 layout 方法.child.layout(left, top, left + width, top + height);
}
復制代碼

可以看到,在遍歷子元素后,又調用了子元素的 layout 方法。子元素如果是繼承 ViewGroup,還是會調用到子元素的 onLayout 方法,遍歷自己的子元素,調用自己子元素的 layout 方法,如此循環遞歸,就完成了整個 View 樹的 layout 流程。

getWidth、getMeasureWidth分析

getWidth 獲取的值和 getMeasureWidth 獲取的值有什么不同嗎? 首先看 getWidth 源碼:

//View 類
public final int getWidth() {return mRight - mLeft;
}
復制代碼

然后再看 mRight 和 mLeft 賦值(在我們分析 layout 的時就有展現):

以下為超精簡代碼,哈哈:

//View 類
public void layout(int l, int t, int r, int b) {//setOpticalFrame 最終也是調用了 setFrame 方法boolean changed = isLayoutModeOptical(mParent) ?setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
}//View 類
protected boolean setFrame(int left, int top, int right, int bottom) {//對四個值進行賦值mLeft = left;mTop = top;mRight = right;mBottom = bottom;
}
復制代碼

然后我們再回顧調用 layout 的方法:

//LinearLayout 類
void layoutVertical(int left, int top, int right, int bottom) {for (int i = 0; i < count; i++) {final int childWidth = child.getMeasuredWidth();final int childHeight = child.getMeasuredHeight();//將獲取的 childWidth 和 childHeight 傳入進去setChildFrame(child, childLeft, childTop + getLocationOffset(child),childWidth, childHeight);}
}
//LinearLayout 類
private void setChildFrame(View child, int left, int top, int width, int height) {//子元素 layout 的方法中傳入的 right  = left + width//子元素 layout 的方法中傳入的 bottom  = left + heightchild.layout(left, top, left + width, top + height);
}
復制代碼

這下就一目了然了,在 getWidth() 方法中 mRight - mLeft 其實就是等于 childWidth,也就是 getWidth() = getMeasureWidth()

那么,他們兩個有不相等的時候嗎?有的:

//自定義 View 
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {//手動改變傳入的值 super.onLayout(changed, left, top, right+100, bottom+100);
}
復制代碼

如果我們在自定義 View 中重寫 onLayout 方法,并手動改變傳入的值, getWidth()getMeasureWidth()的值自然就不一樣了。不過這樣做好像沒什么意義?

還有一種情況就是在某些情況下,View 需要多次 measure 才能確定自己的測量寬/高,在前幾次測量的時候,其得出的測量寬/高 ( getMeasureWidth()/ getMeasureHeight()) 和最終寬/高 ( getWidth()/ getHeight()) 可能不一致,但是最終他們的值還是相等的。(這段話摘自剛哥的《Android 開發藝術探索》)

getHeight 和 getMeasuredHeight 過程類似,這里就不分析了。

所以我們在日常開發中,我們可以認為兩者就是相等的。

另:可能還是會有疑惑,這里只分析了 LinearLayout,那么其他布局也適用這個結論么?

FrameLayout 類

//FrameLayout 類
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {final int count = getChildCount();for (int i = 0; i < count; i++) {final View child = getChildAt(i);if (child.getVisibility() != GONE) {final int width = child.getMeasuredWidth();final int height = child.getMeasuredHeight();//從這里看出 FrameLayout 也是適用這個結論的child.layout(childLeft, childTop, childLeft + width, childTop + height);}}
}
復制代碼

RelativeLayout 類

//RelativeLayout 類
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {final int count = getChildCount();for (int i = 0; i < count; i++) {View child = getChildAt(i);if (child.getVisibility() != GONE) {RelativeLayout.LayoutParams st =(RelativeLayout.LayoutParams) child.getLayoutParams();//Relativelayout 這里傳入的是 LayoutParams 中的屬性//st.mRight 和 st.mBottom 賦值很復雜,不過他們也是適用這個結論的,具體可以查看源碼分析child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);}}
}
復制代碼

其他我們日常開發使用的布局也是適用于這個結論的。

總結

參考文獻

《Android開發藝術探索》第四章-View的工作原理

自定義View Layout過程 - 最易懂的自定義View原理系列(3)

Android開發之getMeasuredWidth和getWidth區別從源碼分析

轉載于:https://juejin.im/post/5cc170d7f265da035d0c7fed

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/251851.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/251851.shtml
英文地址,請注明出處:http://en.pswp.cn/news/251851.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

1-1、作用域深入和面向對象

課時1&#xff1a;預解釋 JS中的數據類型 number、string、 boolean、null、undefined JS中引用數據類型 object: {}、[]、/^$/、Date Function var num12; var obj{name:白鳥齊鳴,age:10}; function fn(){ console.log(勿忘初心方得始終&#xff01;) }console.log(fn);//把整…

茶杯頭開槍ahk代碼

;說明這個工具是為了茶杯頭寫的,F1表示換槍攻擊,F3表示不換槍攻擊,F2表示停止攻擊. $F1::loop{ GetKeyState, state, F2, Pif state D{break } Send, {l down}Send, {l up}sleep,10Send,{m down}Send,{m up} }return $F3::loop{ GetKeyState, state, F2, Pif state D{break }…

Vim使用技巧:撤銷與恢復撤銷

在使用VIM的時候&#xff0c;難免會有輸錯的情況&#xff0c;這個時候我們應該如何撤銷&#xff0c;然后回到輸錯之前的狀態呢&#xff1f;答案&#xff1a;使用u&#xff08;小寫&#xff0c;且在命令模式下&#xff09;命令。 但如果有時我們一不小心在命令模式下輸入了u&…

PaddlePaddle開源平臺的應用

最近接觸了百度的開源深度學習平臺PaddlePaddle&#xff0c;想把使用的過程記錄下來。 作用&#xff1a;按照這篇文章&#xff0c;能夠實現對圖像的訓練和預測。我們準備了四種顏色的海洋球數據&#xff0c;然后給不同顏色的海洋球分類為0123四種。 一、安裝paddlepaddle 1.系統…

Hyperledger Fabric區塊鏈工具configtxgen配置configtx.yaml

configtx.yaml是Hyperledger Fabric區塊鏈網絡運維工具configtxgen用于生成通道創世塊或通道交易的配置文件&#xff0c;configtx.yaml的內容直接決定了所生成的創世區塊的內容。本文將給出configtx.yaml的詳細中文說明。 如果需要快速掌握Fabric區塊鏈的鏈碼與應用開發&#x…

js閉包??

<script>var name "The Window";var object {name : "My Object",getNameFunc : function(){console.log("11111");console.log(this); //this object //調用該匿名函數的是對象return function(){console.log("22222");co…

JavaScript----BOM(瀏覽器對象模型)

BOM 瀏覽器對象模型 BOM 的全稱為 Browser Object Model,被譯為瀏覽器對象模型。BOM提供了獨立于 HTML 頁面內容&#xff0c;而與瀏覽器相關的一系列對象。主要被用于管理瀏覽器窗口及與瀏覽器窗口之間通信等功能。 1、Window 對象 window對象是BOM中最頂層對象&#xff1b;表示…

JWT協議學習筆記

2019獨角獸企業重金招聘Python工程師標準>>> 官方 https://jwt.io 英文原版 https://www.ietf.org/rfc/rfc7519.txt 或 https://tools.ietf.org/html/rfc7519 中文翻譯 https://www.jianshu.com/p/10f5161dd9df 1. 概述 JSON Web Token&#xff08;JWT&#xff09;是…

DOM操作2

一、API和WebAPI API就是接口&#xff0c;就是通道&#xff0c;負責一個程序和其他軟件的溝通&#xff0c;本質是預先定義的函數。Web API是網絡應用程序接口。包含了廣泛的功能&#xff0c;網絡應用通過API接口&#xff0c;可以實現存儲服務、消息服務、計算服務等能力&#x…

浮動布局demo

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>浮動布局</title><style type"text/css">*{margin: 0;padding: 0;}header{height: 150px;background: yellow;}nav{height: 30px;background: green;…

UI行業發展預測 系列規劃的調整

我又雙叒叕拖更了&#xff0c;上一篇還是1月22號更新的&#xff0c;這都3月9號了……前面幾期把職業規劃、能力分析、幾個分析用的設計理論都寫完了&#xff0c;當然實際工作中用到的方法論不止上面這些&#xff0c;后續會接著學習&#xff1b;如果你的目標是一線團隊&#xff…

出現Press ENTER or type command to continue的原因

cd 然后 vim .vimrc 寫入 set nu 保存 退出轉載于:https://www.cnblogs.com/520qtf/p/8968441.html

基于Flask實現后臺權限管理系統 - 導言

網上有這樣一個段子&#xff0c;在評論語言好壞的時候&#xff0c;都會有人評論說PHP是世界上最好的語言&#xff0c;人生苦短我用Python&#xff0c;這里姑且不去評論語言的好壞&#xff0c;每一個語言存在都有它的價值&#xff0c;譬如C語言適合底層開發&#xff0c;整個Linu…

5-1 unittest框架使用

unittest是python的一個單元測試框架&#xff0c;內置的&#xff0c;不需要pip install 什么什么的。直接在py文件里面調用 import unittest。 他這個框架是怎么回事呢&#xff0c;他可以對數據初始化&#xff0c;然后執行測試&#xff08;里面有斷言功能就是判斷返回是否正確…

bzoj 4573: [Zjoi2016]大森林

Description 小Y家里有一個大森林&#xff0c;里面有n棵樹&#xff0c;編號從1到n。一開始這些樹都只是樹苗&#xff0c;只有一個節點&#xff0c;標號為1。這些樹 都有一個特殊的節點&#xff0c;我們稱之為生長節點&#xff0c;這些節點有生長出子節點的能力。小Y掌握了一種魔…

Unity3D在C#編程中的一些命名空間的引用及說明

System包含用于定義常用值和引用數據類型、事件和事件處理程序、接口、屬性和處理異常的基礎類和基類。其他類提供支持下列操作的服務&#xff1a;數據類型轉換&#xff0c;方法參數操作&#xff0c;數學計算&#xff0c;遠程和本地程序調用&#xff0c;應用程序環境管理以及對…

docker入門簡介

簡介docker(容器技術)是實現虛擬化技術的一種方案,通過利用linux中命名空間,控制組和聯合文件系統這個三個主要技術,來實現應用程序空間的隔離.通過對應用程序運行環境的封裝來生成鏡像并部署來實現跨平臺,一定程度上加快了服務交付的整體流程.這篇文章主要介紹docker的一些基本…

Highcharts 配置選項詳細說明

http://www.runoob.com/highcharts/highcharts-setting-detail.html 轉載于:https://www.cnblogs.com/mengfangui/p/8969121.html

linux下的啟停腳本

linux下的根據項目名稱&#xff0c;進行進程的啟停腳本 #!/bin/bashJAVA/usr/bin/java APP_HOME/opt/program/qa/wechat APP_NAMEprogramname.jar APP_PARAM"--spring.config.location${APP_HOME}/application.properties --logging.path${APP_HOME}"case $1 in star…

python 網頁爬取數據生成文字云圖

1. 需要的三個包&#xff1a; from wordcloud import WordCloud #詞云庫 import matplotlib.pyplot as plt #數學繪圖庫 import jieba; 2. 定義變量&#xff08;將對于的變量到一個全局的文件中&#xff09;&#xff1a; import re; pdurl_firsthttps://movie.do…