flutter学习第 3 节:Flutter 核心概念:Widget

在 Flutter 开发中,Widget 是最核心、最基础的概念,几乎所有的界面元素都是由 Widget 构成的。本节课我们将深入理解 Widget 的本质、特性及其在 Flutter 应用中的工作方式,为后续的界面开发打下坚实基础。

一、Widget 是什么?

Widget(组件)是 Flutter 框架中构建用户界面的基本单位,可以理解为屏幕上所有可见元素的抽象描述。从简单的文本、按钮,到复杂的布局、页面,甚至是应用的主题样式,在 Flutter 中都被表示为 Widget。

Widget 不仅仅是视觉元素的描述,它还包含了:

  • 界面元素的结构信息
  • 布局约束和规则
  • 绘制信息
  • 交互行为
  • 状态管理方式

与传统 UI 组件的区别

Flutter 的 Widget 与传统原生开发或其他跨平台框架的 UI 组件有本质区别:

  1. 描述性 vs 实例化

    • 传统组件:通常是直接实例化的对象,持有实际的渲染状态
    • Flutter Widget:是对 UI 的不可变描述,更像是一份 "蓝图" 或 "配置文件"
  2. 轻量级 vs 重量级

    • 传统组件:创建和销毁成本高,包含大量底层渲染逻辑
    • Flutter Widget:非常轻量,创建成本低,频繁重建不会显著影响性能
  3. 组合方式

    • 传统组件:通常通过继承扩展功能
    • Flutter Widget:几乎完全通过组合而非继承来构建复杂 UI
  4. 渲染方式

    • 传统组件:依赖平台原生控件渲染
    • Flutter Widget:由 Flutter 引擎直接渲染,不依赖平台控件

二、不可变 Widget 特性与重建机制

不可变特性(Immutability)

在 Flutter 中,几乎所有 Widget 都是不可变的(immutable),这意味着一旦创建,Widget 的属性(成员变量)就不能再被修改。所有 Widget 的属性都应该被声明为 final

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

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

  // 构造函数必须使用 const 或普通构造函数,不允许有修改属性的方法
  const MyWidget({super.key, required this.title, required this.count});

  @override
  Element createElement() {
    // TODO: implement createElement
    throw UnimplementedError();
  }
  // ...
}

不可变特性的优势

  • 简化状态管理:Widget 本身不存储可变状态
  • 提高性能:Flutter 可以快速比较 Widget 的变化
  • 线程安全:不可变对象可以安全地在多线程间传递
  • 易于测试和调试:状态变化可追踪

重建机制

由于 Widget 是不可变的,当需要更新 UI 时,Flutter 采用的是重建 Widget 树的方式,而不是修改现有 Widget。

这个过程类似于:

  1. 当状态变化时,创建新的 Widget 实例(包含新的属性值)
  2. Flutter 框架对比新旧 Widget 树(这个过程称为 "diffing")
  3. 只更新实际发生变化的部分到渲染树

热重载(Hot Reload)的原理就基于此:修改代码后,Flutter 会重建 Widget 树,但保留应用状态,从而实现快速预览效果。

StatelessWidget 与 StatefulWidget

根据是否需要管理状态,Widget 分为两大类:

  1. StatelessWidget(无状态组件)
    • 不包含可变状态
    • 一旦创建,其外观就完全由构造函数的参数决定
    • 适用于静态 UI 元素,如标题、图标等
    • 示例:TextIconContainer
dart 复制代码
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);
  }
}
  1. StatefulWidget(有状态组件)
    • 包含可变状态(State)
    • 状态变化会触发 UI 重建
    • 适用于需要交互或数据动态变化的场景
    • 示例:TextFieldCheckboxListView
dart 复制代码
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')),
      ],
    );
  }
}

setState() 方法是状态更新的关键,它会通知 Flutter 框架状态已改变,需要重建 Widget。


三、基础 Widget 介绍

Flutter 提供了丰富的基础 Widget,下面介绍几个最常用的:

1. Container

Container 是一个多功能容器 Widget,类似于 HTML 中的 div,可以包含一个子 Widget,并为其添加装饰、边距、padding 等。

dart 复制代码
Container(
  width: 200,                // 宽度
  height: 200,               // 高度
  margin: const EdgeInsets.all(20),  // 外边距
  padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),  // 内边距
  decoration: BoxDecoration(
    color: Colors.blue,      // 背景色
    borderRadius: BorderRadius.circular(10),  // 圆角
    boxShadow: const [       // 阴影
      BoxShadow(
        color: Colors.grey,
        blurRadius: 5,
        offset: Offset(2, 2),
      ),
    ],
  ),
  child: const Text(         // 子Widget
    'Hello Container',
    style: TextStyle(color: Colors.white, fontSize: 18),
  ),
  alignment: Alignment.center,  // 子Widget对齐方式
)

2. Text

Text 用于显示文本,可以通过 TextStyle 配置字体、大小、颜色等样式。

dart 复制代码
const Text(
  'Hello Flutter',
  style: TextStyle(
    fontSize: 24,            // 字体大小
    color: Colors.black87,   // 颜色
    fontWeight: FontWeight.bold,  // 字重
    fontStyle: FontStyle.italic,  // 字体样式
    decoration: TextDecoration.underline,  // 文本装饰
    decorationColor: Colors.red,  // 装饰线颜色
    letterSpacing: 2,        // 字间距
  ),
  textAlign: TextAlign.center,  // 对齐方式
  maxLines: 2,              // 最大行数
  overflow: TextOverflow.ellipsis,  // 溢出处理
)

3. Image

Image 用于显示图片,支持多种图片来源:

dart 复制代码
// 从网络加载
Image.network(
  'https://picsum.photos/200/300',
  width: 200,
  height: 300,
  fit: BoxFit.cover,  // 图片适配方式
  loadingBuilder: (context, child, loadingProgress) {
    if (loadingProgress == null) return child;
    return const Center(child: CircularProgressIndicator());
  },
  errorBuilder: (context, error, stackTrace) {
    return const Icon(Icons.error);
  },
)

// 从本地资源加载(需要在pubspec.yaml中配置)
Image.asset(
  'assets/images/logo.png',
  width: 100,
  height: 100,
)

// 从文件加载
Image.file(File('/path/to/image.jpg'))

// 从内存加载
Image.memory(Uint8List bytes)

使用本地资源图片需要在 pubspec.yaml 中配置:

yaml 复制代码
flutter:
  assets:
    - assets/images/logo.png
    - assets/icons/

4. Icon

Icon 用于显示图标,Flutter 内置了 Material Design 图标库,也支持自定义图标。

dart 复制代码
const Icon(
  Icons.favorite,    // 图标名称
  color: Colors.red, // 颜色
  size: 32,          // 大小
)

// 使用图标按钮
IconButton(
  icon: const Icon(Icons.share),
  onPressed: () {
  // 点击事件处理
  },
  tooltip: 'Share',  // 长按提示
)

可以通过 pubspec.yaml 添加自定义字体图标:

yaml 复制代码
flutter:
  fonts:
    - family: MyIcons
      fonts:
        - asset: fonts/my_icons.ttf

四、Widget 树结构与嵌套规则

Flutter 应用的界面是由 Widget 嵌套形成的树形结构,称为 "Widget 树"。理解 Widget 树的结构和嵌套规则是掌握 Flutter 布局的关键。

Widget 树结构

一个典型的 Flutter 应用 Widget 树结构如下:

plaintext 复制代码
MaterialApp
├── Scaffold
│   ├── AppBar
│   ├── Body
│   │   ├── Column
│   │   │   ├── Text
│   │   │   ├── Container
│   │   │   │   └── Image
│   │   │   └── ElevatedButton
│   │   └── ListView
│   │       ├── ListTile
│   │       └── ListTile
│   └── FloatingActionButton
└── Theme
  • 根节点通常是 MaterialAppCupertinoApp(提供基础应用框架)
  • 中间节点通常是布局类 Widget(如 ColumnRowStack
  • 叶子节点通常是展示类 Widget(如 TextImageIcon

嵌套规则

Flutter 采用 "组合优于继承" 的设计理念,通过 Widget 嵌套实现复杂 UI。嵌套时需要遵循以下规则:

  1. 单一子节点 Widget :只能包含一个子 Widget,如 ContainerPaddingCenter
dart 复制代码
Container(
  child: Text('Single child'),  // 只能有一个child
)
  1. 多子节点 Widget :可以包含多个子 Widget,通常通过 children 属性接收一个 Widget 列表
dart 复制代码
Column(
  children: [  // 多个子Widget放在列表中
    Text('First child'),
    Text('Second child'),
    ElevatedButton(onPressed: () {}, child: Text('Button')),
  ],
)
  1. 布局约束传递:父 Widget 会向子 Widget 传递布局约束(最大 / 最小宽高),子 Widget 在约束范围内决定自己的大小

  2. 上下文(Context)传递 :每个 Widget 都有一个 context,代表其在 Widget 树中的位置,可用于导航、获取主题等

常见布局 Widget

掌握以下布局 Widget 可以满足大多数界面需求:

  1. Row:水平排列子 Widget
  2. Column:垂直排列子 Widget
  3. Stack:层叠排列子 Widget
  4. ListView:可滚动的列表
  5. GridView:网格布局
  6. Expanded:在 Row/Column 中占满剩余空间
  7. Padding:添加内边距
  8. Center:居中对齐子 Widget
  9. Align:自定义对齐方式

示例:结合使用多种布局 Widget

dart 复制代码
Column(
  children: [
    const Text('User Profile', style: TextStyle(fontSize: 20)),
    const SizedBox(height: 16),  // 间距
      Row(
        mainAxisAlignment: MainAxisAlignment.center,  // 水平居中
        children: [
          Image.network(
          'https://picsum.photos/100/100',
          width: 100,
          height: 100,
          ),
          const SizedBox(width: 16),  // 水平间距
          const Column(
            crossAxisAlignment: CrossAxisAlignment.start,  // 左对齐
            children: [
              Text('John Doe'),
              Text('john@example.com'),
            ],
          ),
        ],
        ),
    const Expanded(  // 占满剩余空间
      child: Center(
        child: Text('Profile details will appear here'),
      ),
    ),
  ],
)

五、Widget 生命周期

虽然 Widget 本身是不可变的,但 StatefulWidget 关联的 State 对象有明确的生命周期:

  1. 创建阶段

    • createState():创建 State 对象
    • initState():初始化状态,只调用一次
  2. 构建阶段

    • build():构建 Widget 树,每次状态变化都会调用
    • didUpdateWidget():当父 Widget 重建导致子 Widget 变化时调用
  3. 活跃阶段

    • setState():触发状态更新和重建
    • didChangeDependencies():当依赖的 InheritedWidget 变化时调用
  4. 销毁阶段

    • deactivate():Widget 即将从树中移除时调用
    • dispose():Widget 被永久移除时调用,用于清理资源

理解生命周期有助于正确管理资源和状态,避免内存泄漏。

相关推荐
叽哥10 分钟前
flutter学习第 5 节:文本与样式
android·flutter·ios
RaidenLiu41 分钟前
Flutter 状态管理:Provider 入门与实战
前端·flutter
吧嗒贝斯1 小时前
Flutter项目中接入百度地图(2025 最新 Android版)
flutter
鹏多多.1 小时前
flutter-使用AnimatedDefaultTextStyle实现文本动画
android·前端·css·flutter·ios·html5·web
新镜1 小时前
Flutter报错...Unsupported class file major version 65
flutter
似霰2 小时前
安卓系统属性之androidboot.xxx转换成ro.boot.xxx
android·gitee
0wioiw02 小时前
Android-Kotlin基础(Jetpack①-ViewModel)
android
用户2018792831673 小时前
限定参数范围的注解之 "咖啡店定价" 的故事
android·java
xzkyd outpaper4 小时前
Android中视图测量、布局、绘制过程
android
泓博4 小时前
Android底部导航栏图标变黑色
android