likes
comments
collection
share

Flutter架构初探

作者站长头像
站长
· 阅读数 34

通过一个小问题引出本次分享

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Container(
          child: FlatButton(
              onPressed: () {
                Navigator.of(context).push(
                    MaterialPageRoute(builder: (context) => SecondPage()));
              },
              child: Text('跳转')),
        ),
      ),
    );
  }

}

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
    );
  }
}

点击跳转按钮发现并没有跳转,还报了一个错误

Navigator operation requested with a context that does not include a Navigator.

Widget

flutterEverything is Widget足见Widget之重要性

  • Widget是什么??
  • Widget在整个加载绘制的流程中扮演了什么角色??
  • Widget可变吗??
  • Widget可复用吗??
  • Widget加载之后是怎么存储的??

Flutter架构初探

  • Widget是不可变的配置文件,真正展示的不是他
  • Widget是可以被复用的
  • Widget本身不可变,但是可以通过State实现可变效果

BuildContext

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {}
}

StatelessWidget中有一个build方法传入了context,那么这个build方法是何时触发的,传入的context究竟为何物呢??

StatelessWidget

abstract class StatelessWidget extends Widget {
  const StatelessWidget({ Key key }) : super(key: key);

  @override
  StatelessElement createElement() => StatelessElement(this);

  @protected
  Widget build(BuildContext context);
}

StatelessElement

class StatelessElement extends ComponentElement {
  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();
  }
}

现在我们知道context就是这个StatelessElement对象,StatelessElement中的build方法会触发StatelessWidget中的build方法,StatelessElementbuild方法是Flutter Framework在构建UI树时隐式调用的

其继承关系为

class StatelessElement extends ComponentElement

abstract class ComponentElement extends Element

abstract class Element extends DiagnosticableTree implements BuildContext

使用BuildContext是不鼓励直接访问Element对象 Flutter架构初探

MaterialApp & Navigator

Flutter架构初探

我们看到Navigator是由MaterialApp提供的

flutter 渲染三棵树(Widget、Element、RenderObject)

我们创建的Widget被加载以后最终都会以树的形式存储,上面例子中的Widget Tree

Flutter架构初探

在Flutter中Widget Tree只是界面蓝图,提供界面展示的一些描述信息,Widget Tree中的每个节点多有一个与之对应的Element

Flutter架构初探

我们回顾上面的问题 看意思是通过Navigator.of(context)没有找到Navigator

static NavigatorState of(
  BuildContext context, {
  bool rootNavigator = false,
  bool nullOk = false,
}) {
  final NavigatorState navigator = rootNavigator
      ? context.findRootAncestorStateOfType<NavigatorState>()
      : context.findAncestorStateOfType<NavigatorState>();
  assert(() {
    if (navigator == null && !nullOk) {
      throw FlutterError(
        'Navigator operation requested with a context that does not include a Navigator.\n'
        'The context used to push or pop routes from the Navigator must be that of a '
        'widget that is a descendant of a Navigator widget.'
      );
    }
    return true;
  }());
  return navigator;
}

我们看到这个方法其实获取的是NavigatorState对象

  • findRootAncestorStateOfType找到rootState
  • findAncestorStateOfType找到距离自己最近的state

Flutter架构初探

此时基本可以确定是context出问题了,根据传入的context找不到NavigatorState

Flutter架构初探

知道原因之后很多的方法都能解决我们的问题 1、通过Key

Flutter架构初探

2、封装成子Widget

class BtnWidget extends StatelessWidget{

  @override
  Widget build(BuildContext context) {
    return FlatButton(
        onPressed: () {
          Navigator.of(context).push(
              MaterialPageRoute(builder: (context) => SecondPage()));
        },
        child: Text('跳转'));
  }
}

3、在上面的节点给他提供一个带Navigator的节点

void main() => runApp(MaterialApp(home: MyApp(),));

StatefulWidget组成的页面

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {

  @override
  State<StatefulWidget> createState() {
        return MyAppState();
  }
}

class MyAppState extends State<MyApp>{
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: FlatButton(
              onPressed: () {
                Navigator.of(context).push(
                    MaterialPageRoute(builder: (context) => SecondPage()));
              },
              child: Text('跳转')),
        ),
      ),
    );
  }
  
}

state是由StatefulElement触发StatefulWidget创建的 Flutter架构初探

StatefulWidget并不持有State相反,State持有widget Flutter架构初探

Flutter架构初探

再看一个小例子

void main() => runApp(Screen());

class Screen extends StatefulWidget {
  @override
  _ScreenState createState() => _ScreenState();
}

class _ScreenState extends State<Screen> {
  List<Widget> widgets = [
    StatelessContainer(),
    StatelessContainer(),
  ];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: widgets,
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: switchWidget,
          child: Icon(Icons.undo),
        ),
      ),
    );
  }

  switchWidget(){
    widgets.insert(0, widgets.removeAt(1));
    setState(() {});
  }
}

class StatelessContainer extends StatelessWidget {

  final Color color = Color.fromRGBO(Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1);
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      color: color,
    );
  }
}

运行正常

Flutter架构初探

但是如果把StatelessWidget改为StatefulWidget点击就没有响应了

class StatefulContainer extends StatefulWidget {
  
  @override
  State<StatefulWidget> createState() {
    return _StatefulContainerState();
  }
}

class _StatefulContainerState extends State<StatefulContainer> {
  final Color color = Color.fromRGBO(Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1);
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      color: color,
    );
  }
}

Widget # canUpdate

static bool canUpdate(Widget oldWidget, Widget newWidget) {
  return oldWidget.runtimeType == newWidget.runtimeType
      && oldWidget.key == newWidget.key;
}

Element通过持有的Widget来调用canUpdate方法,如果返回true那么只需要调整Widget就可以了,Element不需要重新创建,否则重新创建Element

Flutter架构初探

我们先分析StatelessWidget的情况

Flutter架构初探

两个WidgetruntimeType一致,所以返回true,只修改Widgets树就可以了,颜色信息存储在Widgets中,所以可以正常交换

分析StatefulWidget的情况

Flutter架构初探

问题在于Widget交换了之后对于显示没有任何影响,因为color数据是存储在State里面的,而此时state对象并没有重新创建,所以交换Widget并不能达到交换显示颜色的目的。

解决方案一
List<Widget> widgets = [
  Container(child: StatefulContainer(),,),
  StatefulContainer(),
];

将其中一个StatefulContainer包一层Container,此时runtimeType不同canUpdate返回false,也就是需要重新构建Element,通过上面的分析可以知道Element的构造函数会触发State的重新创建,当重新创建State的时候颜色信息color也重新生成了,所以最终效果并不是颜色交换,而是每一次点击都重新获取了一个新的颜色

Flutter架构初探

非但达不成我们的目的而且资源消耗也是很大的

解决方案二
List<Widget> widgets = [
  StatefulContainer(key: UniqueKey(),),
  StatefulContainer(key: UniqueKey(),),
];

此时runtimeType相同,但是key不同,所以和解决方案一相同,也会触发Element的重新构建,每一次点击都获取新的颜色信息

解决方案三

此时如果我们改成将color的获取放在Widget里面,State通过widget.color访问,会不会成功呢?

void main() => runApp(Screen());

class Screen extends StatefulWidget {
  @override
  _ScreenState createState() => _ScreenState();
}

class _ScreenState extends State<Screen> {
  List<Widget> widgets = [
    StatefulContainer(),
    StatefulContainer(),
  ];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: widgets,
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: switchWidget,
          child: Icon(Icons.undo),
        ),
      ),
    );
  }

  switchWidget(){
    widgets.insert(0, widgets.removeAt(1));
    setState(() {});
  }
}

class StatefulContainer extends StatefulWidget {
  final Color color = Color.fromRGBO(Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1);
  @override
  State<StatefulWidget> createState() {
    return _StatefulContainerState();
  }
}

class _StatefulContainerState extends State<StatefulContainer> {

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100,
      height: 100,
      color: widget.color,
    );
  }
}

借用上图演示一下😄

此时runtimeTypekey都相同(key为空就只比较runtimeType),所以canUpdate返回trueElementState不需要重新创建,只是复用Widget就可以了,颜色信息color也是存储在Widget中的,所以可以达成我们的目的。

第三棵树 RenderObject树涉及到页面渲染内容比较多,我还没研究明白,大概意思是

  • 先创建出Widget Tree
  • 再通过WidgetcreateElement创建出Element Tree并且和Widget Tree关联起来
  • 通过createRenderObject方法创建出renderObject

真正负责渲染页面的是RenderObjectElement会持有WidgetStateRenderObject

Flutter架构初探

参考文章

# How to Create Stateless Widgets-中文字幕

# How Stateful Widgets Are Used Best-中文字幕

# Flutter | 深入理解BuildContext

# Flutter | 深入浅出Key

# Flutter: WidgetTree、ElementTree

# flutter 渲染三棵树(Widget、Element、RenderObject)

深入了解Flutter界面开发

转载自:https://juejin.cn/post/7008083241978560542
评论
请登录