Flutter开发 12.Widget-StatelessWidget和StatefulWidget

16 篇文章 0 订阅

Flutter开发 12.Widget-StatelessWidget和StatefulWidget

1. Widget绘制原理

在开始之前有必要先了解一下组件显到屏幕上的流程,flutter会生成三棵组件的树型结构,其中有一颗就是渲染树, 前几节课中讲到的Widget组件例如:Text、Icon、GridView等,全是直接或者间接的继承自StatelessWidget,也就是无状态组件,然后StatelessWIdget继随自Widget类,并且重写了createElement方法。

abstract class StatelessWidget extends Widget {
......
  @override
  StatelessElement createElement() => StatelessElement(this);
......  
  }
abstract class StatefulWidget extends Widget {
......
  @override
  StatefulElement createElement() => StatefulElement(this);
......  
}
class StatelessElement extends ComponentElement 
class StatefulElement extends ComponentElement 

那么这个方法将自身的实例对像做为参数,传入创建了一个ComponentElement实例. 也就是说,当我们实现一个widget组合结构时,也会有一个对应的Element组合结构. 此处引用了官方给的一个例子

Container( // 一个容器 widget
  color: Colors.blue, // 设置容器背景色
  child: Row( // 可以将子widget沿水平方向排列
    children: [
      Image.network('https://www.example.com/1.png'), // 显示图片的 widget
      const Text('A'),
    ],
  ),
);

这段代码就对应了一个Widgets的树型结构
在这里插入图片描述
因为每个widget还会创建一个element,因此同样会产生一个Element的树形结构
在这里插入图片描述
那么最终的渲染树的节点分别都是如何得到的呢? ??
我们先看一下 Icon和Text组件的 Build方法
Icon

Widget iconWidget = RichText(
      overflow: TextOverflow.visible, // Never clip.
      textDirection: textDirection, // Since we already fetched it for the assert...
      text: TextSpan(
        text: String.fromCharCode(icon!.codePoint),
        style: TextStyle(
          inherit: false,
          color: iconColor,
          fontSize: iconSize,
          fontFamily: icon!.fontFamily,
          package: icon!.fontPackage,
        ),),);

Text

  Widget result = RichText(
      textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start,
      textDirection: textDirection, // RichText uses Directionality.of to obtain a default if this is null.
      locale: locale, // RichText uses Localizations.localeOf to obtain a default if this is null
      softWrap: softWrap ?? defaultTextStyle.softWrap,
      overflow: overflow ?? effectiveTextStyle?.overflow ?? defaultTextStyle.overflow,
      textScaleFactor: textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
      maxLines: maxLines ?? defaultTextStyle.maxLines,
      strutStyle: strutStyle,
      textWidthBasis: textWidthBasis ?? defaultTextStyle.textWidthBasis,
      textHeightBehavior: textHeightBehavior ?? defaultTextStyle.textHeightBehavior ?? DefaultTextHeightBehavior.of(context),
      text: TextSpan(
        style: effectiveTextStyle,
        text: data,
        children: textSpan != null ? <InlineSpan>[textSpan!] : null,),);

通过上面的源码可以看到,这两个组件,都是使用了 RichText来创建的Widget对像。
跳转到RichText 的源码,可以找到有一个createRenderObject方法

class RichText extends MultiChildRenderObjectWidget {
  ......
 @override
 RenderParagraph createRenderObject(BuildContext context) {
   assert(textDirection != null || debugCheckHasDirectionality(context));
   return RenderParagraph(text,
     textAlign: textAlign,
     textDirection: textDirection ?? Directionality.of(context),
     softWrap: softWrap,
     overflow: overflow,
     textScaleFactor: textScaleFactor,
     maxLines: maxLines,
     strutStyle: strutStyle,
     textWidthBasis: textWidthBasis,
     textHeightBehavior: textHeightBehavior,
     locale: locale ?? Localizations.maybeLocaleOf(context),
   );
 }

接下来再看一下Container的build方法

class Container extends StatelessWidget {
......
 @override
 Widget build(BuildContext context) {
   Widget? current = child;

   if (child == null && (constraints == null || !constraints!.isTight)) {
     current = LimitedBox(
       maxWidth: 0.0,
       maxHeight: 0.0,
       child: ConstrainedBox(constraints: const BoxConstraints.expand()),
     );
   }
   if (alignment != null)
     current = Align(alignment: alignment!, child: current);
     
   final EdgeInsetsGeometry? effectivePadding = _paddingIncludingDecoration;
   if (effectivePadding != null)
     current = Padding(padding: effectivePadding, child: current);

   if (color != null)
     current = ColoredBox(color: color!, child: current);

   if (clipBehavior != Clip.none) {
     assert(decoration != null);
     current = ClipPath(
       clipper: _DecorationClipper(
         textDirection: Directionality.maybeOf(context),
         decoration: decoration!,
       ),
       clipBehavior: clipBehavior,
       child: current,
     );
   }

   if (decoration != null)
     current = DecoratedBox(decoration: decoration!, child: current);

   if (foregroundDecoration != null) {
     current = DecoratedBox(
       decoration: foregroundDecoration!,
       position: DecorationPosition.foreground,
       child: current,
     );
   }

   if (constraints != null)
     current = ConstrainedBox(constraints: constraints!, child: current);

   if (margin != null)
     current = Padding(padding: margin!, child: current);

   if (transform != null)
     current = Transform(transform: transform!, alignment: transformAlignment, child: current);
   return current!;
 }

可以看到最终return的这个组件具体类型的可能性比较多,有可能是LimitedBox、Padding、ColoredBox、ClipPath、DecoratedBox、ConstrainedBox、Transform,那么我们同样需要看一下,这些组件中是否也有创建renderObject的方法呢

class LimitedBox extends SingleChildRenderObjectWidget {
......
@override
 RenderLimitedBox createRenderObject(BuildContext context) {
   return RenderLimitedBox(
     maxWidth: maxWidth,
     maxHeight: maxHeight,
   );
 }
}
class Padding extends SingleChildRenderObjectWidget {
......
 @override
 RenderPadding createRenderObject(BuildContext context) {
   return RenderPadding(
     padding: padding,
     textDirection: Directionality.maybeOf(context),
   );
  }
}
class ColoredBox extends SingleChildRenderObjectWidget {
......
 @override
 RenderObject createRenderObject(BuildContext context) {
   return _RenderColoredBox(color: color);
 }
}
class DecoratedBox extends SingleChildRenderObjectWidget {
......
 @override
 RenderDecoratedBox createRenderObject(BuildContext context) {
   return RenderDecoratedBox(
     decoration: decoration,
     position: position,
     configuration: createLocalImageConfiguration(context),
   );
 }
}
class ClipPath extends SingleChildRenderObjectWidget {
......
 @override
 RenderClipPath createRenderObject(BuildContext context) {
   assert(clipBehavior != Clip.none);
   return RenderClipPath(clipper: clipper, clipBehavior: clipBehavior);
 }
}

上面列举出来的这些源可以看到, 全部都包含了 createRenderObject ,这个方法就是生成渲染节点,那么最只需要有一个时机来调用创建得到这个对象就可以了。
我们先看一下ComponentElement的源码:

abstract class ComponentElement extends Element {
 @override
 void mount(Element? parent, Object? newSlot) {
   super.mount(parent, newSlot);
   assert(_child == null);
   assert(_lifecycleState == _ElementLifecycle.active);
   _firstBuild();
   assert(_child != null);
 }
}

我们只看一下这个挂载方法(mount) 他里边除了调用了父类的方法外,就只执行了_firstBuild(); 我们根据代码逻辑可以得到调用关系如下:
_firstBuild() -> rebuild() -> performRebuild -> build()
接下来我们看下这个build()方法的实现

class StatelessElement extends ComponentElement {
......
 @override
 Widget build() => widget.build(this); //将自身做为参数,传递给widget.build方法
 }
}
class StatefulElement extends ComponentElement {
......
@override
 Widget build() => state.build(this); //将自身做为参数,传递给widget.build方法
}

因为StatefulWidget的对像,是在state中实现的build方法(后面会详细说明), 因此Element在调用上做了区分。

abstract class Element extends DiagnosticableTree implements BuildContext {}
@override
 Widget build(BuildContext context) { //这个context就是element
 ......
 }

这样我们就清楚的知道了,我们使用组件中的Build方法是在哪里调用的了。并且我们也清楚的知道这个参数就是Element,因为Element实现BuildContext, 但是我们依然没有找到在哪里调用的createRenderObject.
因为我们已经找到了widget与element的一对一的关系,接下来我们看一下上面提到的Text和Icon的具体实现类RichText,先看一下RichText的继承关系。

class RichText extends MultiChildRenderObjectWidget {}
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
......
@override
 MultiChildRenderObjectElement createElement() => MultiChildRenderObjectElement(this);
}

可以看到这里边创建的Element对像实际是一个MultiChildRenderObjectElement对像。

class MultiChildRenderObjectElement extends RenderObjectElement 
abstract class RenderObjectElement extends Element {
......
@override
 void mount(Element? parent, Object? newSlot) {
  ......
   _renderObject = widget.createRenderObject(this);
 ......
 }
}

这里就是我们要找的位置了,RenderObjectElement中的mount方法中调用了widget.createRenderObject,而
ComponentElement中的mount调用的方法是build.因此渲染树与element树的数量并不是一一对应的.
在这里插入图片描述

2. StatelessWidget

之前我们用过的很多组件都是继承自StatelessWidget,像Container、Text、 StatelessWidget相对比较简单,它继承自widget类,重写了createElement()方法,StatelessWidget用于不需要维护状态的场景,它通常在build方法中通过嵌套其它 widget 来构建UI,在构建过程中会递归的构建其嵌套的 widget 。
无状态组件用于没有变化的情况,比如一个应用的关于页面:

Text spawnRegularText(String stringText) {
  return Text(stringText, //文本内容
      //文本方向 如果需要使用rtl需要add flutter_localizations package to your pubspec.yml
      textDirection: TextDirection.ltr,
      textAlign: TextAlign.center, //对齐方式
      overflow: TextOverflow.ellipsis, //超长后截断方式 ...
      maxLines: 1, //最大行数
      textScaleFactor: 1.0, //放大缩小比例
      style: const TextStyle(
        // fontFamily: "zzFontFamily", //所用的字体、这里用的是自定义的字体
        fontStyle: FontStyle.normal, //文本显示样式
        // fontWeight: FontWeight.bold, //粗体
        letterSpacing: 1, //字母之间的间距
        wordSpacing: 2, //单词之间的间距
        fontSize: 24, //字体大小
        color: Color.fromARGB(255, 174, 0, 255), // 字体颜色
      ));
}

class ZzAboutWidget extends StatelessWidget {
  const ZzAboutWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [
          ClipRRect(
            borderRadius: BorderRadius.circular(30),
            child: const Image(
                image: NetworkImage(
                  "https://img-blog.csdnimg.cn/20190520151631821.jpeg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3h1YW53ZW5jaGFv,size_16,color_FFFFFF,t_70",
                ),
                width: 120,
                height: 120,
                fit: BoxFit.fill),
          ),
          Padding(
            padding: const EdgeInsets.fromLTRB(0, 20, 0, 30),
            child: spawnRegularText("爪爪课堂 v1.0"),
          ),
          const Text("blog.csdn.net/xuanwenchao",
              style: TextStyle(
                fontSize: 15, //字体大小
                color: Color.fromARGB(255, 37, 58, 252), // 字体颜色
              ))
        ],
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(
      home: Scaffold(
          appBar: AppBar(
            title: const Text("关于页面"),
          ),
          body: const ZzAboutWidget())));
}

运行一下效果如下:
在这里插入图片描述

3.StatefulWidget

有状态组件也就是说数据会变化,也是使用场景最多的组件,StatefulWidget也是继承自widget类,并重写了createElement()方法,不同的是返回的Element 对象并不相同;另外StatefulWidget类中添加了一个新的接口createState()。

abstract class StatefulWidget extends Widget {
  const StatefulWidget({ Key key }) : super(key: key);
  ......
  @protected
  State createState();
}

StatefulWidget本身是不提供build方法的,而是通过createState() 创建和 StatefulWidget 相关的状态类,它在StatefulWidget 的生命周期中可能会被多次调用。例如,当一个 StatefulWidget 同时插入到 widget 树的多个位置时,Flutter 框架就会调用该方法为每一个位置生成一个独立的State实例,其实,本质上就是一个StatefulElement对应一个State实例。当State被改变时,可以手动调用其setState()方法通知Flutter 框架状态发生改变,Flutter 框架在收到消息后,会重新调用其build方法重新构建 widget 树,从而达到更新UI的目的。

接下来我们使用一个实例来演示, 首先新建一个CounterWidget.dart文件:

import 'package:flutter/material.dart';

//CounterWidget
class CounterWidget extends StatefulWidget {
  //在这个构函数参数中,使用"{}"包围的参数属于可选命名参数
  const CounterWidget({Key? key, this.initCounterValue = 0}) : super(key: key);

  final int initCounterValue; //计数器的初始值

  @override
  State<CounterWidget> createState() => _CounterWidgetState();
}

//CounterWidget对应的state类
class _CounterWidgetState extends State<CounterWidget> {
  int _counter = 0;

  @override
  void initState() {
    _counter = widget.initCounterValue; //读取组件类中计数器的初始值
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.fromLTRB(0, 50, 0, 0),
      width: double.infinity,
      child: TextButton(
          child: Text(
            "$_counter",
            style: const TextStyle(fontSize: 28, color: Colors.purple),
          ),
          onPressed: () => setState(() {
                _counter++;
              })),
    );
  }
}

就像其它的自定义组件一样,我们也继承自StatefulWidget并且创建对应的state类。
然后在main.dart中去使用这个组件.

import 'CounterWidget.dart';
void main() {
  runApp(MaterialApp(
      home: Scaffold(
          appBar: AppBar(
            title: const Text("Counter页面"),
          ),
          body: const CounterWidget(
            initCounterValue: 0,
          ))));
}

运行一下就可以看到效果,点击一次数字就会加1
在这里插入图片描述
这里有一个比较重要的方法叫 setState需要说明一下,首选看一下我们自己实现的State类的原型定义:

abstract class State<T extends StatefulWidget> with Diagnosticable {
......//省略其它代码
 @protected
  void setState(VoidCallback fn) {
    ......//省略其它代码
    final Object? result = fn() as dynamic;//动态调用了传入的函数VoidCallback
    ......//省略其它代码
    _element!.markNeedsBuild();
  }
}

这里以以看到,他调用了element的markNeedsBuild方法,用来标记需要构建。

 void markNeedsBuild() {
    assert(_lifecycleState != _ElementLifecycle.defunct);
    if (_lifecycleState != _ElementLifecycle.active)
      return;
    ......//省略其它代码
    if (dirty)
      return;
    _dirty = true;
    owner!.scheduleBuildFor(this);
  }

将当前 element 元素标记为“脏数据”(也就是需要更新的意思),scheduleBuildFor方法会启动onBuildScheduled,并且将element添加到_dirtyElements链表里,以便在下一帧更新信号时更新.

 void scheduleBuildFor(Element element) {
    ......//省略其它代码
    if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
      _scheduledFlushDirtyElements = true;
      onBuildScheduled!();
    }
    _dirtyElements.add(element);
    element._inDirtyList = true;
    ......//省略其它代码
  }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xuanwenchao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值