【06】flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且实现静态跳转打包demo做演示-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
章节内容【06】
flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且打包demo做演示
开发背景
上篇我们做了自定义组件,本文继续完善注册相关页面并且实现跳转
闲话不多,开源仓库地址,可以观摩已经写好的代码:
https://gitee.com/youyacao/ff-flutter
demo下载
实战开始
上一篇我们在完成后发现个报错问题,
Target of URI doesn't exist: 'package:ff_flutter/lib/widgets/pinkbutton.dart'. Try creating the file referenced by the URI, or try using a URI for a file that does exist.
这是提示目录中没有找到pinkbutton.dart文件,相关报错还有很多,但是优雅草卓伊凡的路径是正确的,其实就是package:ff_flutter识别不了了。
这是因为包依赖问题:
如果 ff_flutter 是一个自定义包,确保它已经在 pubspec.yaml 中正确声明。
运行 flutter pub get 更新依赖。
dependencies: ff_flutter: path: ../ff_flutter # 根据实际情况调整路径
清理和重建项目:
运行 flutter clean 清理构建缓存。
运行 flutter pub get 获取最新依赖。
重新启动 IDE 或编辑器以确保所有更改生效。
可是当我们运行调试的时候还是报错了,这时候我们再来看
发现问题了,name应该是 name: ff_flutter 才对。
一切就绪后还是报错,依赖也是也正确的,终于我测试,把lib目录去掉,就成功了
想了一下,那么为啥呢,原来我定义目录已经是path: ../ff_flutter 那么package:ff_flutter 已经就到了lib 目录下了,我再去加lib目录那当然要出错啦,问题解决,我们进行下一步
有了注册页面我们做登录页面
新建login.dart
这个应该是登录页面了,但是login画错了,而且下面有切换用户密码登录,那么这里就是短信登录,因此我反馈给ui了 让去整改下,其次login文件名改为smslogin这样会方便知道。
观察了下样式基本一致因此我们复制注册页面过来,这里有很多地方细节都需要修改,特别是社交登录下方那个是图片才可以实现,我们先增加一下login按钮点击 后跳转到 smslogin.dart
要给 login 按钮增加点击跳转到 smslogin.dart 页面的功能,你需要在 onPressed 回调中使用 Navigator 进行页面跳转,我们需要以下步骤
在注册页面修改登录按钮的代码:
BlackButton( label: 'login', onPressed: () { // 登录按钮点击事件 logger.info('登录按钮被点击'); Navigator.push( context, MaterialPageRoute(builder: (context) => SmsLoginScreen()), ); }, ),
并且增加引入:
import 'package:ff_flutter/screens/smslogin.dart'; // 假设 smslogin.dart 文件位于 screens 目录下
在smslogin.dart页面增加
// smslogin.dart class SmsLoginScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('SMS Login'), ), body: Center( child: const Text('SMS Login Screen'), ), ); } }
完成后,但是我们得到一个警告
Constructors for public widgets should have a named 'key' parameter. Try adding a named parameter to the constructor.
大意是
Flutter 建议为公共小部件的构造函数添加一个命名的 key 参数。为了符合这个建议,你需要在 SmsLoginScreen 和 RegisterScreen 的构造函数中添加 Key 参数。
那么扩展知识又来了
扩展知识
在Flutter中,为小部件的构造函数添加一个命名的 key
参数有以下几个主要作用:
1. 唯一标识小部件
每个小部件都可以通过 key
参数在树结构中唯一标识。这在重建部分树时特别有用,因为它有助于Flutter引擎高效地更新和重用小部件,而不是销毁和重建它们。
2. 保持状态
当你需要保持某个状态时(例如在列表中拖动排序项目),key
参数可以确保小部件在重建时保持其状态。例如,在一个可变顺序的列表中,如果每个项目都有唯一的 key
,那么在列表项被重新排列时,它们的状态仍能正确保持。
3. 控制小部件重建
key
参数可以帮助Flutter引擎决定是否需要重建小部件。通过比较 key
值,Flutter可以在更新UI时更智能地选择重建哪些部分,从而提高性能。
代码示例
以下是如何为小部件添加一个命名的 key 参数的示例
import 'package:flutter/material.dart'; class CustomWidget extends StatelessWidget { final String title; const CustomWidget({Key? key, required this.title}) : super(key: key); @override Widget build(BuildContext context) { return Container( child: Text(title), ); } }
在上面的示例中,CustomWidget 有一个可选的 key 参数,并在构造函数中使用 super(key: key) 进行初始化。当你创建这个小部件的实例时,可以传递一个 key
CustomWidget( key: ValueKey('unique_key'), title: 'My Custom Widget', )
ok 我们照做,
Key 类型
在Flutter中,有几种不同类型的 Key,你可以根据具体需求选择使用:
ValueKey<T>: 通过值来唯一标识小部件,适用于简单数据类型(如字符串或数字)。
ObjectKey: 通过对象来唯一标识小部件,适用于复杂数据类型。
UniqueKey: 保证每次创建时都唯一,适用于需要绝对唯一性的场景(但不能用于状态保持)。
也就是说,接下来需要在 register_screen.dart 中为 RegisterScreen 添加一个 key 值 reg,在 smslogin.dart 中为 SmsLoginScreen 添加一个 key 值 slogin。
以下是修改后的代码
在顶部加入
const RegisterScreen({Key? key}) : super(key: key); // 添加 key 参数
在注册处:
key: const Key('reg'), // 添加 key 值 'reg'
在登录按钮处:
MaterialPageRoute(builder: (context) => SmsLoginScreen(key: const Key('slogin'))), // 添加 key 值 'slogin'
注册页面我们点击登录成功跳转到了登录页面,成功实现了 跳转,本来打算本篇幅 写完所有注册页面,但是看来过长 需要下一篇了,其次有个大一点的原因是注册逻辑有问题,因此我们先去做其他页面。
完整页面供参考,
注册页面
import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:ff_flutter/widgets/pinkbutton.dart'; // 引入 PinkButton import 'package:ff_flutter/widgets/blackbutton.dart'; // 引入 BlackButton import 'package:ff_flutter/screens/smslogin.dart'; // 假设 smslogin.dart 文件位于 screens 目录下 class RegisterScreen extends StatefulWidget { const RegisterScreen({Key? key}) : super(key: key); // 添加 key 参数 @override State<RegisterScreen> createState() => _RegisterScreenState(); } class _RegisterScreenState extends State<RegisterScreen> { // 示例国家地区号列表 final List<String> countryCodes = ['+1', '+86', '+91', '+44', '+33']; // 默认选择的国家地区号 String selectedCountryCode = '+1'; // Checkbox 状态 bool _agreedToTerms = false; @override Widget build(BuildContext context) { final logger = Logger('RegisterScreen'); logger.info('Building RegisterScreen'); return Scaffold( key: const Key('reg'), // 添加 key 值 'reg' backgroundColor: const Color(0xFF1E1E1E), // 设置背景色为 #1E1E1E body: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( "Free Friend", style: TextStyle( color: Colors.white, fontSize: 61.87, fontFamily: "PingFang SC", fontWeight: FontWeight.w800, ), ), const SizedBox(height: 16.0), const Text( "Please register your account", style: TextStyle( color: Colors.white, fontSize: 32, fontFamily: "PingFang SC", fontWeight: FontWeight.w800, ), ), const SizedBox(height: 16.0), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, // 使用 spaceBetween 对齐方式 children: [ Flexible( flex: 1, // 给 DropdownButtonFormField 分配一部分空间 child: DropdownButtonFormField<String>( value: selectedCountryCode, onChanged: (String? newValue) { if (newValue != null) { setState(() { selectedCountryCode = newValue; }); } }, items: countryCodes.map<DropdownMenuItem<String>>((String value) { return DropdownMenuItem<String>( value: value, child: Text( value, style: const TextStyle(color: Colors.white), // 设置文字颜色为 FFFFFF ), ); }).toList(), decoration: const InputDecoration( labelText: '选择国家地区号', border: OutlineInputBorder(), ), style: const TextStyle( fontSize: 16, color: Colors.white, // 设置文字颜色为 FFFFFF ), dropdownColor: const Color(0xFF1E1E1E), // 设置弹窗背景色为 #1E1E1E ), ), const SizedBox(width: 8.0), const Expanded( flex: 2, // 给 TextField 分配更多的空间 child: TextField( decoration: InputDecoration( labelText: '请输入手机号', border: OutlineInputBorder(), hintStyle: TextStyle(color: Color(0xffa9a9a9)), ), style: TextStyle(color: Colors.white), // 设置输入文字颜色为 FFFFFF keyboardType: TextInputType.phone, ), ), ], ), const SizedBox(height: 16.0), const TextField( decoration: InputDecoration( labelText: '请输入密码', hintStyle: TextStyle(color: Color(0xffa9a9a9)), border: OutlineInputBorder(), ), obscureText: true, style: TextStyle(color: Colors.white), // 设置输入文字颜色为 FFFFFF ), Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ Checkbox( value: _agreedToTerms, onChanged: (bool? value) { setState(() { _agreedToTerms = value ?? false; }); }, ), const SizedBox(width: 20), const Text( "You agree to our Terms", style: TextStyle( color: Colors.white, fontSize: 32, fontFamily: "PingFang SC", fontWeight: FontWeight.w500, ), ), ], ), const SizedBox(height: 24.0), PinkButton( label: 'Register', onPressed: () { // 注册按钮点击事件 logger.info('注册按钮被点击'); }, ), const SizedBox(height: 8.0), Expanded( child: Align( alignment: Alignment.bottomCenter, child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( "Already have an account?", style: TextStyle( color: Colors.white, fontSize: 32, fontFamily: "PingFang SC", fontWeight: FontWeight.w800, ), ), const SizedBox(height: 8.0), BlackButton( label: 'login', onPressed: () { // 登录按钮点击事件 logger.info('登录按钮被点击'); Navigator.push( context, MaterialPageRoute(builder: (context) => SmsLoginScreen(key: const Key('slogin'))), // 添加 key 值 'slogin' ); }, ), ], ), ), ), ], ), ), ); } }
登录页面
import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:ff_flutter/widgets/pinkbutton.dart'; // 引入 PinkButton import 'package:ff_flutter/widgets/blackbutton.dart'; // 引入 BlackButton // smslogin.dart class SmsLoginScreen extends StatelessWidget { const SmsLoginScreen({Key? key}) : super(key: key); // 添加 key 参数 @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('SMS Login'), ), body: Center( child: const Text('SMS Login Screen'), ), ); } } class RegisterScreen extends StatefulWidget { const RegisterScreen({super.key}); @override State<RegisterScreen> createState() => _RegisterScreenState(); } class _RegisterScreenState extends State<RegisterScreen> { // 示例国家地区号列表 final List<String> countryCodes = ['+1', '+86', '+91', '+44', '+33']; // 默认选择的国家地区号 String selectedCountryCode = '+1'; // Checkbox 状态 bool _agreedToTerms = false; @override Widget build(BuildContext context) { final logger = Logger('RegisterScreen'); logger.info('Building RegisterScreen'); return Scaffold( backgroundColor: const Color(0xFF1E1E1E), // 设置背景色为 #1E1E1E // appBar: AppBar( // title: const Text( // 'Free Friend', // style: TextStyle( // fontSize: 24.0, // 设置字体大小 // fontFamily: 'PingFang SC', // 设置字体为 PingFang SC // ), // ), // centerTitle: true, // 居中标题 // ), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( "Free Friend", style: TextStyle( color: Colors.white, fontSize: 61.87, fontFamily: "PingFang SC", fontWeight: FontWeight.w800, ), ), const SizedBox(height: 16.0), const Text( "Please login your account", style: TextStyle( color: Colors.white, fontSize: 32, fontFamily: "PingFang SC", fontWeight: FontWeight.w800, ), ), const SizedBox(height: 16.0), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, // 使用 spaceBetween 对齐方式 children: [ Flexible( flex: 1, // 给 DropdownButtonFormField 分配一部分空间 child: DropdownButtonFormField<String>( value: selectedCountryCode, onChanged: (String? newValue) { if (newValue != null) { setState(() { selectedCountryCode = newValue; }); } }, items: countryCodes.map<DropdownMenuItem<String>>((String value) { return DropdownMenuItem<String>( value: value, child: Text( value, style: const TextStyle(color: Colors.white), // 设置文字颜色为 FFFFFF ), ); }).toList(), decoration: const InputDecoration( labelText: '选择国家地区号', border: OutlineInputBorder(), ), style: const TextStyle( fontSize: 16, color: Colors.white, // 设置文字颜色为 FFFFFF ), dropdownColor: const Color(0xFF1E1E1E), // 设置弹窗背景色为 #1E1E1E ), ), const SizedBox(width: 8.0), const Expanded( flex: 2, // 给 TextField 分配更多的空间 child: TextField( decoration: InputDecoration( labelText: '请输入手机号', border: OutlineInputBorder(), hintStyle: TextStyle(color: Color(0xffa9a9a9)), ), style: TextStyle(color: Colors.white), // 设置输入文字颜色为 FFFFFF keyboardType: TextInputType.phone, ), ), ], ), const SizedBox(height: 16.0), const TextField( decoration: InputDecoration( labelText: '请输入密码', hintStyle: TextStyle(color: Color(0xffa9a9a9)), border: OutlineInputBorder(), ), obscureText: true, style: TextStyle(color: Colors.white), // 设置输入文字颜色为 FFFFFF ), Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ Checkbox( value: _agreedToTerms, onChanged: (bool? value) { setState(() { _agreedToTerms = value ?? false; }); }, ), const SizedBox(width: 20), const Text( "You agree to our Terms", style: TextStyle( color: Colors.white, fontSize: 32, fontFamily: "PingFang SC", fontWeight: FontWeight.w500, ), ), ], ), const SizedBox(height: 24.0), PinkButton( label: 'Register', onPressed: () { // 注册按钮点击事件 logger.info('注册按钮被点击'); }, ), const SizedBox(height: 8.0), Expanded( child: Align( alignment: Alignment.bottomCenter, child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( "Already have an account?", style: TextStyle( color: Colors.white, fontSize: 32, fontFamily: "PingFang SC", fontWeight: FontWeight.w800, ), ), const SizedBox(height: 8.0), BlackButton( label: 'login', onPressed: () { // 登录按钮点击事件 logger.info('登录按钮被点击'); }, ), ], ), ), ), ], ), ), ); } }
其次我改了下名字,register_screen.dart改为register.dart,其次打包了apk 供下载给大家看。