首页/文章/flutter

Flutter 常用 Widget 入门

2026-07-01
682223 分钟
...

Flutter 的核心思想是:一切皆 Widget。文字、图片、按钮、页面结构、布局、间距、滚动、动画、手势,几乎都由 Widget 组合出来。

1. Widget 先分成几类理解

类别常用 Widget解决什么问题
应用入口MaterialAppCupertinoApp搭建应用外壳、主题、路由
页面骨架ScaffoldAppBarSafeArea标准页面结构
基础显示TextIconImage显示文字、图标、图片
布局容器ContainerPaddingCenterSizedBox控制尺寸、间距、装饰、位置
线性布局RowColumnFlex横向或纵向排列
弹性布局ExpandedFlexibleSpacer分配剩余空间
层叠布局StackPositioned元素重叠、角标、浮层
流式布局Wrap自动换行标签、按钮组
滚动列表ListViewGridViewSingleChildScrollView长列表、宫格、普通滚动页
表单输入TextFieldFormTextFormField输入框、表单校验
按钮ElevatedButtonTextButtonOutlinedButtonIconButton用户操作
卡片列表CardListTileDivider列表项、设置页、信息卡片
交互反馈SnackBarDialogBottomSheet提示、弹窗、底部面板
状态构建FutureBuilderStreamBuilderValueListenableBuilder根据异步或状态构建 UI
适配布局MediaQueryLayoutBuilder响应式和尺寸判断
手势GestureDetectorInkWell点击、长按、拖动
动画AnimatedContainerAnimatedOpacityHero简单动画和转场

2. StatelessWidget 和 StatefulWidget

2.1 StatelessWidget

StatelessWidget 表示这个组件本身没有内部状态。它依赖外部传入的数据来展示 UI。

class UserName extends StatelessWidget {
  final String name;

  const UserName({
    super.key,
    required this.name,
  });

  @override
  Widget build(BuildContext context) {
    return Text('你好,$name');
  }
}

适合:

  • 纯展示组件
  • 传什么显示什么
  • 不需要 setState

2.2 StatefulWidget

StatefulWidget 表示组件内部有会变化的状态。

class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State<CounterPage> createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('count: $count'),
        ElevatedButton(
          onPressed: () {
            setState(() {
              count++;
            });
          },
          child: const Text('加一'),
        ),
      ],
    );
  }
}

适合:

  • 输入框内容变化
  • tab 切换
  • 开关状态
  • 页面局部刷新
  • 动画控制

简单判断:如果 UI 会因为自身内部数据改变而刷新,就用 StatefulWidget

3. 应用入口 Widget

3.1 MaterialApp

MaterialApp 是 Material 风格应用的入口,绝大多数 Flutter 项目都会用它。

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

常用属性:

属性说明
title应用标题
theme全局主题
darkTheme暗色主题
home首页
routes命名路由
debugShowCheckedModeBanner是否显示右上角 debug 标识

3.2 CupertinoApp

CupertinoApp 是 iOS 风格应用入口。如果你要做纯 iOS 风格,可以用它。

class MyIosApp extends StatelessWidget {
  const MyIosApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      home: CupertinoPageScaffold(
        navigationBar: CupertinoNavigationBar(
          middle: Text('首页'),
        ),
        child: Center(
          child: Text('Hello Cupertino'),
        ),
      ),
    );
  }
}

大多数跨平台应用可以先从 MaterialApp 开始。

4. 页面骨架 Widget

4.1 Scaffold

Scaffold 是一个标准页面骨架。它提供了顶部栏、主体、抽屉、悬浮按钮、底部导航、SnackBar 等常见结构。

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('首页'),
      ),
      body: const Center(
        child: Text('页面内容'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        child: const Icon(Icons.add),
      ),
    );
  }
}

常用属性:

属性说明
appBar顶部导航栏
body页面主体
drawer左侧抽屉
endDrawer右侧抽屉
bottomNavigationBar底部导航
floatingActionButton悬浮按钮
backgroundColor页面背景色

4.2 AppBar

AppBar 一般放在 Scaffold.appBar

AppBar(
  title: const Text('文章详情'),
  centerTitle: true,
  actions: [
    IconButton(
      onPressed: () {},
      icon: const Icon(Icons.share),
    ),
  ],
)

常用属性:

属性说明
title标题
leading左侧按钮
actions右侧按钮列表
centerTitle标题是否居中
backgroundColor背景色
elevation阴影高度

4.3 SafeArea

SafeArea 用来避开刘海屏、状态栏、底部手势条。

Scaffold(
  body: SafeArea(
    child: Text('不会贴到状态栏'),
  ),
)

常用场景:

  • 自定义顶部区域
  • 没有使用 AppBar 的页面
  • 全屏页面里避免内容被遮挡

5. 基础显示 Widget

5.1 Text

Text 用来显示文字。

const Text(
  'Hello Flutter',
  style: TextStyle(
    fontSize: 20,
    fontWeight: FontWeight.bold,
    color: Colors.blue,
  ),
)

常用属性:

属性说明
style文字样式
textAlign对齐方式
maxLines最大行数
overflow超出处理
softWrap是否自动换行

单行省略:

const Text(
  '这是一段很长很长的文字',
  maxLines: 1,
  overflow: TextOverflow.ellipsis,
)

5.2 RichText 和 TextSpan

需要一段文字里有不同样式时,用 RichText

RichText(
  text: const TextSpan(
    style: TextStyle(color: Colors.black, fontSize: 16),
    children: [
      TextSpan(text: '价格:'),
      TextSpan(
        text: '¥99',
        style: TextStyle(
          color: Colors.red,
          fontWeight: FontWeight.bold,
        ),
      ),
    ],
  ),
)

5.3 Icon

const Icon(
  Icons.favorite,
  color: Colors.red,
  size: 32,
)

常用场景:

  • 按钮图标
  • 列表项前缀
  • 状态提示
  • 空状态页面

5.4 Image

网络图片:

Image.network(
  'https://example.com/avatar.png',
  width: 80,
  height: 80,
  fit: BoxFit.cover,
)

本地资源图片:

Image.asset(
  'assets/images/avatar.png',
  width: 80,
  height: 80,
)

pubspec.yaml 里需要声明资源:

flutter:
  assets:
    - assets/images/

常用属性:

属性说明
width宽度
height高度
fit图片填充方式
color图片着色
alignment对齐方式

常用 BoxFit

含义
BoxFit.cover填满容器,可能裁剪
BoxFit.contain完整显示,可能留空
BoxFit.fill拉伸填满,可能变形
BoxFit.fitWidth按宽度适配
BoxFit.fitHeight按高度适配

6. 容器和间距 Widget

6.1 Container

Container 是最常用的容器。它可以控制宽高、边距、内边距、背景色、圆角、边框、阴影等。

Container(
  width: 120,
  height: 48,
  padding: const EdgeInsets.symmetric(horizontal: 16),
  margin: const EdgeInsets.only(top: 12),
  alignment: Alignment.center,
  decoration: BoxDecoration(
    color: Colors.blue,
    borderRadius: BorderRadius.circular(8),
  ),
  child: const Text(
    '按钮样式',
    style: TextStyle(color: Colors.white),
  ),
)

常用属性:

属性说明
width / height宽高
padding内边距
margin外边距
alignment子元素对齐
color背景色
decoration装饰,圆角、边框、渐变、阴影
child子 Widget

注意:colordecoration 里同时设置颜色会冲突。复杂样式统一放到 decoration

6.2 Padding

只需要加内边距时,用 Padding 更清晰。

const Padding(
  padding: EdgeInsets.all(16),
  child: Text('有内边距的文字'),
)

常用写法:

EdgeInsets.all(16)
EdgeInsets.symmetric(horizontal: 16, vertical: 8)
EdgeInsets.only(left: 16, top: 8)

6.3 SizedBox

SizedBox 常用于固定尺寸或制造间距。

const SizedBox(height: 12)

固定子元素尺寸:

SizedBox(
  width: 200,
  height: 48,
  child: ElevatedButton(
    onPressed: () {},
    child: const Text('提交'),
  ),
)

6.4 Center

const Center(
  child: Text('居中显示'),
)

6.5 Align

Align 可以控制子元素在父容器中的位置。

const Align(
  alignment: Alignment.topRight,
  child: Icon(Icons.close),
)

6.6 ClipRRect

给图片裁剪圆角时很常用。

ClipRRect(
  borderRadius: BorderRadius.circular(12),
  child: Image.network(
    'https://example.com/image.png',
    width: 120,
    height: 80,
    fit: BoxFit.cover,
  ),
)

7. Row、Column、Expanded

7.1 Row

Row 横向排列子元素。

Row(
  children: const [
    Icon(Icons.person),
    SizedBox(width: 8),
    Text('用户名'),
  ],
)

常用属性:

属性说明
mainAxisAlignment主轴对齐,Row 中是横向
crossAxisAlignment交叉轴对齐,Row 中是纵向
children子元素列表

7.2 Column

Column 纵向排列子元素。

Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: const [
    Text('标题'),
    SizedBox(height: 8),
    Text('描述内容'),
  ],
)

7.3 主轴和交叉轴

Widget主轴交叉轴
Row横向纵向
Column纵向横向

常见对齐:

mainAxisAlignment: MainAxisAlignment.center
mainAxisAlignment: MainAxisAlignment.spaceBetween
mainAxisAlignment: MainAxisAlignment.spaceAround
mainAxisAlignment: MainAxisAlignment.spaceEvenly

crossAxisAlignment: CrossAxisAlignment.start
crossAxisAlignment: CrossAxisAlignment.center
crossAxisAlignment: CrossAxisAlignment.stretch

7.4 Expanded

Expanded 会占满剩余空间。它只能放在 RowColumnFlex 里面。

Row(
  children: [
    const Icon(Icons.search),
    const SizedBox(width: 8),
    Expanded(
      child: TextField(
        decoration: const InputDecoration(
          hintText: '请输入关键词',
        ),
      ),
    ),
  ],
)

常见用途:

  • 一行里左边固定,右边自适应
  • 搜索框占满剩余宽度
  • 多列等分

等分布局:

Row(
  children: [
    Expanded(child: Container(height: 40, color: Colors.red)),
    Expanded(child: Container(height: 40, color: Colors.blue)),
  ],
)

7.5 Flexible

FlexibleExpanded 更灵活。Expanded 强制占满剩余空间,Flexible 可以不占满。

Row(
  children: const [
    Flexible(
      child: Text(
        '这是一段可能很长的文字',
        overflow: TextOverflow.ellipsis,
      ),
    ),
    Icon(Icons.chevron_right),
  ],
)

7.6 Spacer

Spacer 用来撑开空白。

Row(
  children: const [
    Text('左侧'),
    Spacer(),
    Text('右侧'),
  ],
)

8. Stack 和 Positioned

Stack 用于层叠布局,常见于角标、图片遮罩、浮动按钮。

Stack(
  children: [
    Image.network(
      'https://example.com/banner.png',
      width: double.infinity,
      height: 180,
      fit: BoxFit.cover,
    ),
    Positioned(
      right: 12,
      bottom: 12,
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
        decoration: BoxDecoration(
          color: Colors.black54,
          borderRadius: BorderRadius.circular(8),
        ),
        child: const Text(
          '热门',
          style: TextStyle(color: Colors.white),
        ),
      ),
    ),
  ],
)

Positioned 只能作为 Stack 的直接子元素。

常用属性:

属性说明
left距离左边
right距离右边
top距离顶部
bottom距离底部
width宽度
height高度

9. Wrap

Wrap 会在空间不够时自动换行,适合标签列表。

Wrap(
  spacing: 8,
  runSpacing: 8,
  children: const [
    Chip(label: Text('Flutter')),
    Chip(label: Text('Dart')),
    Chip(label: Text('Widget')),
    Chip(label: Text('状态管理')),
  ],
)

常用属性:

属性说明
spacing主轴间距
runSpacing换行后的行间距
alignment主轴对齐
runAlignment多行对齐

10. 滚动 Widget

10.1 SingleChildScrollView

适合内容不多,但可能超出屏幕的页面,比如详情页、表单页。

SingleChildScrollView(
  padding: const EdgeInsets.all(16),
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: const [
      Text('标题'),
      SizedBox(height: 12),
      Text('很多内容...'),
    ],
  ),
)

注意:长列表不要用 SingleChildScrollView + Column,性能不好。长列表用 ListView.builder

10.2 ListView

少量固定元素:

ListView(
  padding: const EdgeInsets.all(16),
  children: const [
    Text('第一项'),
    Text('第二项'),
    Text('第三项'),
  ],
)

大量数据用 ListView.builder

ListView.builder(
  itemCount: 100,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text('第 $index 项'),
    );
  },
)

ListView.builder 会按需构建列表项,更适合长列表。

10.3 GridView

宫格布局:

GridView.builder(
  padding: const EdgeInsets.all(16),
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    crossAxisSpacing: 12,
    mainAxisSpacing: 12,
    childAspectRatio: 1.2,
  ),
  itemCount: 20,
  itemBuilder: (context, index) {
    return Container(
      alignment: Alignment.center,
      color: Colors.blue.shade100,
      child: Text('Item $index'),
    );
  },
)

常用属性:

属性说明
crossAxisCount每行几个
crossAxisSpacing横向间距
mainAxisSpacing纵向间距
childAspectRatio子项宽高比

10.4 PageView

横向分页,常用于引导页、轮播、图片预览。

PageView(
  children: const [
    Center(child: Text('第 1 页')),
    Center(child: Text('第 2 页')),
    Center(child: Text('第 3 页')),
  ],
)

配合控制器:

final controller = PageController();

StatefulWidget 中记得释放:

@override
void dispose() {
  controller.dispose();
  super.dispose();
}

10.5 RefreshIndicator

下拉刷新:

RefreshIndicator(
  onRefresh: () async {
    await Future.delayed(const Duration(seconds: 1));
  },
  child: ListView.builder(
    itemCount: 20,
    itemBuilder: (context, index) {
      return ListTile(
        title: Text('Item $index'),
      );
    },
  ),
)

注意:RefreshIndicator 的 child 一般要是可滚动组件。

11. 按钮 Widget

11.1 ElevatedButton

强调按钮,适合主要操作。

ElevatedButton(
  onPressed: () {},
  child: const Text('提交'),
)

11.2 TextButton

文字按钮,适合次要操作。

TextButton(
  onPressed: () {},
  child: const Text('取消'),
)

11.3 OutlinedButton

描边按钮。

OutlinedButton(
  onPressed: () {},
  child: const Text('查看更多'),
)

11.4 IconButton

图标按钮。

IconButton(
  onPressed: () {},
  icon: const Icon(Icons.settings),
)

11.5 FloatingActionButton

悬浮按钮,一般用于页面主操作。

FloatingActionButton(
  onPressed: () {},
  child: const Icon(Icons.add),
)

按钮禁用:把 onPressed 设置为 null

ElevatedButton(
  onPressed: null,
  child: const Text('不可点击'),
)

12. 输入和表单 Widget

12.1 TextField

普通输入框。

TextField(
  decoration: const InputDecoration(
    labelText: '用户名',
    hintText: '请输入用户名',
    border: OutlineInputBorder(),
  ),
  onChanged: (value) {
    print(value);
  },
)

常用属性:

属性说明
controller控制输入内容
decoration输入框样式
keyboardType键盘类型
obscureText是否隐藏文字,常用于密码
maxLines最大行数
onChanged内容变化
onSubmitted提交

12.2 TextEditingController

需要主动读取或设置输入框内容时,用 controller。

class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  @override
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  final usernameController = TextEditingController();

  @override
  void dispose() {
    usernameController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: usernameController,
      decoration: const InputDecoration(
        labelText: '用户名',
      ),
    );
  }
}

只要自己创建了 controller,通常就要在 dispose 里释放。

12.3 Form 和 TextFormField

需要表单校验时,用 Form

class LoginForm extends StatefulWidget {
  const LoginForm({super.key});

  @override
  State<LoginForm> createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
  final formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Form(
      key: formKey,
      child: Column(
        children: [
          TextFormField(
            decoration: const InputDecoration(
              labelText: '邮箱',
              border: OutlineInputBorder(),
            ),
            validator: (value) {
              if (value == null || value.isEmpty) {
                return '请输入邮箱';
              }
              if (!value.contains('@')) {
                return '邮箱格式不正确';
              }
              return null;
            },
          ),
          const SizedBox(height: 12),
          ElevatedButton(
            onPressed: () {
              final valid = formKey.currentState?.validate() ?? false;
              if (valid) {
                print('表单通过');
              }
            },
            child: const Text('登录'),
          ),
        ],
      ),
    );
  }
}

13. 卡片和列表项 Widget

13.1 Card

Card(
  child: Padding(
    padding: const EdgeInsets.all(16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: const [
        Text(
          '卡片标题',
          style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
        ),
        SizedBox(height: 8),
        Text('卡片内容'),
      ],
    ),
  ),
)

适合:

  • 商品卡片
  • 文章卡片
  • 设置分组
  • 用户信息块

13.2 ListTile

ListTile 是很常用的列表项组件。

ListTile(
  leading: const CircleAvatar(
    child: Icon(Icons.person),
  ),
  title: const Text('用户名'),
  subtitle: const Text('这是一段简介'),
  trailing: const Icon(Icons.chevron_right),
  onTap: () {},
)

常用属性:

属性说明
leading左侧区域
title主标题
subtitle副标题
trailing右侧区域
onTap点击事件

13.3 Divider

分割线。

const Divider(height: 1)

配合列表:

ListView.separated(
  itemCount: 20,
  separatorBuilder: (context, index) => const Divider(height: 1),
  itemBuilder: (context, index) {
    return ListTile(
      title: Text('Item $index'),
    );
  },
)

14. 交互反馈 Widget

14.1 SnackBar

页面底部轻提示。

ScaffoldMessenger.of(context).showSnackBar(
  const SnackBar(
    content: Text('保存成功'),
  ),
);

通常用于:

  • 保存成功
  • 操作失败
  • 网络错误
  • 撤销提示

14.2 AlertDialog

弹窗确认。

showDialog<void>(
  context: context,
  builder: (context) {
    return AlertDialog(
      title: const Text('提示'),
      content: const Text('确定要删除吗?'),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('取消'),
        ),
        ElevatedButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('确定'),
        ),
      ],
    );
  },
);

14.3 BottomSheet

底部弹出面板。

showModalBottomSheet<void>(
  context: context,
  builder: (context) {
    return SafeArea(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          ListTile(
            leading: const Icon(Icons.photo),
            title: const Text('选择图片'),
            onTap: () => Navigator.pop(context),
          ),
          ListTile(
            leading: const Icon(Icons.camera_alt),
            title: const Text('拍照'),
            onTap: () => Navigator.pop(context),
          ),
        ],
      ),
    );
  },
);

适合:

  • 分享面板
  • 操作菜单
  • 筛选器
  • 选择器

15. 手势 Widget

15.1 GestureDetector

GestureDetector 可以给任意 Widget 添加手势。

GestureDetector(
  onTap: () {
    print('点击');
  },
  onLongPress: () {
    print('长按');
  },
  child: Container(
    width: 120,
    height: 48,
    alignment: Alignment.center,
    color: Colors.blue,
    child: const Text(
      '点我',
      style: TextStyle(color: Colors.white),
    ),
  ),
)

常用事件:

事件说明
onTap点击
onDoubleTap双击
onLongPress长按
onPanUpdate拖动
onScaleUpdate缩放

15.2 InkWell

InkWell 带 Material 水波纹效果。

Material(
  color: Colors.transparent,
  child: InkWell(
    borderRadius: BorderRadius.circular(8),
    onTap: () {},
    child: const Padding(
      padding: EdgeInsets.all(12),
      child: Text('带水波纹的点击区域'),
    ),
  ),
)

如果你希望按钮或卡片点击时有 Material 风格反馈,优先考虑 InkWell

16. 异步和状态构建 Widget

16.1 FutureBuilder

FutureBuilder 用于根据异步请求状态构建 UI。

Future<String> fetchName() async {
  await Future.delayed(const Duration(seconds: 1));
  return 'Mika';
}

class UserNameView extends StatelessWidget {
  const UserNameView({super.key});

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<String>(
      future: fetchName(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const CircularProgressIndicator();
        }

        if (snapshot.hasError) {
          return Text('出错了:${snapshot.error}');
        }

        return Text('用户名:${snapshot.data}');
      },
    );
  }
}

注意:真实项目里不要在频繁重建的 build 中直接创建新的请求。更稳的方式是在 initState 里保存 Future

class UserNamePage extends StatefulWidget {
  const UserNamePage({super.key});

  @override
  State<UserNamePage> createState() => _UserNamePageState();
}

class _UserNamePageState extends State<UserNamePage> {
  late final Future<String> nameFuture;

  @override
  void initState() {
    super.initState();
    nameFuture = fetchName();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<String>(
      future: nameFuture,
      builder: (context, snapshot) {
        if (!snapshot.hasData) {
          return const CircularProgressIndicator();
        }

        return Text(snapshot.data!);
      },
    );
  }
}

16.2 StreamBuilder

StreamBuilder 用于持续数据,比如倒计时、WebSocket、实时消息。

Stream<int> countdown() async* {
  for (var i = 5; i >= 0; i--) {
    await Future.delayed(const Duration(seconds: 1));
    yield i;
  }
}

StreamBuilder<int>(
  stream: countdown(),
  builder: (context, snapshot) {
    return Text('倒计时:${snapshot.data ?? 5}');
  },
)

16.3 ValueListenableBuilder

适合监听 ValueNotifier

final countNotifier = ValueNotifier<int>(0);

ValueListenableBuilder<int>(
  valueListenable: countNotifier,
  builder: (context, count, child) {
    return Text('count: $count');
  },
)

更新:

countNotifier.value++;

ValueNotifier + ValueListenableBuilder 很适合小范围状态,不一定一上来就用大型状态管理。

16.4 Builder

Builder 可以创建一个新的 BuildContext。当你需要拿到某个 Widget 下方的新 context 时有用。

Builder(
  builder: (context) {
    return ElevatedButton(
      onPressed: () {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('Hello')),
        );
      },
      child: const Text('显示提示'),
    );
  },
)

17. 响应式和约束 Widget

17.1 MediaQuery

获取屏幕尺寸、像素密度、系统字体缩放等。

final size = MediaQuery.sizeOf(context);
final width = size.width;

简单适配:

Widget build(BuildContext context) {
  final width = MediaQuery.sizeOf(context).width;
  final isWide = width >= 600;

  return isWide ? const DesktopLayout() : const MobileLayout();
}

17.2 LayoutBuilder

LayoutBuilder 获取父容器给当前 Widget 的约束,比直接看屏幕宽度更适合组件内部响应式。

LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth > 600) {
      return const Row(
        children: [
          Expanded(child: Text('左侧')),
          Expanded(child: Text('右侧')),
        ],
      );
    }

    return const Column(
      children: [
        Text('上方'),
        Text('下方'),
      ],
    );
  },
)

17.3 AspectRatio

固定宽高比。

AspectRatio(
  aspectRatio: 16 / 9,
  child: Image.network(
    'https://example.com/banner.png',
    fit: BoxFit.cover,
  ),
)

17.4 FittedBox

把子元素缩放到合适大小。

const FittedBox(
  child: Text('很大的文字'),
)

常用于:

  • 大数字适配
  • 标签文字不溢出
  • 图标和文字缩放

18. 动画 Widget

18.1 AnimatedContainer

最常用的隐式动画 Widget。

AnimatedContainer(
  duration: const Duration(milliseconds: 300),
  width: selected ? 160 : 100,
  height: 48,
  decoration: BoxDecoration(
    color: selected ? Colors.blue : Colors.grey,
    borderRadius: BorderRadius.circular(selected ? 24 : 8),
  ),
)

当属性变化时,它会自动执行动画。

18.2 AnimatedOpacity

透明度动画。

AnimatedOpacity(
  opacity: visible ? 1 : 0,
  duration: const Duration(milliseconds: 300),
  child: const Text('渐隐渐显'),
)

18.3 AnimatedSwitcher

切换子元素时自动动画。

AnimatedSwitcher(
  duration: const Duration(milliseconds: 300),
  child: Text(
    '$count',
    key: ValueKey(count),
    style: const TextStyle(fontSize: 32),
  ),
)

18.4 Hero

页面跳转共享元素动画。

Hero(
  tag: 'avatar-1',
  child: Image.network(
    'https://example.com/avatar.png',
    width: 80,
    height: 80,
  ),
)

两个页面里使用相同 tagHero,跳转时会自动做转场。

19. 加载、空状态和错误状态

19.1 CircularProgressIndicator

const Center(
  child: CircularProgressIndicator(),
)

19.2 LinearProgressIndicator

const LinearProgressIndicator()

19.3 空状态

const Center(
  child: Column(
    mainAxisSize: MainAxisSize.min,
    children: [
      Icon(Icons.inbox, size: 48, color: Colors.grey),
      SizedBox(height: 8),
      Text('暂无数据'),
    ],
  ),
)

19.4 错误状态

Center(
  child: Column(
    mainAxisSize: MainAxisSize.min,
    children: [
      const Text('加载失败'),
      const SizedBox(height: 8),
      ElevatedButton(
        onPressed: () {},
        child: const Text('重试'),
      ),
    ],
  ),
)

一个成熟页面至少要考虑:

  • loading
  • empty
  • error
  • success

20. 导航相关 Widget

20.1 Navigator

跳转页面:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const DetailPage(),
  ),
);

返回:

Navigator.pop(context);

带返回值:

final result = await Navigator.push<String>(
  context,
  MaterialPageRoute(
    builder: (context) => const SelectPage(),
  ),
);

在目标页返回:

Navigator.pop(context, '选中的值');

20.2 BottomNavigationBar

底部导航。

BottomNavigationBar(
  currentIndex: currentIndex,
  onTap: (index) {
    setState(() {
      currentIndex = index;
    });
  },
  items: const [
    BottomNavigationBarItem(
      icon: Icon(Icons.home),
      label: '首页',
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.person),
      label: '我的',
    ),
  ],
)

通常放在 Scaffold.bottomNavigationBar

20.3 TabBar 和 TabBarView

顶部 Tab。

DefaultTabController(
  length: 2,
  child: Scaffold(
    appBar: AppBar(
      title: const Text('Tab 示例'),
      bottom: const TabBar(
        tabs: [
          Tab(text: '推荐'),
          Tab(text: '关注'),
        ],
      ),
    ),
    body: const TabBarView(
      children: [
        Center(child: Text('推荐内容')),
        Center(child: Text('关注内容')),
      ],
    ),
  ),
)

21. 常用 Material Widget

21.1 Chip

标签。

const Chip(
  label: Text('Flutter'),
  avatar: Icon(Icons.tag),
)

21.2 Checkbox

Checkbox(
  value: checked,
  onChanged: (value) {
    setState(() {
      checked = value ?? false;
    });
  },
)

21.3 Switch

Switch(
  value: enabled,
  onChanged: (value) {
    setState(() {
      enabled = value;
    });
  },
)

21.4 Radio

Radio<String>(
  value: 'male',
  groupValue: gender,
  onChanged: (value) {
    setState(() {
      gender = value;
    });
  },
)

21.5 Slider

Slider(
  value: volume,
  min: 0,
  max: 100,
  divisions: 10,
  label: volume.round().toString(),
  onChanged: (value) {
    setState(() {
      volume = value;
    });
  },
)

22. 常用 Cupertino Widget

如果要做 iOS 风格,可以关注这些:

Widget说明
CupertinoAppiOS 风格应用入口
CupertinoPageScaffoldiOS 页面骨架
CupertinoNavigationBariOS 顶部导航
CupertinoButtoniOS 按钮
CupertinoSwitchiOS 开关
CupertinoTextFieldiOS 输入框
CupertinoAlertDialogiOS 弹窗
CupertinoActionSheetiOS 操作面板
CupertinoActivityIndicatoriOS loading
CupertinoTabScaffoldiOS tab 页面

示例:

CupertinoButton(
  color: CupertinoColors.activeBlue,
  onPressed: () {},
  child: const Text('确认'),
)

需要导入:

import 'package:flutter/cupertino.dart';

23. 常见页面组合示例

23.1 普通列表页

class ArticleListPage extends StatelessWidget {
  const ArticleListPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('文章列表'),
      ),
      body: ListView.separated(
        itemCount: 20,
        separatorBuilder: (context, index) => const Divider(height: 1),
        itemBuilder: (context, index) {
          return ListTile(
            title: Text('文章标题 $index'),
            subtitle: const Text('这里是文章摘要'),
            trailing: const Icon(Icons.chevron_right),
            onTap: () {},
          );
        },
      ),
    );
  }
}

23.2 登录页

class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  @override
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  final formKey = GlobalKey<FormState>();
  final usernameController = TextEditingController();
  final passwordController = TextEditingController();

  @override
  void dispose() {
    usernameController.dispose();
    passwordController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('登录')),
      body: SafeArea(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Form(
            key: formKey,
            child: Column(
              children: [
                TextFormField(
                  controller: usernameController,
                  decoration: const InputDecoration(
                    labelText: '用户名',
                    border: OutlineInputBorder(),
                  ),
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return '请输入用户名';
                    }
                    return null;
                  },
                ),
                const SizedBox(height: 12),
                TextFormField(
                  controller: passwordController,
                  obscureText: true,
                  decoration: const InputDecoration(
                    labelText: '密码',
                    border: OutlineInputBorder(),
                  ),
                  validator: (value) {
                    if (value == null || value.length < 6) {
                      return '密码至少 6 位';
                    }
                    return null;
                  },
                ),
                const SizedBox(height: 16),
                SizedBox(
                  width: double.infinity,
                  child: ElevatedButton(
                    onPressed: () {
                      final valid = formKey.currentState?.validate() ?? false;
                      if (!valid) return;

                      ScaffoldMessenger.of(context).showSnackBar(
                        const SnackBar(content: Text('登录中...')),
                      );
                    },
                    child: const Text('登录'),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

23.3 商品卡片

class ProductCard extends StatelessWidget {
  final String title;
  final String imageUrl;
  final double price;

  const ProductCard({
    super.key,
    required this.title,
    required this.imageUrl,
    required this.price,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      clipBehavior: Clip.antiAlias,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          AspectRatio(
            aspectRatio: 16 / 9,
            child: Image.network(
              imageUrl,
              fit: BoxFit.cover,
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(12),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  title,
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                  style: const TextStyle(
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 8),
                Text(
                  '¥$price',
                  style: const TextStyle(
                    color: Colors.red,
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

24. 常见布局报错和解决思路

24.1 RenderFlex overflowed

常见原因:RowColumn 里的内容超出了可用空间。

常见解决:

Row(
  children: const [
    Icon(Icons.info),
    SizedBox(width: 8),
    Expanded(
      child: Text(
        '很长很长的文字',
        overflow: TextOverflow.ellipsis,
      ),
    ),
  ],
)

处理思路:

  • Row 里给长文字包 ExpandedFlexible
  • Text 设置 maxLinesoverflow
  • 页面内容太高时,使用滚动组件。

24.2 Vertical viewport was given unbounded height

常见原因:在 Column 里直接放 ListView,但没有给它高度。

解决:

Column(
  children: [
    const Text('标题'),
    Expanded(
      child: ListView.builder(
        itemCount: 20,
        itemBuilder: (context, index) {
          return ListTile(title: Text('Item $index'));
        },
      ),
    ),
  ],
)

24.3 setState called after dispose

常见原因:异步任务回来时,页面已经销毁。

解决:

if (!mounted) return;

setState(() {
  // 更新状态
});

24.4 Incorrect use of ParentDataWidget

常见原因:ExpandedFlexiblePositioned 放错位置。

规则:

Widget必须放在哪里
ExpandedRowColumnFlex 的直接子元素
FlexibleRowColumnFlex 的直接子元素
PositionedStack 的直接子元素

25. 新手选 Widget 的口诀

需求优先选择
做一个标准页面Scaffold
顶部标题栏AppBar
居中Center
加内边距Padding
加宽高、背景、圆角Container
固定间距SizedBox
横向排列Row
纵向排列Column
占满剩余空间Expanded
文字太长省略Flexible + TextOverflow.ellipsis
元素重叠Stack + Positioned
自动换行标签Wrap
长列表ListView.builder
宫格GridView.builder
表单校验Form + TextFormField
点击区域InkWellGestureDetector
异步请求 UIFutureBuilder
实时数据 UIStreamBuilder
简单局部状态ValueNotifier + ValueListenableBuilder
响应式布局LayoutBuilder

26. 推荐学习顺序

第 1 阶段:先能搭页面

先掌握:

  • MaterialApp
  • Scaffold
  • AppBar
  • Text
  • Icon
  • Image
  • Container
  • Padding
  • SizedBox
  • Row
  • Column
  • Expanded

能做出普通页面结构后,再继续。

第 2 阶段:列表和输入

重点掌握:

  • ListView.builder
  • GridView.builder
  • ListTile
  • Card
  • TextField
  • Form
  • TextFormField
  • ElevatedButton

这时你就能做登录页、列表页、详情页。

第 3 阶段:交互和状态

重点掌握:

  • setState
  • FutureBuilder
  • StreamBuilder
  • ValueListenableBuilder
  • SnackBar
  • Dialog
  • BottomSheet
  • Navigator

这时就能写出能请求数据、能跳转、能反馈用户操作的页面。

第 4 阶段:适配和优化

重点掌握:

  • MediaQuery
  • LayoutBuilder
  • SafeArea
  • CustomScrollView
  • SliverAppBar
  • AnimatedContainer
  • Hero

这时再去处理复杂页面、响应式和动画。

27. 官方资料

28. 最后总结

Flutter 新手不要一开始就试图记住所有 Widget。更好的方式是按页面需求来学:

  1. 页面结构用 Scaffold
  2. 横竖排列用 RowColumn
  3. 间距尺寸用 PaddingSizedBox
  4. 样式容器用 Container
  5. 长列表用 ListView.builder
  6. 表单用 FormTextFormField
  7. 异步数据用 FutureBuilder
  8. 复杂适配用 LayoutBuilder

真正开发时,大多数页面都是这些 Widget 的组合。先把常用 Widget 用熟,再去学状态管理、网络请求、路由封装和复杂动画,会轻松很多。

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
flutter2026-07-01
Dart 入门回顾
1\. Dart 是什么 Dart 是一门类型安全、支持面向对象、支持异步编程的语言。它的语法和 JavaScript、TypeScript、Java、C# 都有一些相似之处。 Dart 常见使用场景: Flutter 跨平台应用开发 命令行工具 Web 应用 服务端接口或脚本 作为学习现代类型语言的入口 Dart 的几个核心特点: 静态类型:变量、函数返回值、参数都有类型。 类型推断:很多时候可以用 ,让 Dart 自动推断类型。 空...
学习
flutter2024-11-21
在 Flutter 中使用 webview_flutter 4.0
1. 2. ,已入选 3. 4. 本文是第 4 篇,定位是新手入门,介绍和演示 webview 的基础用法。最后还介绍了事件处理的技巧。因为是补写的,所以本来应该首发的却发在最后了。 环境准备已经在 说过了,不再赘述。既然是新手入门,就会本着详尽的原则,而且会多配示例。 获取页面信息 获取页面 title 核心方法:controller.getTitle 完整示例,放在 main.dart 就能运行。运行示例,点击获取 title 的按...
学习
flutter2024-11-06
flutter android端运行报错Namespace not specified. Specify a namespace in the module‘s build file
在使用flutterSDK 3.24之后发现新建项目,项目默认都是基于AGP8了,由于 AGP 开始强制要求 namespace 配置,不仅仅是主工程,也包括第三方库都强制要求 namespace 。但是我们在引入第三方库的时候会发现部分库没能及时的去做相关配置,这也就导致了我们会出现标题错误: 出现这个问题,我们可能会通过去找到本地缓存的第三方库代码去修改它的build.gradle和AndroidManifest.xml来解决缺失...
学习
flutter2024-11-03
Flutter中如何实现APP升级功能背景
背景 每个App应用中一定会包含的功能 ,App的运行平台不同实现升级功能的方式也就不同,基本分类如下所示。本文主要聚焦在使用Flutter框架开发的应用在安卓系统内如何更新,以及所涉及要点梳理。 IOS系统 Andriod系统 官网下载 各类应用市场 (本文聚焦点) 功能清单(按步骤) UpgradeCard组件(展示升级内容) 进行新旧版本对比判断是否需要更新 下载更新然后安装应用 UpgradeCard组件 组件需要包含基本标题、...
学习
flutter2024-11-03
Flutter应用内更新App版本使用flutter_downloader而非ota_update
做当前App应用内更新的前提:用户可以在App更新的时候还可以使用软件,不能让他一直等待下载,关闭下载组件后,打开组件还能继续看到他的进度,并且用户锁屏或者短暂退出APP后需要保证任务不能总是挂掉,这里没有用到保活,而是需要用到生命周期去让他暂停和恢复,所以当前app还要有一个全局管理类保存任务id。UI图在最后 (插件文档说明可以通知栏显示,但是我用的这个版本在100%下完后总是failed,而不是complete,就不会很友好,后面...
学习
flutter2024-11-03
强大的Flutter App升级功能
注意:无特殊说明,Flutter版本及Dart版本如下: Flutter版本: 1.12.13+hotfix.5 Dart版本: 2.7.0 应用程序升级功能是App的基础功能之一,如果没有此功能会造成用户无法升级,应用程序的bug或者新功能老用户无法触达,甚至损失这部分用户。 对于应用程序升级功能的重要性就无需赘言了,下面介绍下应用程序升级功能的几种方式,从平台方面来说: IOS平台,应用程序升级功能只能通过跳转到app store进...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录