DEV Community

Ge Ji
Ge Ji

Posted on

Flutter Lesson 3: Flutter Core Concept - Widget

In Flutter development, Widgets are the most core and fundamental concept - almost all interface elements are composed of Widgets. In this lesson, we'll deeply understand the nature and characteristics of Widgets, as well as their working methods in Flutter applications, laying a solid foundation for subsequent interface development.

I. What is a Widget?

A Widget (component) is the basic unit for building user interfaces in the Flutter framework. It can be understood as an abstract description of all visible elements on the screen. From simple text and buttons to complex layouts and pages, even application theme styles are represented as Widgets in Flutter.

A Widget isn't just a description of visual elements; it also contains:

  • Structural information of interface elements
  • Layout constraints and rules
  • Drawing information
  • Interactive behaviors
  • State management methods

Differences from Traditional UI Components

Flutter Widgets are fundamentally different from UI components in traditional native development or other cross-platform frameworks:

  1. Descriptive vs. Instantiated
    • Traditional components: Usually instantiated objects that hold actual rendering states
    • Flutter Widgets: Immutable descriptions of UI, more like a "blueprint" or "configuration file"
  2. Lightweight vs. Heavyweight
    • Traditional components: High cost to create and destroy, containing A lot of underlying rendering logic
    • Flutter Widgets: Very lightweight with low creation cost; frequent rebuilding doesn't significantly affect performance
  3. Composition Method
    • Traditional components: Usually extended through inheritance
    • Flutter Widgets: Almost entirely built through composition rather than inheritance for complex UIs
  4. Rendering Method
    • Traditional components: Dependent on platform-native controls for rendering
    • Flutter Widgets: Directly rendered by the Flutter engine, independent of platform controls

II. Immutable Widget Characteristics and Reconstruction Mechanism

Immutability

In Flutter, almost all Widgets are immutable, meaning once created, a Widget's properties (member variables) cannot be modified. All Widget properties should be declared as final:

import 'package:flutter/material.dart';

class MyWidget extends Widget {
  final String title;
  final int count;

  // Constructors must be const or regular constructors; methods that modify properties are not allowed
  const MyWidget({
    super.key,
    required this.title,
    required this.count,
  });

// ...
}
Enter fullscreen mode Exit fullscreen mode

Advantages of immutability:

  • Simplifies state management: Widgets themselves don't store mutable state
  • Improves performance: Flutter can quickly compare Widget changes
  • Thread safety: Immutable objects can be safely passed between threads
  • Easier testing and debugging: State changes are traceable

Reconstruction Mechanism

Since Widgets are immutable, when UI updates are needed, Flutter uses a Widget tree reconstruction approach rather than modifying existing Widgets.

This process is similar to:

  1. When state changes, new Widget instances are created (containing new property values)
  2. The Flutter framework compares the old and new Widget trees (a process called "diffing")
  3. Only the parts that actually changed are updated to the rendering tree

The principle of Hot Reload is based on this: after modifying code, Flutter rebuilds the Widget tree but preserves application state, enabling quick preview of changes.

StatelessWidget vs. StatefulWidget

Widgets are divided into two main categories based on whether they need to manage state:

  1. StatelessWidget
    • Contains no mutable state
    • Once created, its appearance is entirely determined by constructor parameters
    • Suitable for static UI elements like titles and icons
    • Examples: Text, Icon, Container
import 'package:flutter/material.dart';

class MyStatelessWidget extends StatelessWidget {
  final String message;

  const MyStatelessWidget({super.key, required this.message});

  @override
  Widget build(BuildContext context) {
    return Text(message);
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. StatefulWidget
    • Contains mutable state
    • State changes trigger UI reconstruction
    • Suitable for scenarios requiring interaction or dynamic data changes
    • Examples: TextField, Checkbox, ListView
import 'package:flutter/material.dart';

class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({super.key});

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $_count'),
        ElevatedButton(
          onPressed: _increment,
          child: const Text('Increment'),
        ),
      ],
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

The setState() method is crucial for state updates, as it notifies the Flutter framework that state has changed and the Widget needs to be rebuilt.


III. Introduction to Basic Widgets

Flutter provides a rich set of basic Widgets. Here are some of the most commonly used ones:

1. Container

Container is a versatile container Widget, similar to div in HTML. It can contain a child Widget and add decorations, margins, padding, etc.

Container(
  width: 200,                // Width
  height: 200,               // Height
  margin: const EdgeInsets.all(20),  // Margin
  padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),  // Padding
    decoration: BoxDecoration(
      color: Colors.blue,      // Background color
      borderRadius: BorderRadius.circular(10),  // Border radius
      boxShadow: const [       // Shadow
        BoxShadow(
        color: Colors.grey,
        blurRadius: 5,
        offset: Offset(2, 2),
        ),
    ],
  ),
  child: const Text(         // Child Widget
  'Hello Container',
  style: TextStyle(color: Colors.white, fontSize: 18),
  ),
  alignment: Alignment.center,  // Alignment of child Widget
)
Enter fullscreen mode Exit fullscreen mode

2. Text

Text is used to display text, with styling options through TextStyle.

const Text(
  'Hello Flutter',
  style: TextStyle(
    fontSize: 24,            // Font size
    color: Colors.black87,   // Color
    fontWeight: FontWeight.bold,  // Font weight
    fontStyle: FontStyle.italic,  // Font style
    decoration: TextDecoration.underline,  // Text decoration
    decorationColor: Colors.red,  // Decoration color
    letterSpacing: 2,        // Letter spacing
  ),
  textAlign: TextAlign.center,  // Alignment
  maxLines: 2,              // Maximum lines
  overflow: TextOverflow.ellipsis,  // Overflow handling
)
Enter fullscreen mode Exit fullscreen mode

3. Image

Image is used to display images and supports multiple image sources:

// Load from network
Image.network(
  'https://picsum.photos/200/300',
  width: 200,
  height: 300,
  fit: BoxFit.cover,  // Image fitting mode
  loadingBuilder: (context, child, loadingProgress) {
    if (loadingProgress == null) return child;
    return const Center(child: CircularProgressIndicator());
  },
  errorBuilder: (context, error, stackTrace) {
    return const Icon(Icons.error);
  },
)

// Load from local assets (needs configuration in pubspec.yaml)
Image.asset(
'assets/images/logo.png',
width: 100,
height: 100,
)

// Load from file
Image.file(File('/path/to/image.jpg'))

// Load from memory
Image.memory(Uint8List bytes)
Enter fullscreen mode Exit fullscreen mode

To use local asset images, configure them in pubspec.yaml:

flutter:
  assets:
    - assets/images/logo.png
    - assets/icons/
Enter fullscreen mode Exit fullscreen mode

4. Icon

Icon is used to display icons. Flutter has a built-in Material Design icon library and supports custom icons.

const Icon(
  Icons.favorite,    // Icon name
  color: Colors.red, // Color
  size: 32,          // Size
)

// Using icon buttons
IconButton(
  icon: const Icon(Icons.share),
  onPressed: () {
  // Handle click event
  },
  tooltip: 'Share',  // Long press hint
)
Enter fullscreen mode Exit fullscreen mode

You can add custom font icons via pubspec.yaml:

flutter:
  fonts:
    - family: MyIcons
      fonts:
        - asset: fonts/my_icons.ttf
Enter fullscreen mode Exit fullscreen mode

IV. Widget Tree Structure and Nesting Rules

A Flutter application's interface is formed by nested Widgets in a tree structure called the "Widget tree". Understanding the Widget tree structure and nesting rules is key to mastering Flutter layouts.

Widget Tree Structure

A typical Flutter application Widget tree structure looks like this:

MaterialApp
├── Scaffold
│   ├── AppBar
│   ├── Body
│   │   ├── Column
│   │   │   ├── Text
│   │   │   ├── Container
│   │   │   │   └── Image
│   │   │   └── ElevatedButton
│   │   └── ListView
│   │       ├── ListTile
│   │       └── ListTile
│   └── FloatingActionButton
└── Theme
Enter fullscreen mode Exit fullscreen mode
  • The root node is usually MaterialApp or CupertinoApp (providing basic application framework)
  • Intermediate nodes are usually layout Widgets (like Column, Row, Stack)
  • Leaf nodes are usually display Widgets (like Text, Image, Icon)

Nesting Rules

Flutter adopts the design philosophy of "composition over inheritance", implementing complex UIs through Widget nesting. These rules should be followed when nesting:

  1. Single-child Widgets: Can contain only one child Widget, such as Container, Padding, Center
Container(
  child: Text('Single child'),  // Can only have one child
)
Enter fullscreen mode Exit fullscreen mode

2 . Multi-child Widgets: Can contain multiple child Widgets, usually receiving a list of Widgets through the children property

Column(
  children: [  // Multiple child Widgets in a list
    Text('First child'),
    Text('Second child'),
    ElevatedButton(onPressed: () {}, child: Text('Button')),
  ],
)
Enter fullscreen mode Exit fullscreen mode

3 . Layout constraint propagation: Parent Widgets pass layout constraints (max/min width/height) to child Widgets, which determine their size within these constraints

4 . Context propagation: Each Widget has a context representing its position in the Widget tree, useful for navigation, theme access, etc.

Common Layout Widgets

Mastering these layout Widgets can meet most interface needs:

  1. Row: Arranges child Widgets horizontally
  2. Column: Arranges child Widgets vertically
  3. Stack: Arranges child Widgets in layers
  4. ListView: Scrollable list
  5. GridView: Grid layout
  6. Expanded: Takes remaining space in Row/Column
  7. Padding: Adds inner padding
  8. Center: Centers child Widget
  9. Align: Custom alignment for child Widget

Example: Combining multiple layout Widgets

Column(
  children: [
    const Text('User Profile', style: TextStyle(fontSize: 20)),
    const SizedBox(height: 16),  // Spacing
    Row(
      mainAxisAlignment: MainAxisAlignment.center,  // Horizontally centered
      children: [
        Image.network(
          'https://picsum.photos/100/100',
          width: 100,
          height: 100,
        ),
        const SizedBox(width: 16),  // Horizontal spacing
        const Column(
          crossAxisAlignment: CrossAxisAlignment.start,  // Left-aligned
          children: [
            Text('John Doe'),
            Text('john@example.com'),
          ],
        ),
      ],
    ),
    const Expanded(  // Takes remaining space
      child: Center(
        child: Text('Profile details will appear here'),
      ),
    ),
  ],
)
Enter fullscreen mode Exit fullscreen mode

V. Widget Lifecycle

While Widgets themselves are immutable, State objects associated with StatefulWidgets have a clear lifecycle:

  1. Creation phase
    • createState(): Creates the State object
    • initState(): Initializes state, called only once
  2. Building phase
    • build(): Builds the Widget tree, called on each state change
    • didUpdateWidget(): Called when parent Widget reconstruction causes child Widget changes
  3. Active phase
    • setState(): Triggers state update and reconstruction
    • didChangeDependencies(): Called when dependent InheritedWidget changes
  4. Destruction phase
    • deactivate(): Called when Widget is about to be removed from the tree
    • dispose(): Called when Widget is permanently removed, used for resource cleanup

Understanding the lifecycle helps properly manage resources and state, avoiding memory leaks.

Top comments (0)