作為系列文章的第七篇,本篇主要在前文的基礎上,再深入瞭解 Widget 和佈局中的一些常識性問題。
文章彙總地址:
在第六篇中我們知道了 Widget
、Element
、RenderObject
三者之間的關係,其中我們最為熟知的 Widget
,作為“配置檔案”的存在,在 Flutter 中它的功能都是比較單一的,屬於 “顆粒度比較細的存在” ,寫程式碼時就像拼樂高“積木”,那這“積木”究竟怎麼拼的?下面就 深入 去挖挖有意思的東西吧。( ̄▽ ̄)
一、單子元素佈局
在 Flutter 單個子元素的佈局 Widget 中,Container
無疑是被用的最廣泛的,因為它在“功能”上並不會如 Padding
等 Widget 那樣功能單一,這是為什麼呢?
究其原因,從下圖原始碼可以看出,Container
其實也只是把其他“單一”的 Widget 做了二次封裝,然後通過配置來達到“多功能的效果”而已。
接著我們先看 ConstrainedBox
原始碼,從下圖原始碼可以看出,它是繼承了 SingleChildRenderObjectWidget
,關鍵是 override 了 createRenderObject
方法,返回了 RenderConstrainedBox
。
這裡體現了第六篇中的 Widget 與 RenderObject 的關係
是的,RenderConstrainedBox
就是繼承自 RenderBox
,從而實現RenderObject
的佈局,這裡我們得到了它們的關係如下 :
Widget | RenderObject |
---|---|
ConstrainedBox | RenderConstrainedBox |
然後我們繼續對其他每個 Widget 進行觀察,可以看到它們也都是繼承SingleChildRenderObjectWidget
,而“簡單來說”它們不同的地方就是 RenderObject
的實現了:
Widget | RenderBox (RenderObject) |
---|---|
Align | RenderPositionedBox |
Padding | RenderPadding |
Transform | RenderTransform |
Offstage | RenderOffstage |
所以我們可以總結:真正的佈局和大小計算等行為,都是在 RenderBox
上去實現的。 不同的 Widget 通過各自的 RenderBox
實現了“差異化”的佈局效果。所以找每個 Widget 的實現,找它的 RenderBox
實現就可以了。(當然,另外還有 RenderSliver
,這裡暫時不討論)
這裡我們通過 Offstage
這個Widget 小結下,Offstage
這個 Widget 是通過 offstage
標誌控制 child 是否顯示的效果,同樣的它也有一個 RenderOffstage
,如下圖,通過 RenderOffstage
的原始碼我們可以“真實”看到 offstage
標誌位的作用:
所以大部分時候,我們的 Widget 都是通過實現 RenderBox
實現佈局的 ,那我們可不可拋起 Widget 直接用 RenderBox
呢?答案明顯是可以的,如果你閒的?疼的話!
Flutter 官方為了治療我們“?疼”,提供了一個叫 CustomSingleChildLayout
的類,它抽象了一個叫 SingleChildLayoutDelegate
的物件,讓你可以更方便的操作 RenderBox
來達到自定義的效果。
如下圖三張原始碼所示,SingleChildLayoutDelegate
的物件提供以下介面,並且介面 前三個 是按照順序被呼叫的,通過實現這個介面,你就可以輕鬆的控制RenderBox 的 佈局位置、大小 等。
二、多子元素佈局
事實上“多子元素佈局”和單子元素類似,通過“舉一反三”我們就可以知道它們的關係了,比如:
Row
、Colum
都繼承了Flex
,而 Flex 繼承了MultiChildRenderObjectWidget
並通過RenderFlex
建立了RenderBox
;Stack
同樣繼承MultiChildRenderObjectWidget
並通過RenderStack
建立了RenderBox
;
Widget | RenderBox (RenderObject) |
---|---|
Row/Colum/Flex | RenderFlex |
Stack | RenderStack |
Flow | RenderFlow |
Wrap | RenderWrap |
同樣“多子元素佈局”也提供了 CustomMultiChildLayout
和 MultiChildLayoutDelegate
滿足你的“?疼”需求。
三、多子元素滑動佈局
滑動佈局作為 “多子元素佈局” 的另一個分支,如 ListView
、GridView
、Pageview
,它們在實現上要複雜的多,從下圖一個的流程上我們大致可以知道它們的關係:
由上圖我們可以知道,流程最終回產生兩個 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 : github.com/CarGuo/
- 開源 Flutter 完整專案:github.com/CarGuo/GSYG…
- 開源 Flutter 多案例學習型: github.com/CarGuo/GSYF…
- 開源 Fluttre 實戰電子書專案:github.com/CarGuo/GSYF…