Flutter 視圖布局(三)
- 2019 年 11 月 11 日
- 筆記
各位少俠、小夥伴們久違了哈哈哈哈!(咕咕咕)
於近期有些事需要處理,未能及時更新非常抱歉!!!
之前的幾篇中開頭也把一些要注意的東西說完了,所以也不用那麼多廢話了,不多逼逼直接進入主題,就問你們開心不開心

那麼這次就繼續來說說關於視圖布局的東西 Table、Wrap、Flow。相對於之前介紹的布局 Widget 這三個在實現上就需要編寫一些對子元素控制邏輯了。
OK,那我們就一起來看看它們的究竟有哪些不同。
當然,慣例配合文章一同食用的程式碼已同步更新到 Github 地址:
https://github.com/linxsbox/myapp.git
01 – Tabel
首先進入眼帘的的是我們的一號選手 Table,也是大家都熟悉的選手。但幾乎好像每一個程式語言具有UI繪製的部分都會有 Table。也許是它們對表格愛得深沉(大霧!)

依照慣例,我們現在需要做什麼呢?
沒錯!當然是看源碼部分啦。

- List<TableRow> children 子元素列表 TableRow 類型
- TableBorder border 表格的邊框線
- Map<int, TableColumnWidth> columnWidths 表格的列寬
- FlexColumnWidth defaultColumnWidth Tabel 中列的寬度分配方式,基於 Flex
- TableCellVerticalAlignment defaultVerticalAlignment Table 中單元格的垂直對齊方式
- TextBaseline textBaseline 文字基準線對齊方式
- TextDirection textDirection 文字裝飾屬性
唉~這一看,是不是有幾個屬性已經眼熟了呢?那麼熟悉的部分就不重複說明了。
children
你這一看,唉喲~是個老熟人,子元素列表嘛。上手就干 children: <Widget>[] 一頓猛敲。編輯器里的紅色下劃波浪線就像考試卷上老師畫下的紅叉,學生時代被考試支配的恐懼從心底如潮水般湧起,久久不能退去。
(什麼?!你說你是學霸?!我不認識你!)

啪!啪!啪!(敲黑板)各位少俠,認真審題啊 List<TableRow> children 子元素列表 TableRow 類型,TableRow 類型啊!上一篇才說完的不要蒙頭魯莽怎麼就不上心呢?你又不是 the shy。

columnWidths/defaultColumnWidth
你也許會奇怪為什麼這兩個屬性會放到一起?
主要還是因為這兩個屬性所使用的類型的是 TableColumnWidth 這是 Table 里對列寬度設定而實現的類。我們來看一下它的源碼部分。
我簡單翻譯一下:
TableColumnWidth 類用來描述 [RenderTable] 中的列應該有多寬。
如果需要將列設置為固定的大小,那麼請使用 [FixedColumnWidth] 這是調整列寬消耗最小的方法。
其他相對消耗較小的列寬演算法有 [FlexColumnWidth],它可以彈性分配所需的空間。
[FractionColumnWidth] 是基於 Table 的容器最大寬度。
當然除了以上提到列寬類型之外還有其他的列寬類型
- IntrinsicColumnWidth 固有列寬,但其單元格以彈性方式計算
- FixedColumnWidth 固定列寬
- FractionColumnWidth 小部分列寬?(可能我英語水平不夠實在是想不到好的中文解釋)
- FlexColumnWidth 彈性列寬,defaultColumnWidth 中默認類型
- MaxColumnWidth 最大列寬,其參數類型為 TableColumnWidth
- MinColumnWidth 最小列寬,其參數類型為 TableColumnWidth
不過這裡要注意的是 FractionColumnWidth 單獨使用的時候編譯器會輸出警告資訊,雖然不會導致編譯錯誤但是會導致渲染錯誤,表格無法正常渲染,以及列數據丟失。
IntrinsicColumnWidth 比較特殊,源碼注釋中說到,這是一種消耗非常大的列表寬度調整方式,它需要計算列中的每一個單元格的寬度來確定。而 FixedColumnWidth 是消耗最小的方式。
關於列寬的設置方式我已經在程式碼中全部列出來了,各位少俠可以更新 GitHub 來嘗試不同的列寬設置組合。
TableCellVerticalAlignment
單元格的垂直對齊方式,這個也無需多說了。
- top 頂部對齊
- middle 垂直居中對齊
- bottom 底部對齊
- baseline 基準線對齊
- fill 充滿 Cell
border
這個用起來和 css 的差不多,唯一需要注意的就是不要放錯位置了,這個是 Table 下的屬性之一。
Table( // 表格邊框 border: TableBorder.all( width: 1.0, // 邊框線寬度 style: BorderStyle.solid, // 邊框線風格 color: Colors.red, // 邊框線顏色 ),)
如果想要實現動態表格項的話還是需要使用編碼的方式。

這樣我們就可以通過以參數的方式來控制生成表格行列以及內容了。
02 – Wrap
在水平或垂直方向中顯示多個子元素的部件。這該怎麼理解呢?

- List<Widget> children 子元素列表,注意看類型基本不是問題
- Axis direction 決定主軸的方向
- WrapAlignment alignment 主軸方向的行內子元素的對齊方式
- WrapCrossAlignment crossAxisAlignment 副軸方向的行對齊方式
- WrapAlignment runAlignment 副軸方向上的行內子元素對齊方式
- VerticalDirection verticalDirection 垂直方向
- TextDirection textDirection 文字方向
- spacing 與下一個子元素的空間大小,默認為 0與下一個 widget 的空間大小,默認為 0與下一個 widget 的空間大小,默認為 0
- runSpacing 行與下一行之間的空間大小,默認為 0
direction
決定了主軸的方向,有 horizontal 和 vertical 分別是橫向和縱向,默認為horizontal,如果還對軸概念不理解也可以去看看布局(一)這一篇或者在程式碼里修改一下嘗試看看結果。
alignment
主軸方向的行內子元素的對齊方式,即是決定的行內的子元素排列對齊的方式,可選值為軸線對齊和空間對齊的方式。
crossAxisAlignment
副軸方向的行對齊方式,在副軸上以行為單位的對齊方式,當然可選值也是軸線對齊和空間對齊的方式。
runAlignment
副軸方向上的行內子元素對齊方式,這裡比較有意思的是,剛開始我還沒以為沒效果,因為我沒有使用寬高屬性來設定子元素的大小,後來才發現當子元素的寬高有差異的時候才能看得出來。
spacing & runSpacing
spacing 控制元素之間的間隔。
runSpacing 控制行之間的間隔。
這兩個也不用特別說明,看圖就明白了。

其實這有點類似 css 的 flex,只是使用了其中的一些概念,如果你對 flex 較為熟悉的話,那麼使用 Wrap 使用起來也是沒有太大難度。
03 – Flow
Flow 顧名思義,即流式布局,通過演算法實現的布局部件。


嗯?這乍一看怎麼才2個屬性?難道如此簡單?我勸你先不要盲目樂觀,莫急。
@required
FlowDelegate delegate Widget 繪製子對象的委託,必須實現
List<Widget> children 子元素列表
children
很熟悉了,也就不再重複說明了,主要的是 delegate 部分。
delegate
delegate 的類型是 FlowDelegate,如果不寫的話。

那麼這個 delegate 到底是個什麼東西呢?
在源碼中的注釋我可以翻譯一下:
The delegate that controls the transformation matrices of the children.
用於控制子對象的變換矩陣的委託。
噢~原來這是一個委託,簡單來說就是用於控制子元素的繪製的委託實現。如果不實現的話,那麼子元素就無法進行繪製渲染了。
FlowDelegate 主要有如下5個函數:
- getSize 需要重寫來控制子容器的大小,默認的情況下會儘可能的大,如果返回的大小不符合給定的約束,則會調整為最接近的大小,但同時仍然遵守約束。
- getConstraintsForChild 重寫以提供給每個子元素的布局約束控制。默認情況下子元素會遵守給定的約束,這些約束會用於調整容器的大小。
- paintChildren 重寫繪製子元素。可以按任意順序繪製子對象,但是每個子元素最多只能繪製一次,容器會將子元素剪裁到自己的邊界內。
- shouldRelayout 重寫此函數以便在需要布局子元素時時返回 true。它會比較當前的委託實現和給定的 oldDelegate的欄位,如果它們不同則返回 true。
- shouldRepaint 重寫此函數以便在子元素需要時返回 true。這將重新繪製子元素,它會比較當前的委託實現和給定的 oldDelegate的欄位,如果它們不同則返回 true。如果委託實現時提供了重繪的動畫,那麼此委託也可以觸發重繪,這種基於動畫機制觸發的重繪比重新構建 Flow Widget 然後再更改委託更有效。即使此函數返回了 false 容器也依然可能重新繪製。
看完上面的內容你可能會一頭霧水,這些都是什麼鬼?那我要怎麼實現呢?這裡完全沒說啊。
不要慌不要怕,其實呢在源碼中已經給出實現例子了,我們只要去掉不需要的部分,留下最簡單的繪製子元素的部分就Ok。

由源碼中的例子可以看出,主要是實現了一個類,這個類繼承了 FlowDelegate,然後重寫了 shouldRepaint 和 paintChildren 函數,shouldRepaint 還算簡單,只要是菜單動畫發生了變化則返回true,然後讓子元素重新繪製。而 paintChildren 部分則是通過繪製的內容中獲取子元素,然後再獲取子元素的寬度,通過矩陣變換的方式來重繪子元素。
大致了解這個實現過程後,我們就來自己寫一個 Flow。
在這裡可看到並沒有編寫太多的程式碼,但是運行的時候就不會報錯了。雖然沒有將子元素的內容繪製在介面上,但是通過 for 循環已經取到了子元素的對象資訊,這裡控制台已經輸出了子元素的大小資訊。
接下來我們只要再將繪製部分的程式碼通過矩陣定位的方式,將子元素渲染到 Flow 容器中就可以在介面中看到子元素最終的身影了。

最終效果的話我就不展示了,少俠小夥伴們,可以自己更新修改程式碼嘗試喲。
配合文章一同食用的程式碼已同步更新到 Github 地址:
https://github.com/linxsbox/myapp.git
結語
整體來說除了 Flow 繪製部分稍微要理解一下,Table、Wrap就很常規了,不用考慮太多即可直接使用生成,但是如果想要更靈活的控制子元素的話那麼一定是非 Flow 莫屬,它可以使用矩陣的方式來進行對子元素的處理,例如說子元素高度不同的情況,就可以通過計算當前列高來確定下一個子元素的 y 軸位置。
最後總結
- 一門優秀的開源程式語言會在源碼中編寫詳細的注釋說明和提供恰當的例子予以解惑,當你不想看文檔時,可以考慮看看源碼。
- 向著優秀的開源程式語言學習,在程式碼中編寫必要且恰當的注釋內容,不論是未來回頭再看或者是將程式碼交予別人,這應當成為一個習慣。
感謝大家的喜歡!
歡迎 關注、留言、分享、轉發、在看。
