Widget 分類
如果按照是否是有狀態(tài)的分類方式,那么Widget
就分為StatelessWidget
和StatefulWidget
,StatelessWidget
和StatefulWidget
的Element
都是ComponentElement
,并且都不具備RenderObject
。
他們UI
的構(gòu)建都是調(diào)用build
方法。區(qū)別就是StatelessWidget
只是簡單的實(shí)現(xiàn)了ComponentElement
,而StatefulWidget
則復(fù)雜了許多,他的build
是由_state
去控制的,狀態(tài)和數(shù)據(jù)都保存在這里面, 這個(gè)在之前的文章中有提及。
StatelessElement
代碼示例:
class StatelessElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatelessElement(StatelessWidget widget) : super(widget);
@override
StatelessWidget get widget => super.widget as StatelessWidget;
@override
Widget build() => widget.build(this);
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_dirty = true;
rebuild();
}
}
可以看出在更新的時(shí)候也只是把_dirty臟標(biāo)記設(shè)置為true,然后就重新構(gòu)建。
State和Element的生命周期對比
State數(shù)據(jù)的傳遞
上面的代碼,當(dāng)點(diǎn)擊FloatingActionButton
之后,最終顯示在屏幕上的文字是什么?為什么?
答案:
上面的代碼當(dāng)我們點(diǎn)擊按鈕之后,內(nèi)容并不會(huì)發(fā)生改變,因?yàn)?code>StatePage的state
已經(jīng)被創(chuàng)建過了,所以createState
不會(huì)走兩次,故而data并不會(huì)發(fā)生改變(但是StatePage
的data
是發(fā)生了改變的),如果我們想使用更新之后的值,我們可以使用widte.data
來引用。
class StatePage extends StatefulWidget {
StatePage({this.data});
final String data;
@override
_StatePageState createState() => _StatePageState();
}
class _StatePageState extends State<StatePage> {
@override
Widget build(BuildContext context) {
return Center(
child: Text(widget.data ?? ""),
);
}
}
setState是如何實(shí)現(xiàn)刷新的?
setState
內(nèi)部會(huì)調(diào)用_element.markNeedsBuild();
方法markNeedsBuild
方法會(huì)在內(nèi)部把_dirty
設(shè)置為true
,然后加入到定時(shí)器當(dāng)中,然后在下一幀的WidgetsBinding.drawFrame
才會(huì)被繪制。此處也可以得知,setState
并不會(huì)馬上生效。
RenderObject分類
RenderBox
特性:會(huì)根據(jù)parent
的constraints
大小判斷自己的布局方法,然后將constraints
傳遞給child
得到child
的大小,最后根據(jù)child
返回的Size
決定自己的Size
,如果沒有child
,就使用自己的Size
。
他用于那些不涉及的滾動(dòng)的控件布局,他的兩個(gè)關(guān)鍵參數(shù)就是BoxConstraints
和Size
。
RenderSliver
特性:因?yàn)槠渲饕糜赗enderViewport之后,里面涉及的運(yùn)算和屬性對比RenderBox要復(fù)雜上許多。他的兩個(gè)關(guān)鍵參數(shù)是SliverConstraints和SliverGeometry。
SliverConstraints和BoxConstraints對比,BoxContraints只包括了,最大/最小的高度/寬度。但是SliverConstraints則更多的是滑動(dòng)方向、滑動(dòng)偏移、滑動(dòng)容器大小、容器緩存大小和位置等相關(guān)參數(shù)。
Size和SliverGeometry進(jìn)行對比,Size只包括了寬和高。但是SliverGeometry包括了滑動(dòng)方位、繪制范圍、偏移等相關(guān)參數(shù)。
RenderBox和RenderSliver對比
RenderBox輸入輸出相較于RenderSliver更為簡單,RenderSliver更為關(guān)注滑動(dòng)、方向、緩存等關(guān)鍵點(diǎn),這也是因?yàn)槠湫枰蚔iewPort配合展示。例如我們經(jīng)常使用的ListView、GirdView、ScrollView等都是有Sliver和ViewPort組成的,可滑動(dòng)的區(qū)域內(nèi)不可以直接使用RenderBox,如果一定要使用必須用RenderSliver進(jìn)行嵌套后進(jìn)行布局。
ViewPort
ViewPort
根據(jù)自己的窗口的大小和偏移量,對child
進(jìn)行布局計(jì)算,通過對child
輸入SliverConstraints
來得到child
的SliverGeometry
,從而確定layout
和paint
等相關(guān)信息。
RenderSliver
對應(yīng)的Sliver
控件需要在ViewPort
中使用。
當(dāng)外部的滑動(dòng)事件產(chǎn)生時(shí),就會(huì)觸發(fā)到ViewPort
的markNeedsLayout
方法,之后變化重新進(jìn)行布局和繪制,并讓Sliver
在ViewPort
中進(jìn)行偏移,達(dá)到看起來像是滑動(dòng)了的效果。
RenderViewPort
中為了避免性能消耗,對于滑動(dòng)的時(shí)候內(nèi)部就會(huì)嘗試重新布局做了一個(gè)限制,最大的嘗試次數(shù)不能超過10次。
ListView
、GridView
內(nèi)部都是一個(gè)SliverList
構(gòu)成,他們的children
布局也是通過SliverList
進(jìn)行布局的。
RenderSliverList
中,會(huì)通過傳入的ramainingCacheExtent
、scrollOffset
等參數(shù)去決定哪些child
需要布局顯示,哪些child
不需要被布局繪制,從而保證了列表中內(nèi)存優(yōu)化和良好的繪制性能。
單元素與多元素分類
根據(jù)Widget
的child
是否支持單個(gè)/多個(gè)child
又可以分為SingleChildRenderObjectWidget
和MultiChildRenderObjectWidget
。
像我們經(jīng)常使用的Clip
、Opacity
、Padding
、Align
、SizededBox
等都屬于SingleChildRenderObjectWidget
;而Stack
、Row
、Column
、RichText
等則屬于MultiChildRenderObjectWidget
。針對兩個(gè)不同的RenderObjectWidget
,Flutter
提供了CustomSingleChildLayout
和CustomMultiChildLayout
的抽象封裝。
SingleChildRenderObjectWidget
SingleChildRenderObjectWidget
繼承RenderObjectWidget
,因?yàn)橹挥幸粋€(gè)child
,所以實(shí)現(xiàn)起來相對簡單。繪制流程是通過RenderObject
計(jì)算出自身的最大、最小寬高,并且通過performLayout
綜合得到child
返回的Size
、最后在進(jìn)行繪制。
MultiChildRenderObjectWidget
從上圖可以看出相較于SingleChildRenderObjectWidget
,MultiChildRenderObjectWidget
實(shí)現(xiàn)起來要復(fù)雜許多,主要復(fù)雜的部分在于RenderBox
,我們需要自定義一個(gè)類繼承于RenderBox
,同時(shí)還得混入ContainerRenderObjectMixin
和RenderBoxContainerDefaultsMixin
,然后去重寫他的兩個(gè)方法:setupParentData
和performLayout
,然后在重寫paint
方法,調(diào)用系統(tǒng)繪制方法,完成繪制操作。
下面用一個(gè)實(shí)際例子來演示:
01- 創(chuàng)建ContainerBoxparentData
這個(gè)就是對應(yīng)上圖中右下方的抽象類(ConstainerBoxParentData
)的具體實(shí)現(xiàn)
class RenderCloudParentData extends ContainerBoxParentData<RenderBox> {
/// 定義寬高
double width;
double height;
/// 通過offset和width、height得到一個(gè)矩形區(qū)域
Rect get content => Rect.fromLTWH(
offset.dx,
offset.dy,
width,
height,
);
}
02-創(chuàng)建RenderBox
這個(gè)就是對應(yīng)上圖的RenderBox
的具體實(shí)現(xiàn)
/// 從類的定義就可以很好的看出,該類需要繼承于RenderBox,
/// 同時(shí)還需要混入ContainerRenderObjectMixin、RenderBoxContainerDefaultsMixin
class RenderCloudWidget extends RenderBox
with
ContainerRenderObjectMixin<RenderBox, RenderCloudParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, RenderCloudParentData> {
/// 構(gòu)造方法
/// * children
/// * overflow 裁剪方式
/// * ratio 比例
RenderCloudWidget({
List<RenderBox> children,
Clip overflow = Clip.none,
double ratio,
}) : _ratio = ratio,
_overflow = overflow {
/// 這個(gè)是ContainerRenderObjectMixin的內(nèi)部方法,其內(nèi)部是一個(gè)雙線鏈表的結(jié)果,
/// 主要是用于快速定位下一個(gè)、上一個(gè)renderObject
addAll(children);
}
///圓周
double _mathPi = math.pi * 2;
///比例
double _ratio;
double get ratio => _ratio;
set ratio(double value) {
assert(value != null);
if (_ratio != value) {
_ratio = value;
markNeedsPaint();
}
}
/// 裁剪方式
Clip get overflow => _overflow;
set overflow(Clip value) {
assert(value != null);
if (_overflow != value) {
_overflow = value;
markNeedsPaint();
}
}
Clip _overflow;
/// 是否需要裁剪
bool _needClip = false;
/// 用于判斷是否重復(fù)區(qū)域了
bool overlaps(RenderCloudParentData data) {
Rect rect = data.content;
RenderBox child = data.previousSibling;
if (child == null) {
return false;
}
do {
RenderCloudParentData childParentData = child.parentData;
if (rect.overlaps(childParentData.content)) {
return true;
}
child = childParentData.previousSibling;
} while (child != null);
return false;
}
/// 這個(gè)就是需要重寫RenderBox其中的一個(gè)方法
@override
void setupParentData(covariant RenderObject child) {
if (child.parentData is! RenderCloudParentData) {
child.parentData = RenderCloudParentData();
}
}
/// 內(nèi)部布局方法,布局每一個(gè)child的位置大小
@override
void performLayout() {
///默認(rèn)不需要裁剪
_needClip = false;
///沒有 childCount 不玩
if (childCount == 0) {
size = constraints.smallest;
return;
}
///初始化區(qū)域
var recordRect = Rect.zero;
var previousChildRect = Rect.zero;
RenderBox child = firstChild;
while (child != null) {
var curIndex = -1;
///提出數(shù)據(jù)
final RenderCloudParentData childParentData = child.parentData;
child.layout(constraints, parentUsesSize: true);
var childSize = child.size;
///記錄大小
childParentData.width = childSize.width;
childParentData.height = childSize.height;
do {
///設(shè)置 xy 軸的比例
var rX = ratio >= 1 ? ratio : 1.0;
var rY = ratio <= 1 ? ratio : 1.0;
///調(diào)整位置
var step = 0.02 * _mathPi;
var rotation = 0.0;
var angle = curIndex * step;
var angleRadius = 5 + 5 * angle;
var x = rX * angleRadius * math.cos(angle + rotation);
var y = rY * angleRadius * math.sin(angle + rotation);
var position = Offset(x, y);
///計(jì)算得到絕對偏移
var childOffset = position - Alignment.center.alongSize(childSize);
++curIndex;
///設(shè)置為遏制
childParentData.offset = childOffset;
///判處是否交疊
} while (overlaps(childParentData));
///記錄區(qū)域
previousChildRect = childParentData.content;
recordRect = recordRect.expandToInclude(previousChildRect);
///下一個(gè)
child = childParentData.nextSibling;
}
///調(diào)整布局大小
size = constraints
.tighten(
height: recordRect.height,
width: recordRect.width,
)
.smallest;
///居中
var contentCenter = size.center(Offset.zero);
var recordRectCenter = recordRect.center;
var transCenter = contentCenter - recordRectCenter;
child = firstChild;
while (child != null) {
final RenderCloudParentData childParentData = child.parentData;
childParentData.offset += transCenter;
child = childParentData.nextSibling;
}
///超過了嘛?
_needClip =
size.width < recordRect.width || size.height < recordRect.height;
}
/// 設(shè)置繪制默認(rèn)
@override
void paint(PaintingContext context, Offset offset) {
if (!_needClip || _overflow == Clip.none) {
defaultPaint(context, offset);
} else {
context.pushClipRect(
needsCompositing,
offset,
Offset.zero & size,
defaultPaint,
);
}
}
/// 觸摸測試,如果不想響應(yīng)就返回false,反正則是true
@override
bool hitTestChildren(HitTestResult result, {Offset position}) {
return defaultHitTestChildren(result, position: position);
}
}
03-創(chuàng)建Widget
主要是把RenderObject
和Widget
進(jìn)行關(guān)聯(lián)起來
/// 創(chuàng)建Widget,繼承與MultiChildRenderObjectWidget
/// 主要是和之前的RenderBox關(guān)聯(lián)起來
class CloudWidget extends MultiChildRenderObjectWidget {
/// 自定義的相關(guān)屬性
final Clip overflow;
final double ratio;
/// 構(gòu)造方法
CloudWidget({
Key key,
this.ratio = -1,
this.overflow = Clip.none,
List<Widget> children = const <Widget>[],
}) : super(key: key, children: children);
/// 重寫創(chuàng)建RenderObject的方法,把之前創(chuàng)建的RenderCouldWidget返回
@override
RenderObject createRenderObject(BuildContext context) {
return RenderCloudWidget(ratio: ratio, overflow: overflow);
}
/// 在這里更新RenderCloudWidget的兩個(gè)關(guān)鍵參數(shù)
@override
void updateRenderObject(
BuildContext context, covariant RenderCloudWidget renderObject) {
/// ..表示級聯(lián)操作符
renderObject
..ratio = ratio
..overflow = overflow;
}
}
04-demo
///云詞圖
class CloudDemoPage extends StatefulWidget {
@override
_CloudDemoPageState createState() => _CloudDemoPageState();
}
class _CloudDemoPageState extends State<CloudDemoPage> {
///Item數(shù)據(jù)
List<CloudItemData> dataList = const <CloudItemData>[
CloudItemData('CloudGSY11111', Colors.amberAccent, 10, false),
CloudItemData('CloudGSY3333333T', Colors.limeAccent, 16, false),
CloudItemData('CloudGSYXXXXXXX', Colors.black, 14, true),
CloudItemData('CloudGSY55', Colors.black87, 33, false),
CloudItemData('CloudGSYAA', Colors.blueAccent, 15, false),
CloudItemData('CloudGSY44', Colors.indigoAccent, 16, false),
CloudItemData('CloudGSYBWWWWWW', Colors.deepOrange, 12, true),
CloudItemData('CloudGSY<<<', Colors.blue, 20, true),
CloudItemData('FFFFFFFFFFFFFF', Colors.blue, 12, false),
CloudItemData('BBBBBBBBBBB', Colors.deepPurpleAccent, 14, false),
CloudItemData('CloudGSY%%%%', Colors.orange, 20, true),
CloudItemData('CloudGSY%%%%%%%', Colors.blue, 12, false),
CloudItemData('CloudGSY&&&&', Colors.indigoAccent, 10, false),
CloudItemData('CloudGSYCCCC', Colors.yellow, 14, true),
CloudItemData('CloudGSY****', Colors.blueAccent, 13, false),
CloudItemData('CloudGSYRRRR', Colors.redAccent, 12, true),
CloudItemData('CloudGSYFFFFF', Colors.blue, 12, false),
CloudItemData('CloudGSYBBBBBBB', Colors.cyanAccent, 15, false),
CloudItemData('CloudGSY222222', Colors.blue, 16, false),
CloudItemData('CloudGSY1111111111111111', Colors.tealAccent, 19, false),
CloudItemData('CloudGSY####', Colors.black54, 12, false),
CloudItemData('CloudGSYFDWE', Colors.purpleAccent, 14, true),
CloudItemData('CloudGSY22222', Colors.indigoAccent, 19, false),
CloudItemData('CloudGSY44444', Colors.yellowAccent, 18, true),
CloudItemData('CloudGSY33333', Colors.lightBlueAccent, 17, false),
CloudItemData('CloudGSYXXXXXXXX', Colors.blue, 16, true),
CloudItemData('CloudGSYFFFFFFFF', Colors.black26, 14, false),
CloudItemData('CloudGSYZUuzzuuu', Colors.blue, 16, true),
CloudItemData('CloudGSYVVVVVVVVV', Colors.orange, 12, false),
CloudItemData('CloudGSY222223', Colors.black26, 13, true),
CloudItemData('CloudGSYGFD', Colors.yellow, 14, true),
CloudItemData('GGGGGGGGGG', Colors.deepPurpleAccent, 14, false),
CloudItemData('CloudGSYFFFFFF', Colors.blueAccent, 10, true),
CloudItemData('CloudGSY222', Colors.limeAccent, 12, false),
CloudItemData('CloudGSY6666', Colors.blue, 20, true),
CloudItemData('CloudGSY33333', Colors.teal, 14, false),
CloudItemData('YYYYYYYYYYYYYY', Colors.deepPurpleAccent, 14, false),
CloudItemData('CloudGSY 3 ', Colors.blue, 10, false),
CloudItemData('CloudGSYYYYYY', Colors.black54, 17, true),
CloudItemData('CloudGSYCC', Colors.lightBlueAccent, 11, false),
CloudItemData('CloudGSYGGGGG', Colors.deepPurpleAccent, 10, false)
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: new Text("CloudDemoPage"),
),
body: new Center(
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.width,
///利用 FittedBox 約束 child
child: new FittedBox(
/// Cloud 布局
child: Container(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 6),
color: Colors.brown,
///布局
child: CloudWidget(
///容器寬高比例
ratio: 1,
children: <Widget>[
for (var item in dataList)
///判斷是否旋轉(zhuǎn)
RotatedBox(
quarterTurns: item.rotate ? 1 : 0,
child: Text(
item.text,
style: new TextStyle(
fontSize: item.size,
color: item.color,
),
),
),
],
),
),
),
),
),
);
}
}
class CloudItemData {
///文本
final String text;
///顏色
final Color color;
///旋轉(zhuǎn)
final bool rotate;
///大小
final double size;
const CloudItemData(
this.text,
this.color,
this.size,
this.rotate,
);
}
CustomMultiChildLayout
官方為了簡化我們實(shí)現(xiàn)自定義布局的方式,還提供了CustomMultiChildLayout
這樣的類,這個(gè)類也是繼承了MultiChildRenderObjectWidget
,并通過一個(gè)代理(MultiChildLayoutDelegate
)來完成自定義UI相關(guān)的功能,通過這個(gè)代理,我們可以直接去重寫內(nèi)部的performLayout
方法,從而達(dá)到我們自定布局的效果。
01-創(chuàng)建Delegate
class CircleLayoutDelegate extends MultiChildLayoutDelegate {
final List<String> customLayoutId;
final Offset center;
Size childSize;
CircleLayoutDelegate(
this.customLayoutId, {
this.center = Offset.zero,
this.childSize,
});
@override
void performLayout(Size size) {
for (var item in customLayoutId) {
if (hasChild(item)) {
double r = 100;
/// 下標(biāo)
int index = int.parse(item);
/// 均分
double step = 360 / customLayoutId.length;
/// 角度
double hd = (2 * math.pi / 360) * step * index;
var x = center.dx + math.sin(hd) * r;
var y = center.dy + math.cos(hd) * r;
/// 使用??= 避免多次賦值
childSize ??= Size(size.width / customLayoutId.length,
size.height / customLayoutId.length);
layoutChild(item, BoxConstraints.loose(childSize));
final double centerX = childSize.width * 0.5;
final double centerY = childSize.height * 0.5;
var result = Offset(x - centerX, y - centerY);
/// 設(shè)置child位置
positionChild(item, result);
}
}
}
@override
bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) {
return true;
}
}
02-使用
class CustomMultiLayoutPage extends StatefulWidget {
@override
_CustomMultiLayoutPageState createState() => _CustomMultiLayoutPageState();
}
class _CustomMultiLayoutPageState extends State<CustomMultiLayoutPage> {
///用于 LayoutId 指定
///CircleLayoutDelegate 操作具體 Child 的 ChildId 是通過 LayoutId 指定的
List customLayoutId = ["0", "1", "2", "3", "4"].toList();
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final childSize = 66.0;
return Scaffold(
appBar: AppBar(),
body: Center(
child: Container(
color: Colors.yellowAccent,
width: size.width,
height: size.width,
child: CustomMultiChildLayout(
delegate: CircleLayoutDelegate(
customLayoutId,
childSize: Size(childSize, childSize),
center: Offset(size.width * 0.5, size.width * 0.5),
),
children: [
///使用 LayoutId 指定 childId
for (var item in customLayoutId)
new LayoutId(id: item, child: ContentItem(item, childSize)),
],
),
),
),
persistentFooterButtons: <Widget>[
TextButton(onPressed: () {
setState(() {
customLayoutId.add("${customLayoutId.length}");
});
},
child: Icon(Icons.add),
),
TextButton(onPressed: () {
setState(() {
if (customLayoutId.length > 1) {
customLayoutId.removeLast();
}
});
},
child: Icon(Icons.remove),
),
],
);
}
}
class ContentItem extends StatelessWidget {
final String text;
final double childSize;
ContentItem(this.text, this.childSize);
@override
Widget build(BuildContext context) {
return Material(
color: Theme.of(context).primaryColor,
borderRadius: BorderRadius.circular(childSize / 2.0),
child: InkWell(
radius: childSize / 2.0,
customBorder: CircleBorder(),
onTap: () {},
child: Container(
width: childSize,
height: childSize,
child: Center(
child: Text(
text,
style: Theme.of(context)
.textTheme
.headline6
.copyWith(color: Colors.white),
),
),
),
),
);
}
}
效果圖
InheritedWidget共享狀態(tài)
InheritedWidge
是Flutter Widget
中非常重要的一個(gè)構(gòu)成部分,因?yàn)?code>InheritedWidget常被用于數(shù)據(jù)共享。比如使用頻率很高的:Theme/ThemeData
、Text/DefaultTextStyle
、Slider/SliderTheme
、Icon/IconTheme
等內(nèi)部都是通過InheritedWidget
實(shí)現(xiàn)數(shù)據(jù)共享的。并且Flutter
中部分的狀態(tài)管理框架,內(nèi)部的狀態(tài)共享方法也是基于InheritedWidget
去實(shí)現(xiàn)的。
InheritedWidget繼承自ProxyWidget,本身并不具備繪制的能力,但共享這個(gè)Widget等與共享Widget內(nèi)保存的數(shù)據(jù),獲取Widget就可以獲取到其內(nèi)部保存的數(shù)據(jù),如下圖:
每一個(gè)Element
當(dāng)中都有一個(gè)成員變量:Map<Type, InheritedElement> _inheritedWidgets
,改成員變量默認(rèn)是空,之后當(dāng)父控件是InheritedWidget
或者本身是InheritedWidget
的時(shí)候才會(huì)初始化,當(dāng)父控件是InheritedWidget
的時(shí)候,這個(gè)Map
會(huì)逐級向下傳遞于合并。
那么context.inheritedFromWidgetOfExactType
內(nèi)部做了啥呢?
通過查看Element
的源碼截圖部分片段
@override
InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
assert(_debugCheckStateIsActiveForAncestorLookup());
/// 首先判斷是否有inheritedElement類型的數(shù)據(jù)
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
/// 找到了
if (ancestor != null) {
assert(ancestor is InheritedElement);
/// 添加到依賴集合中,并且通過updateDependencies將當(dāng)前的Element添加到_dependencies Map中,并且返回InheritedWidget
return inheritFromElement(ancestor, aspect: aspect);
}
_hadUnsatisfiedDependencies = true;
return null;
}
/// 下面兩個(gè)方法就是添加過程的實(shí)現(xiàn)
@override
InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
return dependOnInheritedElement(ancestor, aspect: aspect);
}
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
/// 創(chuàng)建_dependencies
_dependencies ??= HashSet<InheritedElement>();
/// 添加InheritedElement到集合中
_dependencies.add(ancestor);
/// 跟新依賴
ancestor.updateDependencies(this, aspect);
/// 返回InheritedWidget
return ancestor.widget;
}
InheritedWidget是如何通知StatefulWidget進(jìn)行更新的?
例如:當(dāng)我們在外界調(diào)用Theme.of(context)
的時(shí)候,BuildContext
的實(shí)現(xiàn)就是Element
,所以當(dāng)內(nèi)部調(diào)用到context.inheritedFromWidgetOfExactType
時(shí),就會(huì)將context
所代表的Element
添加到InheritedElement
的_dependents
中,當(dāng)InheritedElement
被更新的時(shí)候,就會(huì)觸發(fā)到齊內(nèi)部的notifyClients
方法,該方法就會(huì)挨個(gè)遍歷被加入到_dependents
,從而觸發(fā)到didChangeDependcies
,然后就會(huì)更新UI
ErrorWidget 異常處理
在以往的開發(fā)中,當(dāng)我們程序拋出一些未處理的異常或者錯(cuò)誤的時(shí)候,就會(huì)引發(fā)程序的crash
,但是在Flutter
中則不會(huì),這是因?yàn)?code>Flutter中有一個(gè)全局處理的地方;
當(dāng)我們的代碼發(fā)生一些問題之后,在debug
模式下可能會(huì)有某些或者整個(gè)頁面變成紅色,并顯示一些錯(cuò)誤信息;在release
模式下,則會(huì)顯示灰色的并沒有錯(cuò)誤提示。
為了能讓我們的產(chǎn)品體驗(yàn)更好,我們可以在main方法中做一些處理,讓錯(cuò)誤看起來更加優(yōu)雅
void main() {
runZoned((){
ErrorWidget.builder = (FlutterErrorDetails details) {
Zone.current.handleUncaughtError(details.exception, details.stack);
return Container(color: Colors.orange,);
};
FlutterError.onError = (FlutterErrorDetails details) async {
FlutterError.dumpErrorToConsole(details);
Zone.current.handleUncaughtError(details.exception, details.stack);
};
runApp(MyApp());
}, onError: (Object obj, StackTrace stack) {
print(obj);
print(stack);
});
}