Flutter完整開發實戰詳解(7、 深刻佈局原理)

做爲系列文章的第七篇,本篇主要在前文的基礎上,再深刻了解 Widget 和佈局中的一些常識性問題。git

前文:github

在第六篇中咱們知道了 WidgetElementRenderObject 三者之間的關係,其中咱們最爲熟知的 Widget ,做爲「配置文件」的存在,在 Flutter 中它的功能都是比較單一的,屬於 「顆粒度比較細的存在」 ,寫代碼時就像拼樂高「積木」,那這「積木」究竟怎麼拼的?下面就 深刻 去挖挖有意思的東西吧。( ̄▽ ̄)markdown

1、單子元素佈局

在 Flutter 單個子元素的佈局 Widget 中,Container 無疑是被用的最普遍的,由於它在「功能」上並不會如 Padding 等 Widget 那樣功能單一,這是爲何呢?ide

究其緣由,從下圖源碼能夠看出,Container 其實也只是把其餘「單一」的 Widget 作了二次封裝,而後經過配置來達到「多功能的效果」而已。佈局

Container源碼

接着咱們先看 ConstrainedBox 源碼,從下圖源碼能夠看出,它是繼承了 SingleChildRenderObjectWidget,關鍵是 override 了 createRenderObject 方法,返回了 RenderConstrainedBoxpost

這裏體現了第六篇中的 Widget 與 RenderObject 的關係flex

是的,RenderConstrainedBox 就是繼承自 RenderBox,從而實現RenderObject 的佈局,這裏咱們獲得了它們的關係以下 :ui

Widget RenderObject
ConstrainedBox RenderConstrainedBox

ConstrainedBox

而後咱們繼續對其餘每一個 Widget 進行觀察,能夠看到它們也都是繼承SingleChildRenderObjectWidget ,而「簡單來講」它們不一樣的地方就是 RenderObject 的實現了:spa

Widget RenderBox (RenderObject)
Align RenderPositionedBox
Padding RenderPadding
Transform RenderTransform
Offstage RenderOffstage

因此咱們能夠總結:真正的佈局和大小計算等行爲,都是在 RenderBox 上去實現的。 不一樣的 Widget 經過各自的 RenderBox 實現了「差別化」的佈局效果。因此找每一個 Widget 的實現,找它的 RenderBox 實現就能夠了。3d

這裏咱們經過 Offstage 這個Widget 小結下,Offstage 這個 Widget 是經過 offstage 標誌控制 child 是否顯示的效果,一樣的它也有一個 RenderOffstage ,以下圖,經過 RenderOffstage 的源碼咱們能夠「真實」看到 offstage 標誌位的做用:

RenderOffstage

因此大部分時候,咱們的 Widget 都是經過實現 RenderBox 實現佈局的 ,那咱們可不可拋起 Widget 直接用 RenderBox呢?答案明顯是能夠的,若是你閒的🥚疼的話!

Flutter 官方爲了治療咱們「🥚疼」,提供了一個叫 CustomSingleChildLayout 的類,它抽象了一個叫 SingleChildLayoutDelegate 的對象,讓你能夠更方便的操做 RenderBox 來達到自定義的效果。

以下圖三張源碼所示,SingleChildLayoutDelegate 的對象提供如下接口,而且接口 前三個 是按照順序被調用的,經過實現這個接口,你就能夠輕鬆的控制RenderBox 的 佈局位置、大小 等。

2、多子元素佈局

事實上「多子元素佈局」和單子元素相似,經過「觸類旁通」咱們就能夠知道它們的關係了,好比:

  • RowColum 都繼承了 Flex,而 Flex 繼承了MultiChildRenderObjectWidget 並經過 RenderFlex 建立了 RenderBox
  • Stack 一樣繼承 MultiChildRenderObjectWidget 並經過 RenderStack 建立了 RenderBox
Widget RenderBox (RenderObject)
Row/Colum/Flex RenderFlex
Stack RenderStack
Flow RenderFlow
Wrap RenderWrap

一樣「多子元素佈局」也提供了 CustomMultiChildLayoutMultiChildLayoutDelegate 知足你的「🥚疼」需求。

3、多子元素滑動佈局

滑動佈局做爲 「多子元素佈局」 的另外一個分支,如 ListViewGridViewPageview ,它們在實現上要複雜的多,從下圖一個的流程上咱們大體能夠知道它們的關係:

由上圖咱們能夠知道,流程最終回產生兩個 RenderObject

  • RenderSliverBase class for the render objects that implement scroll effects in viewports.

  • RenderViewportA 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 了吧。)

再稍微說下上圖的流程:

  • ListViewPageviewGridView 等都是經過 ScrollableViewPortSliver你們族實現的效果。這裏簡單不規範描述就是:一個「可滑動」的控件,嵌套了一個「視覺窗口」,而後內部經過「碎片」展現 children

  • 不一樣的是 PageView 沒有繼承 SrollView,而是直接經過 NotificationListenerScrollNotification 嵌套實現。

注意 TabBarView 內部就是:NotificationListener + PageView

是否是以爲少了什麼?哈哈哈,有的有的,官方一樣提供瞭解決「🥚疼」的自定義滑動 CustomScrollView ,它繼承了 ScrollView,可經過 slivers 參數實現佈局,這些 slivers 最終回經過 ScrollablebuildViewport 添加到 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 的佈局有更深刻的瞭解呢?讓咱們愉悅的堆積木吧!

自此,第七篇終於結束了!(///▽///)

資源推薦

完整開源項目推薦:
文章

《Flutter完整開發實戰詳解(1、Dart語言和Flutter基礎)》

《Flutter完整開發實戰詳解(2、 快速開發實戰篇)》

《Flutter完整開發實戰詳解(3、 打包與填坑篇)》

《Flutter完整開發實戰詳解(4、Redux、主題、國際化)》

《Flutter完整開發實戰詳解(5、 深刻探索)》

《Flutter完整開發實戰詳解(6、 深刻Widget原理)》

《Flutter完整開發實戰詳解(7、 深刻佈局原理)》

《Flutter完整開發實戰詳解(8、 實用技巧與填坑)》

《Flutter完整開發實戰詳解(9、 深刻繪製原理)》

《Flutter完整開發實戰詳解(10、 深刻圖片加載流程)》

《Flutter完整開發實戰詳解(11、全面深刻理解Stream)》

《跨平臺項目開源項目推薦》

《移動端跨平臺開發的深度解析》

《React Native 的將來與React Hooks》

咱們還會再見嗎?
相關文章
相關標籤/搜索