前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter之旅:认识Widget(源码级)

Flutter之旅:认识Widget(源码级)

作者头像
张风捷特烈
发布2020-04-30 15:28:12
1.3K0
发布2020-04-30 15:28:12
举报
文章被收录于专栏:Android知识点总结
1.Widget的第一印象
1.1:初次的见面

首先我们来到第一次看到Widget类的场景,那时还对这个世界一无所知, 进入程序的入口时runApp函数中需要传入一个Widget对象,这便是第一眼。 初始项目中的做法是自定义了一个MyApp类继承自StatelessWidget。

代码语言:javascript
复制
void main()=>runApp(MyApp());

---->[flutter/lib/src/widgets/binding.dart:778]----
void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..attachRootWidget(app)
    ..scheduleWarmUpFrame();
}

class MyApp extends StatelessWidget {
    //略...
}

1.2:Widget在源码中的位置

位置:flutterSDK/packages/flutter/lib/src/widgets/framework.dart:369 首先,它在framework包中,可以说至关重要。其次它继承自DiagnosticableTree 下图可见Widget类在Flutter的框架层中是比较顶尖的类。

你之后就会知道,Widget是Flutter界面的中心,可显示在页面上的一切,都和Widget相关。


1.3:Widget类的构成

首先,Widget是一个抽象类,拥有一个createElement()的抽象方法返回一个Element对象。 其次,Widget类本身只有一个字段、一个构造方法、一个抽象方法、一个静态方法和两个普通方法。

代码语言:javascript
复制
abstract class Widget extends DiagnosticableTree {
  const Widget({ this.key });
  final Key key;
  @protected
  Element createElement();

  @override
  String toStringShort() {
    return key == null ? '$runtimeType' : '$runtimeType-$key';
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }

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

关于Widget的源码,这里暂时不做解读,随着了解的深入,再去看源码,效果会好很,现在火候还未到,有个大概印象就行了。


2.Widget的状态
2.1:Widget的状态概述

在Widget源码中明显指出了关于Widget状态的问题:

代码语言:javascript
复制
/// Widgets themselves have no mutable state (all their fields must be final).
/// If you wish to associate mutable state with a widget, consider using a
/// [StatefulWidget], which creates a [State] object (via
/// [StatefulWidget.createState]) whenever it is inflated into an element and
/// incorporated into the tree.
    Widget的本身没有可变状态(所有字段都必须是final)。
    如果您希望将一个widget拥有可变状态,请考虑使用 StatefulWidget,
    每当它被加载为元素并合并到渲染树中时,会创建State对象(通过 StatefulWidget.createState)。

对StatefulWidget和StatelessWidget也做了简要的描述

代码语言:javascript
复制
///  * [StatefulWidget] and [State], for widgets that can build differently
///    several times over their lifetime.
///  * [StatelessWidget], for widgets that always build the same way given a
///    particular configuration and ambient state.
    StatefulWidget和State,用于可以在其生命周期内多次构建的widget。
    StatelessWidget,用于在给定配置和环境的状态的下始终以相同方式构建的widget。

2.2: StatelessWidget 无状态组件

该类的本身非常简洁,由于Widget有一个createElement抽象方法, StatelessWidget类中通过StatelessElement对象完成了该抽象方法, 所以StatelessWidget只需要关注build这个抽象方法即可。

代码语言:javascript
复制
abstract class StatelessWidget extends Widget {
  const StatelessWidget({ Key key }) : super(key: key);
  
  @override
  StatelessElement createElement() => StatelessElement(this);

  @protected
  Widget build(BuildContext context);

如初始项目中,MyApp是继承了StatelessWidget,它的任务在于重写build方法。

代码语言:javascript
复制
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

2.3:StatefulWidget有状态组件

该类本身也比较简单,继承自Widget,createElement方法通过StatefulElement实现 所以该类需要注意的只有抽象方法createState(),负责返回一个State状态对象

代码语言:javascript
复制
abstract class StatefulWidget extends Widget {
  const StatefulWidget({ Key key }) : super(key: key);

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

  @protected
  State createState();
}

初始代码也是用心良苦,为我们准备了一个简单的有状态组件MyHomePage 可以看到,该类的核心是createState方法,返回一个自定义的_MyHomePageState对象

代码语言:javascript
复制
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

2.4:State 对象

比较引人注目的就是State对象中有一个泛型,从源码中来看, 该泛型值接受StatefulWidget,即有状态组件类。 State作为一个抽象类,存在一个build抽象方法来返回一个Widget对象

代码语言:javascript
复制
abstract class State<T extends StatefulWidget> extends Diagnosticable {
   //略...
   @protected
  Widget build(BuildContext context);
}

初始代码中也为我们做了示例: 这里将_counter作为可变状态,通过点按钮改变状态,再通过setState重新渲染, 执行build方法,从而达到了点击按钮数字增加的一个组件,

代码语言:javascript
复制
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
     //略...
    );
  }
}
复制代码

这里需要注意的一点是State类中的widget属性到底是什么,这里通过debug可以看出,就是传入的泛型类, 至于如何widget属性何时赋值以及渲染的,先别急,还有一段很长的路要走。

现在回头看一下,你是否已经对曾经陌生无比的初始项目有了突然熟悉了许多。 蓦然回首,希望这会成为你在一段旅行中美丽的一瞬,也是我这个导游的成功。


3.从Icon源码看StatelessWidget组件

趁人打铁,为了让大家对Widget有更好的理解,这里挑选了两个Widget。 通过源码赏析一下:一个Widget是如何构成的。第一个是无状态家族的Icon组件

3.1:Icon组件的使用

Icon主要有三个属性,分别控制图标,颜色,大小

代码语言:javascript
复制
Icon(
  Icons.local_shipping,
  color: Colors.pink,
  size: 30.0,
)

3.2:Icon源码

从源码中可以看出,Icon类中主要做了四件事: 构造函数--> 声明属性字段--> 实现build方法,返回Widget对象-->debugFillProperties

代码语言:javascript
复制
class Icon extends StatelessWidget {
  const Icon(
    this.icon, {
    Key key,
    this.size,
    this.color,
    this.semanticLabel,
    this.textDirection,
  }) : super(key: key);

  final IconData icon;
  final double size;
  final Color color;
  final String semanticLabel;
  final TextDirection textDirection;

  @override
  Widget build(BuildContext context) {
    //暂略...
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    //暂略...
  }
}

可以看出,构造函数中有一个必须的参数icon,从定义中来看是一个IconData对象 注意:构造函数用const关键字修饰,字段全被修饰为final,这就意味着字段不可再修改。 这也是被称为无状态的原因,一旦对象构建完成,它就样子就无法再被改变。


3.3:build方法

build方法作为StatelessWidget的抽象方法,子类必须去实现 这个方法也将决定一个Widget在界面上的样子,所以它至关重要 从源码中可以看出Icon主要是通过RichText来实现的,核心是text属性

代码语言:javascript
复制
@override
Widget build(BuildContext context) {
    //略...
  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,
      ),
    ),
  );
  if (icon.matchTextDirection) {//做文本方向的处理
    switch (textDirection) {
      case TextDirection.rtl://文本方向从右到左
        iconWidget = Transform(
          transform: Matrix4.identity()..scale(-1.0, 1.0, 1.0),
          alignment: Alignment.center,
          transformHitTests: false,
          child: iconWidget,
        );
        break;
      case TextDirection.ltr://文本方向从左到右
        break;
    }
  }
    // 暂略...
}

3.4:文字图标的实现

映入眼帘的是String.fromCharCode()方法,它接受一个int值 这个int值是由IconData对象的codePoint属性提供的,为了方便开发, Flutter框架给了很多Icon静态常量,当然你也可以使用自定义的图标。

代码语言:javascript
复制
---->[flutter/bin/cache/pkg/sky_engine/lib/core/string.dart:134]----
external factory String.fromCharCode(int charCode);

----[flutter/lib/src/widgets/icon_data.dart:22]----
  const IconData(
    this.codePoint, {
    this.fontFamily,
    this.fontPackage,
    this.matchTextDirection = false,
  });

  /// The Unicode code point at which this icon is stored in the icon font.
  final int codePoint;
  
----[flutter/lib/src/widgets/icon_data.dart:22]----
static const IconData local_shipping = IconData(0xe558, fontFamily: 'MaterialIcons');

一个图标实际上就是一个字体,可以根据一个int值和字库名匹配到它。 所以图标才支持变色和改变大小等方便的功能。


3.5:关于Semantics类

还有一点不知你是否注意,最后返回的是一个包裹了iconWidget的Semantics对象 字面上来看,它是语义化的意思,那他有什么用处呢?

代码语言:javascript
复制
return Semantics(
  label: semanticLabel,
  child: ExcludeSemantics(
    child: SizedBox(
      width: iconSize,
      height: iconSize,
      child: Center(
        child: iconWidget,
      ),
    ),
  ),
);

Flutter中的MaterialApp有一个showSemanticsDebugger的属性可以用来查看语义化界面

代码语言:javascript
复制
---->[main.dart:3]----
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {

    var icon = Icon(
      Icons.local_shipping,
      color: Colors.pink,
      size: 40.0,
      semanticLabel: "一个货车图标",
    );
    
    var scaffold=Scaffold(
      appBar: AppBar(
        title: Text('Flutter Demo Home Page'),
      ),
      body:icon, 
    );

    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      showSemanticsDebugger: true,
      home: scaffold,
    );
  }
}

好了,这样就是对于一个简单的无状态组件构成的简要介绍。


4.从Checkbox看StatefulWidget组件
4.1:CheckBox的使用

有状态组件很好理解,首先它有一个允许改变的状态量,不如Checkbox就是选中与否 下面的测试代码实现了,点击切换Checkbox选中或未选中的状态

代码语言:javascript
复制
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    var scaffold=Scaffold(
      appBar: AppBar(
        title: Text('Flutter Demo Home Page'),
      ),
      body:CheckBoxWidget(), 
    );

    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: scaffold,
    );
  }
}

class CheckBoxWidget extends StatefulWidget {
  @override
  _CheckBoxWidgetState createState() => _CheckBoxWidgetState();
}

class _CheckBoxWidgetState extends State<CheckBoxWidget> {
  bool _checked=true;//维护CheckBox框状态
  @override
  Widget build(BuildContext context) {
    return
        Checkbox(
          value: _checked,
          activeColor: Colors.blue, //选中时的颜色
          onChanged:(value){
            setState(() {
              _checked=value;
            });
          } ,
    );
  }
}

4.2:CheckBox源码简析

下面是Checkbox的代码,继承自StatefulWidget,所以需要实现createState方法 这时,源码中使用自定义的_CheckboxState类来管理状态。

代码语言:javascript
复制
class Checkbox extends StatefulWidget {
  const Checkbox({
    Key key,
    @required this.value,
    this.tristate = false,
    @required this.onChanged,
    this.activeColor,
    this.checkColor,
    this.materialTapTargetSize,
  }) : assert(tristate != null),
       assert(tristate || value != null),
       super(key: key);

  final bool value;//是否选中
  final ValueChanged<bool> onChanged;//点击回调
  final Color activeColor;//激活态框颜色
  final Color checkColor;//激活态对勾颜色
  final bool tristate;//三态
  final MaterialTapTargetSize materialTapTargetSize;
  static const double width = 18.0;

  @override
  _CheckboxState createState() => _CheckboxState();
}

通过这两个组件源码,可以总结出一些风格特点:

代码语言:javascript
复制
1.构造函数用const修饰,每行写一个属性  
2.必须的属性用@required注解  
3.非空的属性用assert断言
4.字段全是final类型

_CheckboxState中的build方法返回_CheckboxRenderObjectWidget对象 CheckBox具体绘制逻辑及状态改变,在_RenderCheckbox中实现

代码语言:javascript
复制
---->[flutter/packages/flutter/lib/src/material/checkbox.dart:140]----
class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
  @override
  Widget build(BuildContext context) {
        //略...
    }
    return _CheckboxRenderObjectWidget(
      //略...
    );
  }
}

---->[flutter/packages/flutter/lib/src/material/checkbox.dart:168]----
class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
    //略...
  @override
  _RenderCheckbox createRenderObject(BuildContext context) => _RenderCheckbox(
    //略...
  );

  @override
  void updateRenderObject(BuildContext context, _RenderCheckbox renderObject) {
    //略...
  }

4.3:Checkbox核心绘制方法

_RenderCheckbox继承自RenderToggleable,可以重写paint方法 这边简单看一下主要的边框和对勾的绘制方法

代码语言:javascript
复制
 // 可以看出画笔的颜色是checkColor,以线条的形式
 void _initStrokePaint(Paint paint) {
   paint
     ..color = checkColor
     ..style = PaintingStyle.stroke
     ..strokeWidth = _kStrokeWidth;
 }
 
//绘制边线
void _drawBorder(Canvas canvas, RRect outer, double t, Paint paint) {
  assert(t >= 0.0 && t <= 0.5);
  final double size = outer.width;
  // 当t从0.0到1.0时,逐渐填充外部矩形。
  final RRect inner = outer.deflate(math.min(size / 2.0, _kStrokeWidth + size * t));
  canvas.drawDRRect(outer, inner, paint);
}

//绘制对勾
void _drawCheck(Canvas canvas, Offset origin, double t, Paint paint) {
  assert(t >= 0.0 && t <= 1.0);
  final Path path = Path();
  const Offset start = Offset(_kEdgeSize * 0.15, _kEdgeSize * 0.45);//起始偏移点
  const Offset mid = Offset(_kEdgeSize * 0.4, _kEdgeSize * 0.7);//中间偏移点
  const Offset end = Offset(_kEdgeSize * 0.85, _kEdgeSize * 0.25);//终止偏移点
  if (t < 0.5) {//t<0.5时,绘制短边
    final double strokeT = t * 2.0;
    final Offset drawMid = Offset.lerp(start, mid, strokeT);
    path.moveTo(origin.dx + start.dx, origin.dy + start.dy);
    path.lineTo(origin.dx + drawMid.dx, origin.dy + drawMid.dy);
  } else {//t>0.5时,绘制长边
    final double strokeT = (t - 0.5) * 2.0;
    final Offset drawEnd = Offset.lerp(mid, end, strokeT);
    path.moveTo(origin.dx + start.dx, origin.dy + start.dy);
    path.lineTo(origin.dx + mid.dx, origin.dy + mid.dy);
    path.lineTo(origin.dx + drawEnd.dx, origin.dy + drawEnd.dy);
  }
  canvas.drawPath(path, paint);
}

这样你一个StatefulWidget组件从定义到状态,到绘制的流程应该有所了解

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019年07月08日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.Widget的第一印象
    • 1.1:初次的见面
      • 1.2:Widget在源码中的位置
        • 1.3:Widget类的构成
        • 2.Widget的状态
          • 2.1:Widget的状态概述
            • 2.2: StatelessWidget 无状态组件
              • 2.3:StatefulWidget有状态组件
                • 2.4:State 对象
                • 3.从Icon源码看StatelessWidget组件
                  • 3.1:Icon组件的使用
                    • 3.2:Icon源码
                      • 3.3:build方法
                        • 3.4:文字图标的实现
                          • 3.5:关于Semantics类
                          • 4.从Checkbox看StatefulWidget组件
                            • 4.1:CheckBox的使用
                              • 4.2:CheckBox源码简析
                                • 4.3:Checkbox核心绘制方法
                                领券
                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档