flutter完成注册页面-密码登录-手机短信验证-找回密码相关页面-并且打包demo做演示
上篇我们做了自定义组件,本文继续完善注册相关页面并且实现跳转
闲话不多,开源仓库地址,可以观摩已经写好的代码:
https://gitee.com/youyacao/ff-flutter
上一篇我们在完成后发现个报错问题,
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
参数有以下几个主要作用:
每个小部件都可以通过 key
参数在树结构中唯一标识。这在重建部分树时特别有用,因为它有助于Flutter引擎高效地更新和重用小部件,而不是销毁和重建它们。
当你需要保持某个状态时(例如在列表中拖动排序项目),key
参数可以确保小部件在重建时保持其状态。例如,在一个可变顺序的列表中,如果每个项目都有唯一的 key
,那么在列表项被重新排列时,它们的状态仍能正确保持。
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 供下载给大家看。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。