同步操作将从 CarGuo/GSYFlutterBook 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
作为系列文章的第七篇,本篇主要在前文的基础上,再深入了解 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'),
);
},
),
),
],
)
自此,第七篇终于结束了!(///▽///)
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。