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:
- 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"
- 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
- Composition Method
- Traditional components: Usually extended through inheritance
- Flutter Widgets: Almost entirely built through composition rather than inheritance for complex UIs
- 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,
});
// ...
}
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:
- When state changes, new Widget instances are created (containing new property values)
- The Flutter framework compares the old and new Widget trees (a process called "diffing")
- 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:
- 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);
}
}
- 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'),
),
],
);
}
}
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
)
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
)
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)
To use local asset images, configure them in pubspec.yaml:
flutter:
assets:
- assets/images/logo.png
- assets/icons/
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
)
You can add custom font icons via pubspec.yaml:
flutter:
fonts:
- family: MyIcons
fonts:
- asset: fonts/my_icons.ttf
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
- 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:
- Single-child Widgets: Can contain only one child Widget, such as Container, Padding, Center
Container(
child: Text('Single child'), // Can only have one child
)
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')),
],
)
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:
- Row: Arranges child Widgets horizontally
- Column: Arranges child Widgets vertically
- Stack: Arranges child Widgets in layers
- ListView: Scrollable list
- GridView: Grid layout
- Expanded: Takes remaining space in Row/Column
- Padding: Adds inner padding
- Center: Centers child Widget
- 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'),
),
),
],
)
V. Widget Lifecycle
While Widgets themselves are immutable, State objects associated with StatefulWidgets have a clear lifecycle:
- Creation phase
- createState(): Creates the State object
- initState(): Initializes state, called only once
- Building phase
- build(): Builds the Widget tree, called on each state change
- didUpdateWidget(): Called when parent Widget reconstruction causes child Widget changes
- Active phase
- setState(): Triggers state update and reconstruction
- didChangeDependencies(): Called when dependent InheritedWidget changes
- 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)