1. First impressions of widgets

1.1: First meeting

Let’s start with the first time we saw the Widget class, when we knew nothing about the world,

As a first look, you need to pass in a Widget object in the runApp function to enter the program entry. The approach in the initial project is to customize a MyApp class that inherits from the StatelessWidget.

void main()=>runApp(MyApp()); ---->[flutter/lib/src/widgets/binding.dart:778]---- void runApp(Widget app) { WidgetsFlutterBinding.ensureInitialized() . attachRootWidget(app) .. scheduleWarmUpFrame(); } class MyApp extends StatelessWidget {//... }Copy the code

1.2:Widget location in the source code

Location: flutterSDK/packages/flutter/lib/SRC/widgets/framework. The dart: 369

First, it is in the Framework package, so to speak, crucial. Second, it inherited from DiagnosticableTree. As shown in the figure below, the Widget class is the top class in the frame layer of Flutter.

You will learn later that the Widget is at the center of the Flutter interface, and everything that can be displayed on the page is related to the Widget.


1.3: Widget class composition

First, a Widget is an abstract class that has an abstract method createElement() that returns an Element object.

Second, the Widget class itself has only one field, one constructor, one abstract method, one static method, and two ordinary methods.

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; }}Copy the code

As for the source code of the Widget, I will not read it here for the time being. As I learn more about it, I will look at the source code again.


2. The state of the Widget

2.1: Status overview of widgets

There are obvious issues with Widget status in the Widget source code:

/// 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 /// Widget has no mutable state of its own (all fields must be final). If you want to use a widget has a variable State, please consider using StatefulWidget, whenever it is loaded and merge for the element to render tree, will create the State object (by StatefulWidget. CreateState).Copy the code

StatefulWidget and StatelessWidget are also briefly described

/// * [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. For widgets that can be built many times over their lifetime. StatelessWidget, a widget that always builds the same way given the configuration and state of the environment.Copy the code

2.2: StatelessWidget stateless component

The class itself is pretty neat, and since the Widget has a createElement abstract method,

The StatelessWidget class implements the abstract method through the StatelessElement object, so the StatelessWidget only needs to focus on building the abstract method.

abstract class StatelessWidget extends Widget {
  const StatelessWidget({ Key key }) : super(key: key);
  
  @override
  StatelessElement createElement() => StatelessElement(this);

  @protected
  Widget build(BuildContext context);
Copy the code

As in the original project,MyApp inherits the StatelessWidget and its job is to override the build method.

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'), ); }}Copy the code

2.3:StatefulWidget Stateful component

The class itself is relatively simple, inheriting from the Widget, and the createElement method is implemented through StatefulElement

So all that matters to this class is the abstract method createState(), which returns a State object

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

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

  @protected
  State createState();
}
Copy the code

The initial code was also well-intentioned, giving us a simple stateful component called MyHomePage

As you can see, the core of this class is the createState method, which returns a custom _MyHomePageState object

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}
Copy the code

2.4: the State object

What’s interesting is that there’s a generic in the State object, and from the source code,

This generic value accepts the StatefulWidget, or stateful component class. State is an abstract class, and there is a build abstract method that returns a Widget object

Abstract class State<T extends StatefulWidget> extends Diagnosticable {//... @protected Widget build(BuildContext context); }Copy the code

The initial code also provides an example for us:

Here, _counter is taken as the variable state, and the state is changed by clicking the button, and then the setState is re-rendered, and the build method is executed, so as to reach a component that is increased by clicking the button number.

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),), // ; }}Copy the code

Note that the widget property in the State class is the same as that in the generic class.

As for how and when widget properties are assigned and rendered, there’s still a long way to go.

Now look back and see if you are suddenly familiar with the original project that was once completely alien.

Looking back, I hope it will be a beautiful moment in your journey and a success for me as a tour guide.


3. View StatelessWidget components from Icon source code

To give you a better understanding of widgets, here are two of them.

Take a look at how a Widget is made. The first is the Icon component of the stateless family

3.1: Use of Icon components

The main properties of Icon are Icon, color and size

Icon(Icons. Local_shipping, color: colors.pink, size: 30.0,)Copy the code

3.2: Icon source code

From the source code, we can see that the Icon class does four main things:

Constructor –> Declare property field –> implement build method, return Widget object –>debugFillProperties

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 Widget build(BuildContext context) {override Widget build(BuildContext context); } @ override void debugFillProperties (DiagnosticPropertiesBuilder properties) {/ / temporary slightly... }}Copy the code

As you can see, the constructor has a required parameter, icon, which by definition is an IconData object

Note that the constructor is decorated with the const keyword, and the fields are all modified to final, which means that the fields cannot be modified. That’s why it’s called stateless, because once an object is built, it looks like it can’t be changed.


3.3: Build method

The Build method is the abstract method of the StatelessWidget that subclasses must implement

This method also determines what a Widget looks like on the interface, so it is crucial. As you can see from the source code, the Icon is mainly implemented through RichText, with the text property at its core

Override Widget build(BuildContext context) {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),// Text: TextStyle(inherit: false, color: inherit) iconColor, fontSize: iconSize, fontFamily: icon.fontFamily, package: icon.fontPackage, ), ), ); If (icon.matchtextdirection) {switch (textDirection) {case textdirection. RTL :// textDirection from right to left iconWidget = Transform( transform: Matrix4.identity().. Scale (-1.0, 1.0, 1.0), alignment: align.center, transformHitTests: false, Child: iconWidget,); break; Case textdirection. LTR :// TextDirection from left to right break; }} // For now... }Copy the code

3.4: text icon implementation

What catches your eye is the string.fromCharcode () method, which takes an int

This int value is provided by the codePoint property of the IconData object. To facilitate development, the Flutter framework gives many ICONS static constants. You can also use custom ICONS.

---->[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');Copy the code

An icon is actually a font that can be matched based on an int value and a font name.

That’s why ICONS can change color and resize easily.


3.5: About the Semantics class

One other thing I don’t know if you’ve noticed is that what is returned is a Semantics object that wraps the iconWidget

Literally, it means semantic, so what’s the use of it?

return Semantics(
  label: semanticLabel,
  child: ExcludeSemantics(
    child: SizedBox(
      width: iconSize,
      height: iconSize,
      child: Center(
        child: iconWidget,
      ),
    ),
  ),
);
Copy the code

The MaterialApp in Flutter has a showSemanticsDebugger property that can be used to view semantic interfaces

---->[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: "A van icon ",); 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, ); }}Copy the code

Ok, so that’s a brief introduction to the composition of a simple stateless component.


4. View StatefulWidget components from the Checkbox

4.1: Use of CheckBox

A stateful component is easy to understand. First, it has an amount of state that can be changed, like Checkbox

The following test code toggles Checkbox checked or unchecked

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; @override Widget build(BuildContext context) {return CheckBox (value: _checked, activeColor: Color.blue, // the selected color onChanged:(value){setState(() {_checked=value; }); }); }}Copy the code

4.2: CheckBox source code brief analysis

Here is the code for Checkbox, inherited from StatefulWidget, so you need to implement the createState method

At this point, the source code uses a custom _CheckboxState class to manage state.

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; // Whether final ValueChanged<bool> onChanged; // Click the final Color activeColor callback; // Activate box Color final Color checkColor; // Final bool tristATE; // Final MaterialTapTargetSize MaterialTapTargetSize; Static const double width = 18.0; @override _CheckboxState createState() => _CheckboxState(); }Copy the code

Through the source code of these two components, you can summarize some style characteristics:

1. Constructors are const, with one attribute per line. 2. Assert non-empty attributes. 4. Fields are all final typesCopy the code

Returns the _CheckboxRenderObjectWidget _CheckboxState the build method

_RenderCheckbox specifies the rendering logic and state changes in _RenderCheckbox

---->[flutter/packages/flutter/lib/src/material/checkbox.dart:140]---- class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {@override Widget build(BuildContext context) { } return _CheckboxRenderObjectWidget (/ / slightly...). ; } } ---->[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) { }Copy the code

4.3: Checkbox core rendering method

Rendercheckbox inherits RenderToggleable and can override the paint method

Here is a brief look at the main border and tick drawing method

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; // Gradually fill the outer rectangle as t goes from 0.0 to 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); // Start Offset const Offset mid = Offset(_kEdgeSize * 0.4, _kEdgeSize * 0.7); Const Offset end = Offset(_kEdgeSize * 0.85, _kEdgeSize * 0.25); // Const Offset end = Offset(_kEdgeSize * 0.85, _kEdgeSize * 0.25); Final double strokeT = t * 2.0; 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); }Copy the code

So you should know the flow of a StatefulWidget component from definition to state to drawing


Hopefully, this article will give you a deeper understanding of widgets, but this is just the beginning. This article concludes with a more in-depth exploration of Widgets. If you want to experiment with Flutter quickly, Flutter 7 is a must-have. If you want to explore it, follow in my footsteps and complete a Flutter tour. In addition, I have a Flutter wechat communication group. You are welcome to join and discuss Flutter issues together. My wechat account is ZDL1994328.