第4讲:理解Flutter的灵魂 - “Everything is a Widget”

掌握Widget,你就掌握了构建FlutterUI世界的基本法则。

你好,欢迎回到《Flutter入门到精通》专栏。在上一讲中,我们剖析了Flutter项目的结构,并体验了热重载的神奇。你可能已经注意到,代码中充满了各种各样的 Widget。没错,在Flutter的世界里,一切皆是Widget

本讲将带你深入理解Widget的概念,区分无状态与有状态Widget,并亲手构建你的第一个自定义Widget。

一、什么是Widget?UI的构建块

1.1 Widget的本质

简单来说,Widget是您用于声明和构建用户界面的蓝图或配置

它并不代表最终绘制在屏幕上的UI元素,而是一个描述。Flutter框架会根据这些Widget的配置信息,创建出相应的、高效的渲染对象,最终由Skia引擎绘制到屏幕上。

一个生动的比喻:

  • Widget 就像建筑图纸。它描述了房子的结构、房间的布局、门窗的样式。

  • Flutter框架 就像施工队,它读取图纸(Widget),调用建筑材料(渲染对象),最终建起真正的房子(用户界面)。

  • 当你想改变一扇窗(UI)时,你不需要自己去拆墙,你只需要修改图纸(更新Widget),施工队(Flutter框架)就会高效地帮你完成更新。

1.2 Widget树:UI的层次结构

Flutter的UI是通过嵌套Widget来构建的,形成一个Widget树

让我们回顾一下上一讲计数器应用中的 build 方法:

dart

复制代码
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text(widget.title), // AppBar 包含一个 Text
    ),
    body: Center( // Center 包含一个 Column
      child: Column( // Column 包含两个 Text
        children: <Widget>[
          const Text('You have pushed the button this many times:'),
          Text('$_counter'),
        ],
      ),
    ),
    floatingActionButton: FloatingActionButton( // 一个独立的Widget
      onPressed: _incrementCounter,
      child: const Icon(Icons.add), // FloatingActionButton 包含一个 Icon
    ),
  );
}

这段代码对应的Widget树可以简化为:

text

复制代码
Scaffold
├── AppBar
│   └── Text
├── Center
│   └── Column
│       ├── Text
│       └── Text
└── FloatingActionButton
    └── Icon

理解Widget树是理解Flutter布局和渲染的关键。


二、StatelessWidget vs StatefulWidget:核心区别

这是Flutter中最重要的概念之一。所有Widget都基于这两种类型之一。

2.1 StatelessWidget(无状态Widget)

  • 定义 : 一旦创建,其内部状态就不可变的Widget。它就像一张静态图片。

  • 特点

    • 它依赖于父Widget传递过来的初始配置信息和自身不变的固有属性。

    • 它不存储任何会随时间变化的数据。

    • 它只有一个必需的 build 方法。

  • 常见例子Text, Icon, Image, AppBar 等。

创建一个简单的StatelessWidget:

让我们创建一个显示问候语的Widget。

dart

复制代码
// 定义一个名为 GreetingText 的无状态Widget
class GreetingText extends StatelessWidget {
  // 构造函数:接收一个不可变的最终属性 `name`
  const GreetingText({super.key, required this.name});

  // 声明一个 final 成员变量,意味着它一旦被赋值就不能再改变
  final String name;

  // 必须实现的 build 方法,描述如何构建这个Widget
  @override
  Widget build(BuildContext context) {
    return Text(
      'Hello, $name!', // 使用传入的 name 属性
      style: const TextStyle(fontSize: 20),
    );
  }
}

如何使用它?

在你之前 MyHomePagebody 中,可以这样使用:

dart

复制代码
body: Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      GreetingText(name: 'Flutter Developer'), // 在这里使用!
      Text('$_counter'),
    ],
  ),
),

2.2 StatefulWidget(有状态Widget)

  • 定义 : 在其生命周期内,持有可变状态的Widget。它就像一个可以播放的视频播放器,其播放进度、音量等状态会变化。

  • 特点

    • 它由两个类组成:一个不可变的 StatefulWidget 子类和一个可变的 State 子类。

    • 状态(数据)存储在 State 对象中 ,而不是 StatefulWidget 本身。

    • 当状态改变时,调用 setState() 来通知框架重建UI。

  • 常见例子 : 带有表单输入的页面、动画、计数器(我们之前的 MyHomePage 就是)。

剖析StatefulWidget的结构:

计数器应用中的 MyHomePage 就是一个完美的例子。我们来分解它:

dart

复制代码
// 1. StatefulWidget 部分:本身是不可变的,主要负责接收和传递初始配置
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title; // 不可变的属性

  // 这个方法创建并返回管理状态的 State 对象
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

// 2. State 部分:这里是状态和逻辑存在的地方
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0; // 可变的状态!存储在这里

  void _incrementCounter() {
    // 修改状态,并通知框架“状态已改变,请重建UI!”
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    // build 方法在 State 类中!
    // 它可以访问可变状态 (_counter) 和来自Widget的不可变属性 (widget.title)
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)), // 使用 widget.[属性名] 访问
      body: Center(child: Text('$_counter')), // 使用状态 _counter
      floatingActionButton: FloatingActionButton(onPressed: _incrementCounter),
    );
  }
}

关键点总结:

特性 StatelessWidget StatefulWidget
状态 不可变 可变 (存储在单独的 State 对象中)
组成 一个类 两个类:StatefulWidget + State
性能 较轻量 相对较重(因为需要管理状态生命周期)
使用场景 静态内容、展示型UI 交互式UI、动态数据、动画

三、实践:构建一个交互式自定义Widget

现在,让我们综合所学,创建一个全新的、包含交互的自定义StatefulWidget------一个简单的"喜欢"按钮。

  1. lib 文件夹下,新建一个Dart文件,比如 like_button.dart

  2. 在文件中写入以下代码:

dart

复制代码
import 'package:flutter/material.dart';

class LikeButton extends StatefulWidget {
  // 构造函数:可以接收一个初始的喜欢状态,默认为false
  const LikeButton({super.key, this.isLiked = false});

  final bool isLiked;

  @override
  State<LikeButton> createState() => _LikeButtonState();
}

class _LikeButtonState extends State<LikeButton> {
  // 状态:这个按钮当前是否被“喜欢”
  bool _isLiked = false;

  // 初始化状态:我们可以用从Widget传过来的初始值来设置状态
  @override
  void initState() {
    super.initState();
    _isLiked = widget.isLiked;
  }

  // 处理点击事件的方法
  void _toggleLike() {
    setState(() {
      _isLiked = !_isLiked; // 切换喜欢状态
    });
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector( // 一个可以检测手势的Widget
      onTap: _toggleLike, // 当被点击时,调用 _toggleLike 方法
      child: Row(
        mainAxisSize: MainAxisSize.min, // 让Row只占用所需的空间
        children: [
          Icon(
            _isLiked ? Icons.favorite : Icons.favorite_border, // 根据状态选择图标
            color: _isLiked ? Colors.red : Colors.grey, // 根据状态选择颜色
          ),
          const SizedBox(width: 5), // 添加一些间距
          Text(_isLiked ? 'Liked' : 'Like'), // 根据状态显示文本
        ],
      ),
    );
  }
}
  1. main.dart 中引入这个文件,并在 MyHomePagebody 中使用它:

dart

复制代码
// 在文件顶部导入
import 'like_button.dart';

// ... 在 MyHomePage 的 body 的 Column 的 children 中添加 ...
body: Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      const Text('Check out this new feature:'),
      LikeButton(), // 使用默认值
      const SizedBox(height: 20), // 添加垂直间距
      LikeButton(isLiked: true), // 使用初始值为true
      const SizedBox(height: 20),
      Text('Counter: $_counter'),
    ],
  ),
),

现在运行你的应用,你应该能看到两个"Like"按钮,点击它们可以独立地在"Like"和"Liked"状态之间切换!

结语

恭喜!你已经迈出了成为Flutter开发者的最关键一步。你不仅理解了Widget是Flutter UI的基石 ,还掌握了无状态与有状态Widget的核心区别与创建方法,并成功构建了属于自己的交互式组件。

记住这个简单的决策流程:如果你的UI部分需要根据数据变化而改变,就使用StatefulWidget;如果它只是静态展示,就使用StatelessWidget。

在下一讲中,我们将暂时放下抽象概念,开始学习具体的、强大的布局与基础Widget ,如 Container, Row, Column, Stack 等,让你能够构建出更加复杂和精美的界面。

相关推荐
西西学代码23 分钟前
Flutter---Listview横向滚动列表(1)
flutter
学困昇24 分钟前
C++11中的{}与std::initializer_list
开发语言·c++·c++11
郝学胜-神的一滴27 分钟前
Qt的QComboBox控件详解:从API到样式定制
开发语言·c++·qt·程序人生·个人开发
憧憬blog29 分钟前
【Kiro开发集训营】拒绝“屎山”堆积:在 Kiro 中重构“需求-代码”的血缘关系
java·开发语言·kiro
行云流水6261 小时前
uniapp pinia实现数据持久化插件
前端·javascript·uni-app
n***i951 小时前
Java NIO文件操作
java·开发语言·nio
星释1 小时前
Rust 练习册 72:多米诺骨牌与回溯算法
开发语言·算法·rust
zhangyao9403302 小时前
uniapp动态修改 顶部导航栏标题和右侧按钮权限显示隐藏
前端·javascript·uni-app
程序喵大人3 小时前
推荐个C++高性能内存分配器
开发语言·c++·内存分配