news 2026/5/14 1:01:16

Flutter for HarmonyOS 开发指南(三):登录页面开发,使用DIO发起http请求

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter for HarmonyOS 开发指南(三):登录页面开发,使用DIO发起http请求

前言

本文将从前端开发者的视角出发,快速带你上手Flutter for HarmonyOS的页面开发,重点介绍:

  • Flutter 页面结构

  • 布局与样式的实现方式

  • 常见 UI 组件的使用

  • 使用 Dio 发起 HTTP 请求

通过一个完整的登录页面示例,快速建立 Flutter 页面开发的整体认知。

准备工作

在开始之前,请确保你已经完成以下准备:

  • ✅ Flutter for HarmonyOS 开发环境已搭建完成

  • ✅ 已成功创建 Flutter Hello World 项目

  • ✅ 可以正常运行并看到页面

开发目标

本示例将实现一个登录页面,页面包含以下元素:

  • 应用 Logo

  • 账号输入框

  • 密码输入框

  • 登录按钮

项目代码结构

lib/ ├── main.dart └── pages/ └── login.dart

页面开发

开发一个登录页面,页面包含着应用logo,账号密码输入框,登录按钮。

代码目录

lib/main.dart 代码修改如下:

import 'package:flutter/material.dart'; import 'pages/login.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.red.shade500), appBarTheme: const AppBarTheme( // 设置背景色(可选) // backgroundColor: Colors.white, // 设置标题文本的全局样式 titleTextStyle: TextStyle( color: Colors.black, // 全局标题颜色 fontSize: 18, // 全局字体大小 fontWeight: FontWeight.bold, ), ), ), home: const LoginPage(), ); } }

lib/pages/login.dart代码如下:

import 'package:flutter/material.dart'; // 1. 改为 StatefulWidget,因为我们需要持有 Input 的状态(Controller) class LoginPage extends StatefulWidget { const LoginPage({super.key}); @override State<LoginPage> createState() => _LoginPageState(); } class _LoginPageState extends State<LoginPage> { // 2. 定义两个控制器 (相当于 Vue 的 data/v-model) // 它们负责监听和获取输入框的内容 final TextEditingController _accountController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); // 3. 登录逻辑方法 void _handleLogin() { // 获取输入框的文本 final String account = _accountController.text; final String password = _passwordController.text; // 判断账号是否为空 if (account.isEmpty) { // 弹出提示框 (SnackBar) ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('请输入账号'))); return; // 中断执行 } // 判断密码是否为空 if (password.isEmpty) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('请输入密码'))); return; } // 如果都通过了 print('验证通过,开始登录...'); print('账号: $account, 密码: $password'); // 这里可以写后续的 API 请求逻辑 ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('登录成功!'), backgroundColor: Colors.green), ); } // 4. 销毁控制器 (好习惯:页面关闭时释放内存) @override void dispose() { _accountController.dispose(); _passwordController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('登录'), centerTitle: true), // 标题居中 body: Container( padding: const EdgeInsets.all(12), width: double.infinity, child: Column( // mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ Container( width: 100, height: 100, decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), image: const DecorationImage( image: AssetImage('assets/images/logo.png'), fit: BoxFit.cover, // 类似 CSS 的 object-fit: cover,填满容器不变形 ), boxShadow: [ BoxShadow( color: Colors.black12, blurRadius: 8, offset: Offset(0, 5), ), ], ), ), const SizedBox(height: 16), TextField( controller: _accountController, // 绑定控制器 decoration: const InputDecoration( labelText: '账号', border: OutlineInputBorder(), ), ), const SizedBox(height: 16), TextField( controller: _passwordController, // 绑定控制器 decoration: const InputDecoration( labelText: '密码', border: OutlineInputBorder(), ), obscureText: true, ), const SizedBox(height: 16), SizedBox( width: double.infinity, height: 45, child: ElevatedButton( onPressed: _handleLogin, child: const Text('登录'), ), ), ], ), ), ); } }

为什么要用 StatefulWidget?

和 Vue / React 一样,当页面需要维护状态时,就必须使用有状态组件

在本页面中,我们需要:

  • 监听输入框内容

  • 控制 loading 状态

  • 响应按钮点击事件

因此需要使用StatefulWidget


Flutter 与 CSS 的一些类比

颜色差异

Flutter 使用Color类,常见写法:Colors.black.withOpacity(0.1)

等价于 CSS:rgba(0, 0, 0, 0.1)


容器嵌套规则

Flutter 的布局思想与前端非常相似:

  • Containerdiv

  • Column/Rowflex-direction: column / row

  • 一切 UI 都是 Widget,可无限嵌套


盒子阴影(BoxShadow)

CSS 支持多层阴影,Flutter 同样支持:

boxShadow: [ BoxShadow(...), BoxShadow(...), ]

CSS 属性Flutter 参数说明
rgba(0,0,0,0.1)color阴影颜色
0px 4pxoffset: Offset(0, 4)偏移量
10pxblurRadius模糊程度
1pxspreadRadius扩散大小

实现DIO 发起Http请求

一.安装 Dio

1.打开你的pubspec.yaml

2.在dependencies:下添加:

dependencies: flutter: sdk: flutter dio: ^5.4.0

3.执行命令:

flutter pub get

4.或者一步执行安装(推荐)

flutter pub add dio

二.编写请求代码

核心逻辑对比 (JS vs Dart):

  • JS (Axios): const res = await axios.post('/login', { name: 'xx' })

  • Dart (Dio): final res = await dio.post('/login', data: { 'name': 'xx' })

DioGET请求示例:

import 'package:dio/dio.dart'; final dio = Dio(); void request() async { Response response; response = await dio.get('/test?id=12&name=dio'); print(response.data.toString()); // The below request is the same as above. response = await dio.get( '/test', queryParameters: {'id': 12, 'name': 'dio'}, ); print(response.data.toString()); }

Dio Post请求示例:

response = await dio.post('/test', data: {'id': 12, 'name': 'dio'});

login.dart实现代码如下:

import 'package:flutter/material.dart'; import 'package:dio/dio.dart'; // 1. 引入 Dio // 1. 改为 StatefulWidget,因为我们需要持有 Input 的状态(Controller) class LoginPage extends StatefulWidget { const LoginPage({super.key}); @override State<LoginPage> createState() => _LoginPageState(); } class _LoginPageState extends State<LoginPage> { // 2. 定义两个控制器 (相当于 Vue 的 data/v-model) // 它们负责监听和获取输入框的内容 final TextEditingController _accountController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); // 增加一个 loading 状态,用来控制按钮转圈圈 bool _isLoading = false; // 3. 登录逻辑方法 void _handleLogin() async { // 获取输入框的文本 final String account = _accountController.text; final String password = _passwordController.text; // 判断账号是否为空 if (account.isEmpty) { // 弹出提示框 (SnackBar) ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('请输入账号'))); return; // 中断执行 } // 判断密码是否为空 if (password.isEmpty) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('请输入密码'))); return; } // --- 开始请求 --- // A. 设置 loading 为 true,让界面显示加载中 setState(() { _isLoading = true; }); // B. 创建 Dio 实例 (实际开发中通常会封装成一个单例 Global) final dio = Dio(); print("登录1. 创建 Dio 实例"); try { // C. 发送 POST 请求 (模拟请求 reqres.in 的测试接口) // 注意:await 等待结果 final response = await dio.post( 'https://reqres.in/api/login', // 这是一个免费的测试 API data: { 'email': account, // 测试账号用: eve.holt@reqres.in 'password': password, // 测试密码用: cityslicka }, // 如果需要 header 可以在这里加 options: Options(...) ); print("登录2. 发送 POST 请求"); // D. 处理结果 if (response.statusCode == 200) { // Dio 会自动把 JSON 转成 Map/List,不需要 JSON.parse final data = response.data; final token = data['token']; // 获取返回的 token print('登录成功,Token: $token'); if (!mounted) return; // ⚠️ 异步操作后检查页面是否还在,防止报错 ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('登录成功!Token: $token'), backgroundColor: Colors.green, ), ); // TODO: 这里通常会跳转页面: Navigator.push(...) } } on DioException catch (e) { print("登录3. 捕获 Dio 错误: $e"); // E. 捕获 Dio 专门的错误 (类似 Axios 的 catch) String errorMsg = '请求失败'; if (e.response != null) { // 服务器返回了错误状态码 (4xx, 5xx) errorMsg = e.response?.data['error'] ?? '服务器错误'; } else { // 网络问题 errorMsg = '网络连接异常'; } if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(errorMsg), backgroundColor: Colors.red), ); } finally { // F. 无论成功失败,都关闭 loading if (mounted) { setState(() { _isLoading = false; }); } } } // 4. 销毁控制器 (好习惯:页面关闭时释放内存) @override void dispose() { _accountController.dispose(); _passwordController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('登录'), centerTitle: true), // 标题居中 body: Container( padding: const EdgeInsets.all(12), width: double.infinity, child: Column( // mainAxisAlignment: MainAxisAlignment.start, children: <Widget>[ Container( width: 100, height: 100, decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), image: const DecorationImage( image: AssetImage('assets/images/logo.png'), fit: BoxFit.cover, // 类似 CSS 的 object-fit: cover,填满容器不变形 ), boxShadow: [ BoxShadow( color: Colors.black12, blurRadius: 8, offset: Offset(0, 5), ), ], ), ), const SizedBox(height: 16), TextField( controller: _accountController, // 绑定控制器 decoration: const InputDecoration( labelText: '账号', border: OutlineInputBorder(), ), ), const SizedBox(height: 16), TextField( controller: _passwordController, // 绑定控制器 decoration: const InputDecoration( labelText: '密码', border: OutlineInputBorder(), ), obscureText: true, ), const SizedBox(height: 16), SizedBox( width: double.infinity, height: 45, child: ElevatedButton( // loading 时禁用按钮,防止重复点击 onPressed: _isLoading ? null : _handleLogin, child: _isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : const Text('登录'), ), ), ], ), ), ); } }

运行效果

总结

通过这个示例,你已经完成了:

  • Flutter 页面结构搭建

  • 基础布局与样式开发

  • 前端思维向 Flutter 的迁移

  • 使用 Dio 发起 HTTP 请求

  • 登录页面完整实现

对前端开发者来说,Flutter 的学习曲线远没有想象中陡峭

欢迎加入开源鸿蒙跨平台社区 https://openharmonycrossplatform.csdn.net

Dio文档地址:https://pub.dev/packages/dio

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/7 1:33:11

AI心理助手应用场景:基于Emotion2Vec+ Large的情绪识别落地方案

AI心理助手应用场景&#xff1a;基于Emotion2Vec Large的情绪识别落地方案 你有没有遇到过这样的场景&#xff1a;客服热线里&#xff0c;用户声音发颤却只说“没事”&#xff0c;而系统还在机械播报标准话术&#xff1b;心理咨询师面对上百小时录音&#xff0c;靠人工标注情绪…

作者头像 李华
网站建设 2026/5/11 11:19:27

Qwen-Image-Layered更新日志解读,新功能太实用

Qwen-Image-Layered更新日志解读&#xff0c;新功能太实用 1. 这不是普通修图工具&#xff0c;而是给图片“动手术”的新范式 你有没有试过想把一张照片里的人像换件衣服&#xff0c;却总在边缘留下毛边&#xff1f;想把商品图的背景替换成纯白&#xff0c;结果阴影和发丝细节…

作者头像 李华
网站建设 2026/5/1 7:08:25

cv_resnet18_ocr-detection快速上手:10分钟完成环境部署

cv_resnet18_ocr-detection快速上手&#xff1a;10分钟完成环境部署 1. 这是什么&#xff1f;一个开箱即用的OCR文字检测工具 你是不是也遇到过这些情况&#xff1a; 手里有一堆产品说明书、合同扫描件、发票照片&#xff0c;想快速提取其中的文字内容&#xff0c;却要一张张…

作者头像 李华
网站建设 2026/5/10 17:48:51

Z-Image-Turbo推理精度下降?FP16与BF16模式对比评测

Z-Image-Turbo推理精度下降&#xff1f;FP16与BF16模式对比评测 1. 为什么你生成的图“看起来有点糊”&#xff1f; 你是不是也遇到过这种情况&#xff1a;刚部署好Z-Image-Turbo&#xff0c;满怀期待输入一段精致提示词&#xff0c;点击生成——结果出来的图细节发虚、文字边…

作者头像 李华
网站建设 2026/5/10 17:53:38

IQuest-Coder-V1开源生态展望:社区贡献与模型迭代部署指南

IQuest-Coder-V1开源生态展望&#xff1a;社区贡献与模型迭代部署指南 1. 这不是又一个“会写代码”的模型&#xff0c;而是能理解软件如何生长的伙伴 你有没有试过让一个大模型帮你改一段正在演化的微服务代码&#xff1f;不是简单补全函数&#xff0c;而是理解上周提交里加…

作者头像 李华
网站建设 2026/5/10 17:53:07

NewBie-image-Exp0.1部署教程:基于16GB显存环境的优化实践

NewBie-image-Exp0.1部署教程&#xff1a;基于16GB显存环境的优化实践 1. 为什么这个镜像值得你花10分钟部署&#xff1f; 你是不是也遇到过这样的情况&#xff1a;下载了一个号称“开箱即用”的动漫生成模型&#xff0c;结果卡在环境配置上两小时——CUDA版本不对、PyTorch编…

作者头像 李华