前段開始學習View的工作原理,前兩篇部落格的草稿都已經寫好了,本想一鼓作氣寫完所有的相關文章,然後經曆了一段連續加班,結果今天準備繼續寫文章時,把之前寫好的東西都忘記了,又重新梳理了一遍,是以說那怕就是已經掌握的知識,也要記得溫故而知新。
言歸正傳,之前我們讨論過了measure過程,measure過程完成之後,我們就可以通過 <code>getMeasuredWidth</code>或<code>getMeasuredHeight</code>來得到View的寬高尺寸了。而知道了寬高尺寸之後,剩下的就是布局(layout)過程了。說直白點,怎麼把具一個寬高已定的View擺放在螢幕上。
我們先來看一個demo。
具體的實作效果,是這個樣子。

我們暫且不讨論代碼當中的實作原理之類的,僅僅就想想一種場景,給你一些尺寸固定但不同的盒子,擺放在一個大房間的地面上,我們應該怎麼擺放呢。
之是以要根據這個demo來設想這種場景,是因為我覺的,不管代碼怎麼寫,但是基本的道理都不複雜,正所謂大道至簡,(畢竟我們不是做那些高大上的算法的)。自己根據場景去設想怎麼擺放,同樣的也有助于了解代碼。
假設我們按照 <code>LinearLayout</code>豎直方式的規則來擺放,那麼開始擺放第一個盒子肯定從房間的左上角開始計算位置,根據父容器padding,本身的margin等來決定具體擺放的坐标。然後再在豎直方向上 向前推進擺放第二個盒子。
我并不知道上面那段文字有沒有能清楚的表達出我的意思, 但是這是我前段時間關于這個layout的思考過程。思考過程本身就很難表達,但是我想這樣思考是有助于了解這個過程的。
和measure過程一樣,layout過程也是一個遞歸過程,并且ViewGroup類本身不 應該具體實作<code>onLayout</code>,具體實作過程應該放在<code>Framelayout</code>,<code>LinearLayout</code>,<code>RelativieLayout</code>等容器類中。
當layout過程完成之後,我們就可以得到View的左上角和右下角的坐标了。(就是left,top,right,bottom)。值得注意的是這個坐标是相對坐标,就是相對于View父容器的坐标。是以通過遞歸來計算是最自然最友善的方式。
這幾天看了關于程式設計語言曆史方面的文章, 在早期的程式設計語言中,是沒有遞歸這個概念的,是後來有人想出了這個概念,才逐漸的在各種程式設計語言中實作。是以說現在我們來看稀奇平常很自然的概念,但是當初第一個想出這個概念的人,那就是天才了。
下面我們就從源碼角度來進行分析。
<code>performTraversals</code>調用了<code>PerformLayout</code>方法。
<code>host</code>其實就是<code>DecorView</code>了,在執行<code>layout</code>時,注意傳遞的頭兩個參數都是0,這說明 整個layout過程是從螢幕坐标系的 左上角開始執行的。
下一步還是走到了<code>View#Layout()</code>, <code>View#Layout</code>方法如下:
<code>setFrame()</code>方法的作用也不複雜。
通過分析以上代碼可以知道,在<code>layout()</code>方法當中,主要做的就是将left,top,right,bottom方法儲存起來,而View自身怎麼被布局(其實我覺的用擺放這個詞更合适)。則是父容器需要完成的工作。
因為總所周知的原因(其實就是各個容器類ViewGroup布局子元素的方式差異很大)。ViewGroup并沒有實作具體的<code>onLayout</code>方法,各個具體的ViewGroup,比如<code>LinearLayout</code>,<code>RelativeLayout</code>,<code>FrameLayout</code>等,它們都有它們具體的 <code>onLayout</code>實作方式。
我們以<code>LinearLayout</code>為例來進行分析。
和measure過程是一樣的套路,也是分為兩種不同的情況,我們就以 豎直方向為例。(源碼比較多,并且實際也要考慮使用<code>layout_weight</code>的情況,不過我們僅僅隻抽取一部分分析基本原理)。
在該方法中,通過調用<code>setChildFrame</code>來為子元素指定相應的位置。
而<code>setChildFrame</code>隻是很簡單的一句話。
而<code>child.layout</code>其實不就是繼續調用 <code>onLayout</code>之類的嘛。是以這樣就形成了完美的遞歸。直到把整個View樹都完成了layout過程。
自此,我們就分析完了layout過程,相比與measure過程,簡單了很多。并且了解起來也更容易,就想想在一個大房間的地面上,我們怎麼使用遞歸的方式來擺放很多的盒子。
最後再來看看幾個方法。
同樣都是得到寬高的方式,這兩組方法有什麼差別呢?現在我們了解了Measure和layout過程,那麼我們就知道了,<code>getMeasuredWidth()</code>方法是在measure過程完成之後可以調用的,而<code>getWidth()</code>方法則是在 layout過程之後可以調用的。并且幾乎在所有情況下,兩者的傳回值都是相等的。
作者:
www.yaoxiaowen.com
github:
https://github.com/yaowen369
歡迎對于本人的部落格内容批評指點,如果問題,可評論或郵件([email protected])聯系