什么是有状态组件(StatefulWidget)?

在 Flutter 中,组件分为两种:

  • StatelessWidget(无状态组件) - 静态的,一旦创建就不会改变
  • StatefulWidget(有状态组件) - 动态的,可以根据用户操作或数据变化而更新

简单理解:

  • 无状态组件 = 一张静态的照片
  • 有状态组件 = 一段可以播放的视频

为什么需要有状态组件?

想象这些场景:

  • 点击按钮,计数器数字增加 ✅
  • 输入框输入文字,界面显示输入内容 ✅
  • 切换开关,按钮颜色改变 ✅
  • 下拉刷新,列表数据更新 ✅

这些都需要界面"动态更新",就必须用 StatefulWidget

无状态 vs 有状态

无状态组件示例

class MyText extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Text('我是静态文本');
  }
}

这个文本永远显示"我是静态文本",不会改变。

有状态组件示例

class Counter extends StatefulWidget {
  
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int count = 0;  // 这是状态

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('点击了 $count 次'),
        ElevatedButton(
          onPressed: () {
            setState(() {
              count++;  // 改变状态,界面会自动更新
            });
          },
          child: Text('点我'),
        ),
      ],
    );
  }
}

点击按钮,数字会增加,界面会更新!

StatefulWidget 的结构

有状态组件由两部分组成:

// 第一部分:StatefulWidget(组件本身)
class MyWidget extends StatefulWidget {
  
  _MyWidgetState createState() => _MyWidgetState();
}

// 第二部分:State(状态管理)
class _MyWidgetState extends State<MyWidget> {
  // 这里定义状态变量
  
  
  Widget build(BuildContext context) {
    // 这里构建界面
    return Container();
  }
}

为什么要分两部分?

  • StatefulWidget - 不可变的配置信息
  • State - 可变的状态数据

这样设计可以让 Flutter 高效地重建界面。

核心概念:setState()

setState() 是有状态组件的灵魂,它告诉 Flutter:“我的数据变了,请重新绘制界面!”

正确用法

// ✅ 正确:在 setState 中修改状态
setState(() {
  count++;
});

错误用法

// ❌ 错误:直接修改状态,界面不会更新
count++;

记住:想让界面更新,必须用 setState()!

基础示例

示例1:简单计数器

import 'package:flutter/material.dart';

class CounterApp extends StatefulWidget {
  
  _CounterAppState createState() => _CounterAppState();
}

class _CounterAppState extends State<CounterApp> {
  int _counter = 0;

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('计数器')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              '你点击了',
              style: TextStyle(fontSize: 20),
            ),
            Text(
              '$_counter',
              style: TextStyle(fontSize: 50, fontWeight: FontWeight.bold),
            ),
            Text(
              '次',
              style: TextStyle(fontSize: 20),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _increment,
        child: Icon(Icons.add),
      ),
    );
  }
}

示例2:开关切换

class ToggleSwitch extends StatefulWidget {
  
  _ToggleSwitchState createState() => _ToggleSwitchState();
}

class _ToggleSwitchState extends State<ToggleSwitch> {
  bool _isOn = false;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('开关')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              width: 200,
              height: 200,
              decoration: BoxDecoration(
                color: _isOn ? Colors.green : Colors.grey,
                shape: BoxShape.circle,
              ),
              child: Center(
                child: Text(
                  _isOn ? '开' : '关',
                  style: TextStyle(fontSize: 40, color: Colors.white),
                ),
              ),
            ),
            SizedBox(height: 30),
            Switch(
              value: _isOn,
              onChanged: (value) {
                setState(() {
                  _isOn = value;
                });
              },
            ),
          ],
        ),
      ),
    );
  }
}

示例3:文本输入实时显示

class TextInputDemo extends StatefulWidget {
  
  _TextInputDemoState createState() => _TextInputDemoState();
}

class _TextInputDemoState extends State<TextInputDemo> {
  String _inputText = '';

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('输入演示')),
      body: Padding(
        padding: EdgeInsets.all(20),
        child: Column(
          children: [
            TextField(
              onChanged: (value) {
                setState(() {
                  _inputText = value;
                });
              },
              decoration: InputDecoration(
                labelText: '输入点什么',
                border: OutlineInputBorder(),
              ),
            ),
            SizedBox(height: 20),
            Text(
              '你输入了:$_inputText',
              style: TextStyle(fontSize: 20),
            ),
            SizedBox(height: 10),
            Text(
              '字数:${_inputText.length}',
              style: TextStyle(fontSize: 16, color: Colors.grey),
            ),
          ],
        ),
      ),
    );
  }
}

生命周期

StatefulWidget 有完整的生命周期,就像人的一生:出生 → 成长 → 死亡。

生命周期方法

class LifecycleDemo extends StatefulWidget {
  
  _LifecycleDemoState createState() {
    print('1. createState - 创建状态对象');
    return _LifecycleDemoState();
  }
}

class _LifecycleDemoState extends State<LifecycleDemo> {
  
  void initState() {
    super.initState();
    print('2. initState - 初始化,只执行一次');
    // 在这里做初始化工作:加载数据、订阅事件等
  }

  
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('3. didChangeDependencies - 依赖改变时调用');
  }

  
  Widget build(BuildContext context) {
    print('4. build - 构建界面(会多次调用)');
    return Container(
      child: Text('生命周期演示'),
    );
  }

  
  void didUpdateWidget(LifecycleDemo oldWidget) {
    super.didUpdateWidget(oldWidget);
    print('5. didUpdateWidget - 组件更新时调用');
  }

  
  void deactivate() {
    print('6. deactivate - 组件被移除时调用');
    super.deactivate();
  }

  
  void dispose() {
    print('7. dispose - 组件销毁时调用');
    // 在这里做清理工作:取消订阅、释放资源等
    super.dispose();
  }
}

生命周期执行顺序

创建阶段:
createState → initState → didChangeDependencies → build

更新阶段:
didUpdateWidget → build

销毁阶段:
deactivate → dispose

常用生命周期方法

方法 用途 调用次数
initState() 初始化数据、订阅事件 1次
build() 构建界面 多次
dispose() 清理资源、取消订阅 1次

实战案例

案例1:待办事项列表

class TodoList extends StatefulWidget {
  
  _TodoListState createState() => _TodoListState();
}

class _TodoListState extends State<TodoList> {
  List<String> _todos = [];
  TextEditingController _controller = TextEditingController();

  void _addTodo() {
    if (_controller.text.isNotEmpty) {
      setState(() {
        _todos.add(_controller.text);
        _controller.clear();
      });
    }
  }

  void _removeTodo(int index) {
    setState(() {
      _todos.removeAt(index);
    });
  }

  
  void dispose() {
    _controller.dispose();  // 清理控制器
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('待办事项')),
      body: Column(
        children: [
          Padding(
            padding: EdgeInsets.all(10),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _controller,
                    decoration: InputDecoration(
                      hintText: '输入待办事项',
                      border: OutlineInputBorder(),
                    ),
                  ),
                ),
                SizedBox(width: 10),
                ElevatedButton(
                  onPressed: _addTodo,
                  child: Text('添加'),
                ),
              ],
            ),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: _todos.length,
              itemBuilder: (context, index) {
                return ListTile(
                  title: Text(_todos[index]),
                  trailing: IconButton(
                    icon: Icon(Icons.delete, color: Colors.red),
                    onPressed: () => _removeTodo(index),
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

案例2:颜色选择器

class ColorPicker extends StatefulWidget {
  
  _ColorPickerState createState() => _ColorPickerState();
}

class _ColorPickerState extends State<ColorPicker> {
  Color _selectedColor = Colors.blue;

  final List<Color> _colors = [
    Colors.red,
    Colors.blue,
    Colors.green,
    Colors.orange,
    Colors.purple,
    Colors.pink,
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('颜色选择器')),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Container(
            width: 200,
            height: 200,
            decoration: BoxDecoration(
              color: _selectedColor,
              borderRadius: BorderRadius.circular(20),
              boxShadow: [
                BoxShadow(
                  color: _selectedColor.withOpacity(0.5),
                  blurRadius: 20,
                  spreadRadius: 5,
                ),
              ],
            ),
          ),
          SizedBox(height: 30),
          Text(
            '选择一个颜色',
            style: TextStyle(fontSize: 20),
          ),
          SizedBox(height: 20),
          Wrap(
            spacing: 10,
            children: _colors.map((color) {
              return GestureDetector(
                onTap: () {
                  setState(() {
                    _selectedColor = color;
                  });
                },
                child: Container(
                  width: 50,
                  height: 50,
                  decoration: BoxDecoration(
                    color: color,
                    shape: BoxShape.circle,
                    border: Border.all(
                      color: _selectedColor == color
                          ? Colors.black
                          : Colors.transparent,
                      width: 3,
                    ),
                  ),
                ),
              );
            }).toList(),
          ),
        ],
      ),
    );
  }
}

案例3:倒计时器

import 'dart:async';

class CountdownTimer extends StatefulWidget {
  
  _CountdownTimerState createState() => _CountdownTimerState();
}

class _CountdownTimerState extends State<CountdownTimer> {
  int _seconds = 60;
  Timer? _timer;
  bool _isRunning = false;

  void _startTimer() {
    if (_isRunning) return;
    
    setState(() {
      _isRunning = true;
    });

    _timer = Timer.periodic(Duration(seconds: 1), (timer) {
      setState(() {
        if (_seconds > 0) {
          _seconds--;
        } else {
          _stopTimer();
        }
      });
    });
  }

  void _stopTimer() {
    _timer?.cancel();
    setState(() {
      _isRunning = false;
    });
  }

  void _resetTimer() {
    _stopTimer();
    setState(() {
      _seconds = 60;
    });
  }

  
  void dispose() {
    _timer?.cancel();  // 组件销毁时取消定时器
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('倒计时')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              '$_seconds',
              style: TextStyle(
                fontSize: 80,
                fontWeight: FontWeight.bold,
                color: _seconds <= 10 ? Colors.red : Colors.black,
              ),
            ),
            SizedBox(height: 30),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: _isRunning ? null : _startTimer,
                  child: Text('开始'),
                ),
                SizedBox(width: 10),
                ElevatedButton(
                  onPressed: _isRunning ? _stopTimer : null,
                  child: Text('暂停'),
                ),
                SizedBox(width: 10),
                ElevatedButton(
                  onPressed: _resetTimer,
                  child: Text('重置'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

常见问题

1. 什么时候用 StatefulWidget?

需要用的场景:

  • 界面需要根据用户操作更新
  • 需要保存用户输入的数据
  • 需要定时器、动画等
  • 需要网络请求后更新界面

不需要用的场景:

  • 纯展示的静态页面
  • 不会改变的配置信息
  • 只是组合其他组件

2. setState() 里应该放什么?

// ✅ 推荐:只放修改状态的代码
setState(() {
  count++;
});

// ❌ 不推荐:放复杂的计算
setState(() {
  // 大量计算...
  count = complexCalculation();
});

// ✅ 正确做法:先计算,再 setState
int newCount = complexCalculation();
setState(() {
  count = newCount;
});

3. 为什么界面没有更新?

原因1:忘记用 setState

// ❌ 错误
count++;  // 界面不会更新

// ✅ 正确
setState(() {
  count++;
});

原因2:修改了对象内部属性

// ❌ 错误
myList.add('item');  // 界面不会更新

// ✅ 正确
setState(() {
  myList.add('item');
});

4. 如何在 initState 中使用 context?


void initState() {
  super.initState();
  
  // ❌ 错误:initState 中不能直接用 context
  // showDialog(context: context, ...);
  
  // ✅ 正确:延迟到下一帧执行
  WidgetsBinding.instance.addPostFrameCallback((_) {
    showDialog(context: context, builder: (context) => AlertDialog());
  });
}

性能优化技巧

1. 避免不必要的 setState

// ❌ 不好:每次都 setState
void updateData(String value) {
  setState(() {
    data = value;
  });
}

// ✅ 更好:只在数据真正改变时 setState
void updateData(String value) {
  if (data != value) {
    setState(() {
      data = value;
    });
  }
}

2. 拆分大组件

// ❌ 不好:整个页面都在一个 StatefulWidget
class BigPage extends StatefulWidget { ... }

// ✅ 更好:拆分成多个小组件
class BigPage extends StatefulWidget {
  
  Widget build(BuildContext context) {
    return Column(
      children: [
        Header(),  // 独立的组件
        Content(),  // 独立的组件
        Footer(),  // 独立的组件
      ],
    );
  }
}

3. 使用 const 构造函数

// ✅ 好:使用 const,Flutter 会复用组件
const Text('标题')
const Icon(Icons.home)

完整示例:综合应用

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'StatefulWidget 示例',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _counter = 0;
  bool _isVisible = true;
  String _selectedItem = '选项1';

  
  void initState() {
    super.initState();
    print('页面初始化');
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('有状态组件示例'),
      ),
      body: SingleChildScrollView(
        padding: EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // 计数器
            Card(
              child: Padding(
                padding: EdgeInsets.all(20),
                child: Column(
                  children: [
                    Text('计数器', style: TextStyle(fontSize: 20)),
                    SizedBox(height: 10),
                    Text('$_counter', style: TextStyle(fontSize: 40)),
                    SizedBox(height: 10),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        ElevatedButton(
                          onPressed: () => setState(() => _counter++),
                          child: Text('+'),
                        ),
                        SizedBox(width: 10),
                        ElevatedButton(
                          onPressed: () => setState(() => _counter--),
                          child: Text('-'),
                        ),
                        SizedBox(width: 10),
                        ElevatedButton(
                          onPressed: () => setState(() => _counter = 0),
                          child: Text('重置'),
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ),
            SizedBox(height: 20),

            // 显示/隐藏
            Card(
              child: Padding(
                padding: EdgeInsets.all(20),
                child: Column(
                  children: [
                    Text('显示控制', style: TextStyle(fontSize: 20)),
                    SizedBox(height: 10),
                    if (_isVisible)
                      Container(
                        padding: EdgeInsets.all(20),
                        color: Colors.blue.withOpacity(0.2),
                        child: Text('我是可以隐藏的内容'),
                      ),
                    SizedBox(height: 10),
                    ElevatedButton(
                      onPressed: () => setState(() => _isVisible = !_isVisible),
                      child: Text(_isVisible ? '隐藏' : '显示'),
                    ),
                  ],
                ),
              ),
            ),
            SizedBox(height: 20),

            // 下拉选择
            Card(
              child: Padding(
                padding: EdgeInsets.all(20),
                child: Column(
                  children: [
                    Text('下拉选择', style: TextStyle(fontSize: 20)),
                    SizedBox(height: 10),
                    DropdownButton<String>(
                      value: _selectedItem,
                      isExpanded: true,
                      items: ['选项1', '选项2', '选项3'].map((item) {
                        return DropdownMenuItem(
                          value: item,
                          child: Text(item),
                        );
                      }).toList(),
                      onChanged: (value) {
                        setState(() => _selectedItem = value!);
                      },
                    ),
                    SizedBox(height: 10),
                    Text('你选择了:$_selectedItem'),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  
  void dispose() {
    print('页面销毁');
    super.dispose();
  }
}

总结

StatefulWidget 的核心要点:

  1. 结构 - 由 StatefulWidget + State 两部分组成
  2. 更新 - 用 setState() 触发界面重建
  3. 生命周期 - initState → build → dispose
  4. 使用场景 - 任何需要动态更新的界面

记住这个公式:

数据改变 + setState() = 界面更新

现在你已经掌握了 Flutter 中最重要的概念之一,去创建你的动态应用吧!

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐