【Flutter Todo 实战】第七章:UI 组件详解

· 5 min read

本章将详细介绍待办事项应用中的各个 UI 组件,帮助你理解 Flutter 界面是如何构建的。

7.1 主入口 main.dart

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 设置首选方向(竖屏)
  SystemChrome.setPreferredOrientations([
    DeviceOrientation.portraitUp,
    DeviceOrientation.portraitDown,
  ]);
  
  runApp(const TodoApp());
}

class TodoApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => TaskProvider(),
      child: MaterialApp(
        title: '待办事项',
        debugShowCheckedModeBanner: false,
        theme: _buildLightTheme(),
        darkTheme: _buildDarkTheme(),
        themeMode: ThemeMode.system,
        home: const HomeScreen(),
      ),
    );
  }
}

MaterialApp 配置

属性 作用
title 应用标题(显示在任务管理器中)
debugShowCheckedModeBanner 是否显示调试横幅
theme 亮色主题
darkTheme 暗色主题
themeMode 主题模式(system/light/dark)
home 应用首页

7.2 主页面 HomeScreen

HomeScreen 是应用的主页面,包含:

  • 可折叠的 AppBar
  • 筛选芯片栏
  • 任务列表
  • 浮动操作按钮

页面结构

class HomeScreen extends StatefulWidget {
  @override
  State createState() => _HomeScreenState();
}

class _HomeScreenState extends State {
  @override
  void initState() {
    super.initState();
    // 页面初始化后加载任务
    WidgetsBinding.instance.addPostFrameCallback((_) {
      context.read().loadTasks();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          SliverAppBar(...),      // 可折叠 AppBar
          SliverToBoxAdapter(...), // 筛选芯片
          SliverList(...),         // 任务列表
        ],
      ),
      floatingActionButton: FloatingActionButton(...),
    );
  }
}

Sliver 系列组件

Sliver 是 Flutter 中用于实现可滚动效果的特殊组件。

普通滚动:ListView
┌─────────────────┐
│    AppBar       │  ← 固定
├─────────────────┤
│                 │
│   可滚动内容     │  ← 一起滚动
│                 │
└─────────────────┘

Sliver 滚动:CustomScrollView + Slivers
┌─────────────────┐
│  SliverAppBar   │  ← 可折叠
│  (滚动时缩小)   │
├─────────────────┤
│                 │
│   SliverList    │  ← 统一滚动
│                 │
└─────────────────┘

SliverAppBar

SliverAppBar(
  expandedHeight: 120,    // 展开高度
  floating: true,         // 滚动时是否浮动显示
  pinned: true,           // 是否固定
  flexibleSpace: FlexibleSpaceBar(
    title: Text('待办事项'),
    centerTitle: true,
  ),
  actions: [
    IconButton(...),  // 清除已完成按钮
  ],
)

7.3 任务项 TaskItem

TaskItem 是显示单个任务的组件,支持:

  • 点击切换完成状态
  • 左滑删除
  • 编辑和删除按钮

组件结构

class TaskItem extends StatelessWidget {
  final Task task;
  final VoidCallback onToggle;
  final VoidCallback onDelete;
  final Function(String) onEdit;

  @override
  Widget build(BuildContext context) {
    return Dismissible(    // 滑动删除
      key: Key(task.id),
      direction: DismissDirection.endToStart,
      background: Container(color: Colors.red),
      onDismissed: (_) => onDelete(),
      child: Card(         // 卡片容器
        child: ListTile(   // 列表项
          leading: _buildCheckbox(),
          title: _buildTitle(),
          subtitle: _buildSubtitle(),
          trailing: _buildActions(),
        ),
      ),
    );
  }
}

Dismissible(滑动删除)

Dismissible(
  key: Key(task.id),  // 必须提供唯一 key
  direction: DismissDirection.endToStart,  // 从右向左滑动
  background: Container(
    color: Colors.red,
    child: Icon(Icons.delete, color: Colors.white),
  ),
  onDismissed: (_) {
    // 滑动完成后删除
    onDelete();
  },
  child: Card(...),
)

自定义复选框

Widget _buildCheckbox(ColorScheme colorScheme) {
  return InkWell(
    onTap: onToggle,
    child: Container(
      width: 28,
      height: 28,
      decoration: BoxDecoration(
        shape: BoxShape.circle,
        border: Border.all(
          color: task.isCompleted 
              ? colorScheme.primary 
              : colorScheme.outline,
        ),
        color: task.isCompleted 
            ? colorScheme.primary 
            : Colors.transparent,
      ),
      child: task.isCompleted
          ? Icon(Icons.check, size: 16)
          : null,
    ),
  );
}

效果:

未完成:    已完成:
┌────┐     ┌────┐
│    │     │ ✓  │
└────┘     └────┘
圆圈       填充圆圈

带删除线的标题

Text(
  task.title,
  style: TextStyle(
    decoration: task.isCompleted 
        ? TextDecoration.lineThrough 
        : null,
    color: task.isCompleted 
        ? Colors.grey 
        : Colors.black,
  ),
)

7.4 添加任务对话框 AddTaskDialog

对话框结构

class AddTaskDialog extends StatefulWidget {
  @override
  _AddTaskDialogState createState() => _AddTaskDialogState();
}

class _AddTaskDialogState extends State {
  final _controller = TextEditingController();
  DateTime? _selectedDate;

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Text('添加新任务'),
      content: Column(
        children: [
          TextField(           // 任务输入框
            controller: _controller,
            decoration: InputDecoration(
              labelText: '任务内容',
              hintText: '输入你要完成的任务...',
            ),
          ),
          _buildDateSelector(), // 日期选择器
        ],
      ),
      actions: [
        TextButton(          // 取消按钮
          onPressed: () => Navigator.pop(context),
          child: Text('取消'),
        ),
        FilledButton(        // 添加按钮
          onPressed: _submit,
          child: Text('添加'),
        ),
      ],
    );
  }
}

日期选择器

Future _pickDate() async {
  final picked = await showDatePicker(
    context: context,
    initialDate: DateTime.now(),
    firstDate: DateTime.now(),
    lastDate: DateTime.now().add(Duration(days: 365 * 5)),
  );

  if (picked != null) {
    setState(() {
      _selectedDate = picked;
    });
  }
}

返回数据

void _submit() {
  final text = _controller.text.trim();
  if (text.isEmpty) return;

  // 返回数据并关闭对话框
  Navigator.pop(context, {
    'title': text,
    'dueDate': _selectedDate,
  });
}

使用:

final result = await showDialog(
  context: context,
  builder: (context) => AddTaskDialog(),
);

if (result != null) {
  provider.addTask(
    result['title'],
    dueDate: result['dueDate'],
  );
}

7.5 筛选栏 FilterChipBar

组件结构

class FilterChipBar extends StatelessWidget {
  final TaskFilter currentFilter;
  final Function(TaskFilter) onFilterChanged;
  final int allCount;
  final int activeCount;
  final int completedCount;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        _buildFilterChip(TaskFilter.all, allCount),
        _buildFilterChip(TaskFilter.active, activeCount),
        _buildFilterChip(TaskFilter.completed, completedCount),
      ],
    );
  }
}

FilterChip 使用

Widget _buildFilterChip(TaskFilter filter, int count) {
  final isSelected = currentFilter == filter;
  
  return FilterChip(
    selected: isSelected,
    label: Row(
      children: [
        Text(filter.label),
        Container(
          padding: EdgeInsets.symmetric(horizontal: 6),
          decoration: BoxDecoration(
            color: isSelected ? Colors.blue : Colors.grey,
            borderRadius: BorderRadius.circular(10),
          ),
          child: Text(count.toString()),
        ),
      ],
    ),
    onSelected: (_) => onFilterChanged(filter),
  );
}

效果:

[ 全部 3 ] [ 进行中 2 ] [ 已完成 1 ]
   选中      未选中      未选中

7.6 空状态 EmptyState

当没有任务时显示友好的提示:

class EmptyState extends StatelessWidget {
  final bool isFiltered;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(
            isFiltered ? Icons.filter_list_off : Icons.check_circle_outline,
            size: 60,
            color: Colors.grey,
          ),
          SizedBox(height: 16),
          Text(
            isFiltered ? '没有符合条件的任务' : '还没有任务',
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
          SizedBox(height: 8),
          Text(
            isFiltered 
                ? '尝试切换其他筛选条件' 
                : '点击右下角按钮添加第一个任务吧!',
            style: TextStyle(color: Colors.grey),
          ),
        ],
      ),
    );
  }
}

7.7 常用 Widget 速查

布局 Widget

Widget 作用
Container 通用容器,可设置宽高、背景、边距
Row 水平排列子组件
Column 垂直排列子组件
Stack 层叠排列子组件
Expanded 在 Row/Column 中占据剩余空间
SizedBox 固定尺寸的占位
Padding 添加内边距
Center 居中子组件

交互 Widget

Widget 作用
GestureDetector 检测各种手势
InkWell 带涟漪效果的点击区域
ElevatedButton 凸起按钮
TextButton 文本按钮
FilledButton 填充按钮
IconButton 图标按钮
FloatingActionButton 浮动操作按钮

输入 Widget

Widget 作用
TextField 文本输入框
TextFormField 带验证的文本输入框
Checkbox 复选框
Switch 开关
Slider 滑块
DropdownButton 下拉选择

显示 Widget

Widget 作用
Text 显示文本
Icon 显示图标
Image 显示图片
Card 卡片容器
ListTile 列表项
Divider 分割线
CircularProgressIndicator 圆形进度指示器
LinearProgressIndicator 线性进度指示器

7.8 小结

本章介绍了应用的主要 UI 组件:

  1. MaterialApp - 应用根组件,配置主题和路由
  2. Scaffold - 页面基本结构
  3. SliverAppBar - 可折叠的顶部栏
  4. Dismissible - 滑动删除
  5. AlertDialog - 对话框
  6. FilterChip - 筛选芯片
  7. EmptyState - 空状态提示

下一步第八章:关键技术深入

Related Articles