Flutter完整開發實戰詳解(七、-深入布局原理)
作為系列文章的第七篇,本篇主要在前文的基礎上,再深入了解 Widget 和布局中的一些常識性問題。
前文:
一、Dart語言和Flutter基礎 二、 快速開發實戰篇 三、 打包與填坑篇四、 Redux、主題、國際化
五、 深入探索 六、 深入Widget原理
在第六篇中我們知道了 Widget
、Element
、RenderObject
三者之間的關係,其中我們最為熟知的 Widget
,作為「配置文件」的存在,在 Flutter 中它的功能都是比較單一的,屬於「顆粒度比較細的存在」 ,寫代碼時就像拼樂高「積木」,那這「積木」究竟怎麼拼的?下面就 深入 去挖挖有意思的東西吧。( ̄▽ ̄)
一、單子元素布局
在 Flutter 單個子元素的布局 Widget 中,Container
無疑是被用的最廣泛的,因為它在「功能」上並不會如 Padding
等 Widget 那樣功能單一,這是為什麼呢?
究其原因,從下圖源碼可以看出,Container
其實也只是把其他「單一」的 Widget 做了二次封裝,然後通過配置來達到「多功能的效果」而已。
![](http://i1.wp.com/pic1.zhimg.com/50/v2-14ecc5d8a7dfc1a922ae72a4a1fff4ec_720w.webp)
接著我們先看 ConstrainedBox
源碼,從下圖源碼可以看出,它是繼承了 SingleChildRenderObjectWidget
,關鍵是 override 了 createRenderObject
方法,返回了 RenderConstrainedBox
。
這裡體現了第六篇中的 Widget 與 RenderObject 的關係
是的,RenderConstrainedBox
就是繼承自 RenderBox
,從而實現RenderObject
的布局,這裡我們得到了它們的關係如下 :
![](http://i1.wp.com/pic2.zhimg.com/50/v2-b0abf78c266a9617ff1253773888ca31_720w.webp)
![](http://i1.wp.com/pic4.zhimg.com/50/v2-30d44438b1e87ced5d35603626b642a7_720w.webp)
然後我們繼續對其他每個 Widget 進行觀察,可以看到它們也都是繼承SingleChildRenderObjectWidget
,而「簡單來說」它們不同的地方就是 RenderObject
的實現了:
![](http://i1.wp.com/pic1.zhimg.com/50/v2-23e510cbf8cfccd9917dac20269b15a4_720w.webp)
所以我們可以總結:真正的布局和大小計算等行為,都是在 RenderBox
上去實現的。 不同的 Widget 通過各自的 RenderBox
實現了「差異化」的布局效果。所以找每個 Widget 的實現,找它的 RenderBox
實現就可以了。
這裡我們通過 Offstage
這個Widget 小結下,Offstage
這個 Widget 是通過 offstage
標誌控制 child 是否顯示的效果,同樣的它也有一個 RenderOffstage
,如下圖,通過 RenderOffstage
的源碼我們可以「真實」看到 offstage
標誌位的作用:
![](http://i1.wp.com/pic4.zhimg.com/50/v2-aabf1e4a0f3092971227df428e0701ff_720w.webp)
所以大部分時候,我們的 Widget 都是通過實現 RenderBox
實現布局的 ,那我們可不可拋起 Widget 直接用 RenderBox
呢?答案明顯是可以的,如果你閑的??疼的話!
Flutter 官方為了治療我們「??疼」,提供了一個叫 CustomSingleChildLayout
的類,它抽象了一個叫 SingleChildLayoutDelegate
的對象,讓你可以更方便的操作 RenderBox
來達到自定義的效果。
![](http://i1.wp.com/pic4.zhimg.com/50/v2-baa7581557efa7fae05e3c09d34191c3_720w.webp)
如下圖三張源碼所示,SingleChildLayoutDelegate
的對象提供以下介面,並且介面 前三個 是按照順序被調用的,通過實現這個介面,你就可以輕鬆的控制RenderBox 的 布局位置、大小 等。
![](http://i1.wp.com/pic2.zhimg.com/50/v2-de7ac04c331077c95a93ff79b99d2a55_720w.webp)
![](http://i1.wp.com/pic4.zhimg.com/50/v2-09e045a4028e3f202fb8763233c71bdb_720w.webp)
![](http://i1.wp.com/pic4.zhimg.com/50/v2-98b93ee8d64df5953e4f254bd98c17c3_720w.webp)
二、多子元素布局
事實上「多子元素布局」和單子元素類似,通過「舉一反三」我們就可以知道它們的關係了,比如:
Row
、Colum
都繼承了Flex
,而 Flex 繼承了MultiChildRenderObjectWidget
並通過RenderFlex
創建了RenderBox
;Stack
同樣繼承MultiChildRenderObjectWidget
並通過RenderStack
創建了RenderBox
;
![](http://i1.wp.com/pic3.zhimg.com/50/v2-bb8b4578db48dae8778f8fa67edb00a6_720w.webp)
同樣「多子元素布局」也提供了 CustomMultiChildLayout
和 MultiChildLayoutDelegate
滿足你的「??疼」需求。
三、多子元素滑動布局
滑動布局作為 「多子元素布局」 的另一個分支,如 ListView
、GridView
、Pageview
,它們在實現上要複雜的多,從下圖一個的流程上我們大致可以知道它們的關係:
![](http://i1.wp.com/pic3.zhimg.com/50/v2-ca2ab29e63a4e9a1bcb34f2c3e68406a_720w.webp)
由上圖我們可以知道,流程最終回產生兩個 RenderObject :
RenderSliver
:Base class for the render objects that implement scroll effects in viewports.RenderViewport
:A render object that is bigger on the inside.
/// [RenderViewport] cannot contain [RenderBox] children directly. Instead, use
/// a [RenderSliverList], [RenderSliverFixedExtentList], [RenderSliverGrid], or
/// a [RenderSliverToBoxAdapter], for example.
並且從 RenderViewport
的說明我們知道,RenderViewport
內部是不能直接放置 RenderBox
,需要通過 RenderSliver
大家族來完成布局。而從源碼可知:RenderViewport
對應的 Widget Viewport
就是一個 MultiChildRenderObjectWidget
。 (你看,又回到 MultiChildRenderObjectWidget
了吧。)
再稍微說下上圖的流程:
ListView
、Pageview
、GridView
等都是通過Scrollable
、ViewPort
、Sliver
大家族實現的效果。這裡簡單不規範描述就是:一個「可滑動」的控制項,嵌套了一個「視覺窗口」,然後內部通過「碎片」展示 children 。- 不同的是
PageView
沒有繼承SrollView
,而是直接通過NotificationListener
和ScrollNotification
嵌套實現。 注意TabBarView
內部就是:NotificationListener
+PageView
是不是覺得少了什麼?哈哈哈,有的有的,官方同樣提供了解決「??疼」的自定義滑動 CustomScrollView
,它繼承了 ScrollView
,可通過 slivers 參數實現布局,這些 slivers
最終回通過 Scrollable
的 buildViewport
添加到 ViewPort
中,如下代碼所示:
CustomScrollView(
slivers: <Widget>[
const SliverAppBar(
pinned: true,
expandedHeight: 250.0,
flexibleSpace: FlexibleSpaceBar(
title: Text(Demo),
),
),
SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200.0,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 4.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.teal[100 * (index % 9)],
child: Text(grid item $index),
);
},
childCount: 20,
),
),
SliverFixedExtentList(
itemExtent: 50.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: Text(list item $index),
);
},
),
),
],
)
不知道你看完本篇後,有沒有對 Flutter 的布局有更深入的了解呢?讓我們愉悅的堆積木吧!
自此,第七篇終於結束了!(///▽///)
資源推薦
- Github : https://github.com/CarGuo
- 本文代碼 :https://github.com/CarGuo/GSYGithubAppFlutter
完整開源項目推薦:
- GSYGithubApp Flutter
- GSYGithubApp React Native
- GSYGithubAppWeex
文章
《Flutter完整開發實戰詳解(一、Dart語言和Flutter基礎)》
《Flutter完整開發實戰詳解(二、 快速開發實戰篇)》
《Flutter完整開發實戰詳解(三、 打包與填坑篇)》
《Flutter完整開發實戰詳解(四、Redux、主題、國際化)》
《Flutter完整開發實戰詳解(五、 深入探索)》
《Flutter完整開發實戰詳解(六、 深入Widget原理)》
《Flutter完整開發實戰詳解(七、 深入布局原理)》
《跨平台項目開源項目推薦》
《移動端跨平台開發的深度解析》
![](http://i1.wp.com/pic4.zhimg.com/50/v2-3e325a3bf61e3ddfdb155b80a18f5067_720w.webp)
推薦閱讀:
TAG:Flutter |