编写你的第一个 Flutter Web 应用

365体育平台真假怎么分 📅 2026-06-15 06:55:58 ✍️ admin 👁️ 3286 ❤️ 805
编写你的第一个 Flutter Web 应用

编写你的第一个 Flutter Web 应用入门chevron_right编写你的第一个 Web 应用内容步骤 0:获取初始 Web 应用步骤 1:显示欢迎屏幕步骤 2:启用登录进度跟踪步骤 2.5:启动 Dart DevTools步骤 3:为登录进度添加动画完整示例下一步是什么? lightbulb 提示本 Codelab 将引导你编写你的第一个 Flutter Web 应用。你可能更倾向于尝试 另一个 Codelab,该 Codelab 采用更通用的方法。请注意,此页面上的 Codelab 在下载并配置适当的工具后可在移动设备和桌面上运行。

这是一个创建第一个 Flutter **Web** 应用的指南。如果你熟悉面向对象编程以及变量、循环和条件等概念,你就可以完成本教程。你不需要之前使用 Dart、移动或 Web 编程的经验。

你将构建什么#你将实现一个简单的 Web 应用,该应用显示一个登录屏幕。屏幕包含三个文本字段:名字、姓氏和用户名。当用户填写字段时,进度条会在登录区域顶部进行动画。当三个字段都填写完毕时,进度条以绿色显示在登录区域的整个宽度,并且**注册**按钮将被启用。点击**注册**按钮会导致欢迎屏幕从屏幕底部动画进入。

动画 GIF 显示了本实验完成后应用的工作方式。

你将学到什么如何在 Web 上编写看起来自然的 Flutter 应用。Flutter 应用的基本结构。如何实现补间动画。如何实现有状态小部件。如何使用调试器设置断点。你将使用什么你需要三款软件来完成本实验

Flutter SDKChrome 浏览器文本编辑器或 IDE在开发过程中,在 Chrome 中运行你的 Web 应用,以便你可以使用 Dart DevTools(也称为 Flutter DevTools)进行调试。

步骤 0:获取初始 Web 应用#你将从我们为你提供的简单 Web 应用开始。

启用 Web 开发。在命令行中,执行以下命令以确保你已正确安装 Flutter。flutter doctor

Doctor summary (to see all details, run flutter doctor -v):

[✓] Flutter (Channel stable, 3.24.5, on macOS darwin-arm64, locale en)

[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.1)

[✓] Xcode - develop for iOS and macOS (Xcode 16)

[✓] Chrome - develop for the web

[✓] Android Studio (version 2024.2)

[✓] VS Code (version 1.95)

[✓] Connected device (4 available)

[✓] HTTP Host Availability

• No issues found!如果你看到“flutter: command not found”,请确保你已安装 Flutter SDK 并且它在你的路径中。

如果 Android 工具链、Android Studio 和 Xcode 工具未安装,也没有关系,因为该应用仅供 Web 使用。如果你稍后希望此应用在移动设备上运行,则需要进行其他安装和设置。

列出设备。为了确保已安装 Web,请列出可用的设备。你应该会看到如下内容

flutter devices

4 connected devices:

sdk gphone64 arm64 (mobile) • emulator-5554 •

android-arm64 • Android 13 (API 33) (emulator)

iPhone 14 Pro Max (mobile) • 45A72BE1-2D4E-4202-9BB3-D6AE2601BEF8 • ios

• com.apple.CoreSimulator.SimRuntime.iOS-16-0 (simulator)

macOS (desktop) • macos •

darwin-arm64 • macOS 12.6 21G115 darwin-arm64

Chrome (web) • chrome •

web-javascript • Google Chrome 105.0.5195.125**Chrome** 设备会自动启动 Chrome 并启用 Flutter DevTools 工具的使用。

起始应用显示在以下 DartPad 中。

import 'package:flutter/material.dart';

void main() => runApp(const SignUpApp());

class SignUpApp extends StatelessWidget {

const SignUpApp({super.key});

@override

Widget build(BuildContext context) {

return MaterialApp(

routes: {

'/': (context) => const SignUpScreen(),

},

);

}

}

class SignUpScreen extends StatelessWidget {

const SignUpScreen({super.key});

@override

Widget build(BuildContext context) {

return Scaffold(

backgroundColor: Colors.grey[200],

body: const Center(

child: SizedBox(

width: 400,

child: Card(

child: SignUpForm(),

),

),

),

);

}

}

class SignUpForm extends StatefulWidget {

const SignUpForm({super.key});

@override

State createState() => _SignUpFormState();

}

class _SignUpFormState extends State {

final _firstNameTextController = TextEditingController();

final _lastNameTextController = TextEditingController();

final _usernameTextController = TextEditingController();

double _formProgress = 0;

@override

Widget build(BuildContext context) {

return Form(

child: Column(

mainAxisSize: MainAxisSize.min,

children: [

LinearProgressIndicator(value: _formProgress),

Text('Sign up', style: Theme.of(context).textTheme.headlineMedium),

Padding(

padding: const EdgeInsets.all(8),

child: TextFormField(

controller: _firstNameTextController,

decoration: const InputDecoration(hintText: 'First name'),

),

),

Padding(

padding: const EdgeInsets.all(8),

child: TextFormField(

controller: _lastNameTextController,

decoration: const InputDecoration(hintText: 'Last name'),

),

),

Padding(

padding: const EdgeInsets.all(8),

child: TextFormField(

controller: _usernameTextController,

decoration: const InputDecoration(hintText: 'Username'),

),

),

TextButton(

style: ButtonStyle(

foregroundColor: WidgetStateProperty.resolveWith((states) {

return states.contains(WidgetState.disabled)

? null

: Colors.white;

}),

backgroundColor: WidgetStateProperty.resolveWith((states) {

return states.contains(WidgetState.disabled)

? null

: Colors.blue;

}),

),

onPressed: null,

child: const Text('Sign up'),

),

],

),

);

}

}

error 重要此页面使用 DartPad 的嵌入版本来显示示例和练习。如果你看到空框而不是 DartPad,请访问 DartPad 故障排除页面。

运行示例。点击**运行**按钮运行示例。请注意,你可以键入文本字段,但**注册**按钮处于禁用状态。

复制代码。点击代码窗格右上方的剪贴板图标,将 Dart 代码复制到剪贴板。

创建一个新的 Flutter 项目。从你的 IDE、编辑器或命令行中,创建一个新的 Flutter 项目 并将其命名为signin_example。

将lib/main.dart的内容替换为剪贴板的内容。

观察#此示例的整个代码都位于lib/main.dart文件中。如果你了解 Java,Dart 语言应该会让你感觉非常熟悉。所有应用的 UI 都是用 Dart 代码创建的。有关更多信息,请参阅声明式 UI 简介。应用的 UI 遵循Material Design,这是一种可在任何设备或平台上运行的视觉设计语言。你可以自定义 Material Design 小部件,但如果你更喜欢其他内容,Flutter 还提供 Cupertino 小部件库,该库实现了当前的 iOS 设计语言。或者你可以创建自己的自定义小部件库。在 Flutter 中,几乎所有东西都是一个Widget。即使应用本身也是一个小部件。应用的 UI 可以描述为一个小部件树。步骤 1:显示欢迎屏幕#SignUpForm类是有状态小部件。这仅仅意味着小部件存储可以更改的信息,例如用户输入或来自 Feed 的数据。由于小部件本身是不可变的(创建后无法修改),Flutter 将状态信息存储在一个伴随类中,称为State类。在本实验中,你所有的编辑都将在私有_SignUpFormState类中进行。

lightbulb 有趣的事实Dart 编译器会为任何以下划线为前缀的标识符强制实施隐私。有关更多信息,请参阅Effective Dart 样式指南。

首先,在你的lib/main.dart文件中,在SignUpScreen类之后添加以下WelcomeScreen小部件的类定义

dartclass WelcomeScreen extends StatelessWidget {

const WelcomeScreen({super.key});

@override

Widget build(BuildContext context) {

return Scaffold(

body: Center(

child: Text(

'Welcome!',

style: Theme.of(context).textTheme.displayMedium,

),

),

);

}

}接下来,你将启用按钮以显示屏幕并创建显示它的方法。

找到_SignUpFormState类的build()方法。这是构建“注册”按钮的代码部分。注意按钮是如何定义的:它是一个带有蓝色背景的TextButton,白色文本显示为**注册**,并且按下时不执行任何操作。

更新onPressed属性。将onPressed属性更改为调用将显示欢迎屏幕的(不存在的)方法。

将onPressed: null更改为以下内容

dartonPressed: _showWelcomeScreen,添加_showWelcomeScreen方法。修复分析器报告的_showWelcomeScreen未定义的错误。在build()方法的正上方,添加以下函数

dartvoid _showWelcomeScreen() {

Navigator.of(context).pushNamed('/welcome');

}添加/welcome路由。创建连接以显示新屏幕。在SignUpApp的build()方法中,在'/'下方添加以下路由

dart'/welcome': (context) => const WelcomeScreen(),运行应用。**注册**按钮现在应该已启用。点击它以调出欢迎屏幕。注意它是如何从底部动画进入的。你可以免费获得此行为。

观察#_showWelcomeScreen()函数在build()方法中用作回调函数。回调函数通常用于 Dart 代码中,在本例中,这意味着“按下按钮时调用此方法”。构造函数前面的const关键字非常重要。当 Flutter 遇到一个常量小部件时,它会简化大多数幕后的重建工作,从而使渲染更加高效。Flutter 只有一个Navigator对象。此小部件在一个堆栈中管理 Flutter 的屏幕(也称为路由或页面)。堆栈顶部的屏幕是当前显示的视图。将新屏幕推送到此堆栈会将显示切换到该新屏幕。这就是_showWelcomeScreen函数将WelcomeScreen推送到 Navigator 堆栈的原因。用户点击按钮,瞧,欢迎屏幕出现了。同样,在Navigator上调用pop()会返回到上一个屏幕。由于 Flutter 的导航已集成到浏览器的导航中,因此在点击浏览器的后退箭头按钮时会隐式地发生这种情况。步骤 2:启用登录进度跟踪#此登录屏幕有三个字段。接下来,你将启用跟踪用户填写表单字段进度的能力,并在表单填写完毕时更新应用的 UI。

info 注意此示例**不会**验证用户输入的准确性。如果你愿意,以后可以使用表单验证添加此功能。

添加一个更新_formProgress的方法。在_SignUpFormState类中,添加一个名为_updateFormProgress()的新方法

dartvoid _updateFormProgress() {

var progress = 0.0;

final controllers = [

_firstNameTextController,

_lastNameTextController,

_usernameTextController

];

for (final controller in controllers) {

if (controller.value.text.isNotEmpty) {

progress += 1 / controllers.length;

}

}

setState(() {

_formProgress = progress;

});

}此方法根据非空文本字段的数量更新_formProgress字段。

表单更改时调用_updateFormProgress。在_SignUpFormState类的build()方法中,向Form小部件的onChanged参数添加一个回调。添加下面标记为 NEW 的代码

dartreturn Form(

onChanged: _updateFormProgress, // NEW

child: Column(更新onPressed属性(再次)。在步骤 1中,你修改了**注册**按钮的onPressed属性以显示欢迎屏幕。现在,更新该按钮,仅当表单完全填写后才显示欢迎屏幕

dartTextButton(

style: ButtonStyle(

foregroundColor: WidgetStateProperty.resolveWith((states) {

return states.contains(WidgetState.disabled)

? null

: Colors.white;

}),

backgroundColor: WidgetStateProperty.resolveWith((states) {

return states.contains(WidgetState.disabled)

? null

: Colors.blue;

}),

),

onPressed:

_formProgress == 1 ? _showWelcomeScreen : null, // UPDATED

child: const Text('Sign up'),

),运行应用。**注册**按钮最初处于禁用状态,但当所有三个文本字段包含(任何)文本时将启用。

观察#调用小部件的setState()方法告诉 Flutter 屏幕上的小部件需要更新。然后,框架会处理掉以前不可变的小部件(及其子级),创建一个新的(及其伴随的子级小部件树),并将其渲染到屏幕上。为了使这能无缝地工作,Flutter 需要快速。新的小部件树必须在不到 1/60 秒的时间内创建并渲染到屏幕上,才能创建平滑的视觉过渡——尤其是对于动画而言。幸运的是,Flutter 确实很快。

progress字段定义为浮点值,并在_updateFormProgress方法中更新。当所有三个字段都填写完毕时,_formProgress设置为 1.0。当_formProgress设置为 1.0 时,onPressed回调将设置为_showWelcomeScreen方法。现在它的onPressed参数不为 null,该按钮已启用。与 Flutter 中的大多数 Material Design 按钮一样,TextButton如果它们的onPressed和onLongPress回调为 null,则默认情况下处于禁用状态。

请注意,_updateFormProgress将一个函数传递给setState()。这称为匿名函数,具有以下语法

dartmethodName(() {...});其中methodName是将匿名回调函数作为参数的命名函数。

上一步中显示欢迎屏幕的 Dart 语法为

dart_formProgress == 1 ? _showWelcomeScreen : null这是一个 Dart 条件赋值,语法如下:condition ? expression1 : expression2。如果表达式 _formProgress == 1 为真,则整个表达式的结果为 : 左侧的值,在本例中为 _showWelcomeScreen 方法。

步骤 2.5:启动 Dart DevTools#如何调试 Flutter web 应用?这与调试任何 Flutter 应用没有太大区别。您需要使用 Dart DevTools!(不要与 Chrome DevTools 混淆。)

我们的应用目前没有错误,但无论如何我们还是检查一下。以下启动 DevTools 的说明适用于任何工作流程,但如果您使用 IntelliJ,则有一个快捷方式。有关更多信息,请参阅本节末尾的提示。

运行应用。如果您的应用当前未运行,请启动它。从下拉菜单中选择Chrome设备,并从您的 IDE 或命令行使用flutter run -d chrome启动它。

获取 DevTools 的 WebSocket 信息。在命令行或 IDE 中,您应该会看到类似以下内容的消息

Launching lib/main.dart on Chrome in debug mode...

Building application for the web... 11.7s

Attempting to connect to browser instance..

Debug service listening on ws://127.0.0.1:54998/pJqWWxNv92s=复制调试服务的地址(以粗体显示)。您需要使用它来启动 DevTools。

确保已安装 Dart 和 Flutter 插件。如果您使用的是 IDE,请确保已设置 Flutter 和 Dart 插件,如VS Code和Android Studio 和 IntelliJ页面中所述。如果您在命令行中工作,请按照DevTools 命令行页面中的说明启动 DevTools 服务器。

连接到 DevTools。DevTools 启动后,您应该会看到类似以下内容

Serving DevTools at http://127.0.0.1:9100在 Chrome 浏览器中访问此 URL。您应该会看到 DevTools 启动屏幕。它应该如下所示

连接到正在运行的应用。在连接到正在运行的站点下,粘贴您在步骤 2 中复制的 WebSocket (ws) 位置,然后单击连接。您现在应该会看到 Dart DevTools 在您的 Chrome 浏览器中成功运行

恭喜,您现在正在运行 Dart DevTools!

info 注意这并不是启动 DevTools 的唯一方法。如果您使用的是 IntelliJ,则可以通过转到Flutter 检查器->更多操作->打开 DevTools来打开 DevTools

设置断点。现在您已运行 DevTools,请在顶部的蓝色栏中选择调试器选项卡。调试器窗格出现,在左下方,可以看到示例中使用的库列表。选择lib/main.dart以在中间窗格中显示您的 Dart 代码。

设置断点。在 Dart 代码中,向下滚动到更新progress的位置

dartfor (final controller in controllers) {

if (controller.value.text.isNotEmpty) {

progress += 1 / controllers.length;

}

}通过单击行号左侧,在包含 for 循环的行上设置断点。断点现在显示在窗口左侧的断点部分。

触发断点。在正在运行的应用中,单击其中一个文本字段以获取焦点。应用会命中断点并暂停。在 DevTools 屏幕上,您可以在左侧看到progress的值,为 0。这是预期的,因为没有任何字段被填充。逐步执行 for 循环以查看程序执行。

恢复应用。通过单击 DevTools 窗口中的绿色恢复按钮来恢复应用。

删除断点。再次单击断点将其删除,然后恢复应用。

这可以让您略微了解使用 DevTools 可以实现的功能,但还有更多功能!有关更多信息,请参阅DevTools 文档。

步骤 3:为登录进度添加动画#现在该添加动画了!在最后一步中,您将为登录区域顶部的LinearProgressIndicator创建动画。动画具有以下行为

应用启动时,在登录区域顶部会出现一个很小的红色条。当一个文本字段包含文本时,红色条变为橙色并在登录区域中动画 0.15 的距离。当两个文本字段包含文本时,橙色条变为黄色并在登录区域中动画一半的距离。当所有三个文本字段包含文本时,橙色条变为绿色并在登录区域中动画整个距离。此外,注册按钮将被启用。添加AnimatedProgressIndicator。在文件底部,添加此 widget

dartclass AnimatedProgressIndicator extends StatefulWidget {

final double value;

const AnimatedProgressIndicator({

super.key,

required this.value,

});

@override

State createState() {

return _AnimatedProgressIndicatorState();

}

}

class _AnimatedProgressIndicatorState extends State

with SingleTickerProviderStateMixin {

late AnimationController _controller;

late Animation _colorAnimation;

late Animation _curveAnimation;

@override

void initState() {

super.initState();

_controller = AnimationController(

duration: const Duration(milliseconds: 1200),

vsync: this,

);

final colorTween = TweenSequence([

TweenSequenceItem(

tween: ColorTween(begin: Colors.red, end: Colors.orange),

weight: 1,

),

TweenSequenceItem(

tween: ColorTween(begin: Colors.orange, end: Colors.yellow),

weight: 1,

),

TweenSequenceItem(

tween: ColorTween(begin: Colors.yellow, end: Colors.green),

weight: 1,

),

]);

_colorAnimation = _controller.drive(colorTween);

_curveAnimation = _controller.drive(CurveTween(curve: Curves.easeIn));

}

@override

void didUpdateWidget(AnimatedProgressIndicator oldWidget) {

super.didUpdateWidget(oldWidget);

_controller.animateTo(widget.value);

}

@override

Widget build(BuildContext context) {

return AnimatedBuilder(

animation: _controller,

builder: (context, child) => LinearProgressIndicator(

value: _curveAnimation.value,

valueColor: _colorAnimation,

backgroundColor: _colorAnimation.value?.withOpacity(0.4),

),

);

}

}当AnimatedProgressIndicator发生更改时,didUpdateWidget函数会更新AnimatedProgressIndicatorState。

使用新的AnimatedProgressIndicator。然后,将Form中的LinearProgressIndicator替换为此新的AnimatedProgressIndicator

dartchild: Column(

mainAxisSize: MainAxisSize.min,

children: [

AnimatedProgressIndicator(value: _formProgress), // NEW

Text('Sign up', style: Theme.of(context).textTheme.headlineMedium),

Padding(此 widget 使用AnimatedBuilder将进度指示器动画到最新的值。

运行应用。在三个字段中输入任何内容以验证动画是否有效,以及单击注册按钮是否会显示欢迎屏幕。

完整示例# import 'package:flutter/material.dart';

void main() => runApp(const SignUpApp());

class SignUpApp extends StatelessWidget {

const SignUpApp({super.key});

@override

Widget build(BuildContext context) {

return MaterialApp(

routes: {

'/': (context) => const SignUpScreen(),

'/welcome': (context) => const WelcomeScreen(),

},

);

}

}

class SignUpScreen extends StatelessWidget {

const SignUpScreen({super.key});

@override

Widget build(BuildContext context) {

return Scaffold(

backgroundColor: Colors.grey[200],

body: const Center(

child: SizedBox(

width: 400,

child: Card(

child: SignUpForm(),

),

),

),

);

}

}

class WelcomeScreen extends StatelessWidget {

const WelcomeScreen({super.key});

@override

Widget build(BuildContext context) {

return Scaffold(

body: Center(

child: Text(

'Welcome!',

style: Theme.of(context).textTheme.displayMedium,

),

),

);

}

}

class SignUpForm extends StatefulWidget {

const SignUpForm({super.key});

@override

State createState() => _SignUpFormState();

}

class _SignUpFormState extends State {

final _firstNameTextController = TextEditingController();

final _lastNameTextController = TextEditingController();

final _usernameTextController = TextEditingController();

double _formProgress = 0;

void _updateFormProgress() {

var progress = 0.0;

final controllers = [

_firstNameTextController,

_lastNameTextController,

_usernameTextController

];

for (final controller in controllers) {

if (controller.value.text.isNotEmpty) {

progress += 1 / controllers.length;

}

}

setState(() {

_formProgress = progress;

});

}

void _showWelcomeScreen() {

Navigator.of(context).pushNamed('/welcome');

}

@override

Widget build(BuildContext context) {

return Form(

onChanged: _updateFormProgress,

child: Column(

mainAxisSize: MainAxisSize.min,

children: [

AnimatedProgressIndicator(value: _formProgress),

Text('Sign up', style: Theme.of(context).textTheme.headlineMedium),

Padding(

padding: const EdgeInsets.all(8),

child: TextFormField(

controller: _firstNameTextController,

decoration: const InputDecoration(hintText: 'First name'),

),

),

Padding(

padding: const EdgeInsets.all(8),

child: TextFormField(

controller: _lastNameTextController,

decoration: const InputDecoration(hintText: 'Last name'),

),

),

Padding(

padding: const EdgeInsets.all(8),

child: TextFormField(

controller: _usernameTextController,

decoration: const InputDecoration(hintText: 'Username'),

),

),

TextButton(

style: ButtonStyle(

foregroundColor: WidgetStateProperty.resolveWith((states) {

return states.contains(WidgetState.disabled)

? null

: Colors.white;

}),

backgroundColor: WidgetStateProperty.resolveWith((states) {

return states.contains(WidgetState.disabled)

? null

: Colors.blue;

}),

),

onPressed: _formProgress == 1 ? _showWelcomeScreen : null,

child: const Text('Sign up'),

),

],

),

);

}

}

class AnimatedProgressIndicator extends StatefulWidget {

final double value;

const AnimatedProgressIndicator({

super.key,

required this.value,

});

@override

State createState() {

return _AnimatedProgressIndicatorState();

}

}

class _AnimatedProgressIndicatorState extends State

with SingleTickerProviderStateMixin {

late AnimationController _controller;

late Animation _colorAnimation;

late Animation _curveAnimation;

@override

void initState() {

super.initState();

_controller = AnimationController(

duration: const Duration(milliseconds: 1200),

vsync: this,

);

final colorTween = TweenSequence([

TweenSequenceItem(

tween: ColorTween(begin: Colors.red, end: Colors.orange),

weight: 1,

),

TweenSequenceItem(

tween: ColorTween(begin: Colors.orange, end: Colors.yellow),

weight: 1,

),

TweenSequenceItem(

tween: ColorTween(begin: Colors.yellow, end: Colors.green),

weight: 1,

),

]);

_colorAnimation = _controller.drive(colorTween);

_curveAnimation = _controller.drive(CurveTween(curve: Curves.easeIn));

}

@override

void didUpdateWidget(AnimatedProgressIndicator oldWidget) {

super.didUpdateWidget(oldWidget);

_controller.animateTo(widget.value);

}

@override

Widget build(BuildContext context) {

return AnimatedBuilder(

animation: _controller,

builder: (context, child) => LinearProgressIndicator(

value: _curveAnimation.value,

valueColor: _colorAnimation,

backgroundColor: _colorAnimation.value?.withOpacity(0.4),

),

);

}

}

观察#您可以使用AnimationController运行任何动画。当Animation的值发生变化时,AnimatedBuilder会重建 widget 树。使用Tween,您可以在几乎任何值之间进行插值,在本例中为Color。下一步是什么?#恭喜!您已使用 Flutter 创建了第一个 web 应用!

如果您想继续使用此示例,也许您可以添加表单验证。有关如何执行此操作的建议,请参阅Flutter cookbook中的构建带验证的表单菜谱。

有关 Flutter web 应用、Dart DevTools 或 Flutter 动画的更多信息,请参阅以下内容

动画文档Dart DevTools隐式动画 代码实验室Web 示例除非另有说明,否则本网站上的文档反映了 Flutter 的最新稳定版本。页面上次更新于 2024-10-17。 查看源代码 或 报告问题.

相关创意

开发软件哪个专业好学,软件开发:哪个专业更易学?
微信群机器人怎么弄?群主必备的搭建指南
英国皇家邮政宣布2025年4月起调整邮资价格
沈子善年谱
措手不及的意思
国内个人账户怎么收美金?个人账户收美金有限制吗?
rust如何选服务器
笔记本辐射大吗 如何减少笔记本辐射
童男哪里多