flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件
接上一篇已经设计好目录,我们需要一步步来对应实现,另外此前注册页面完善样式bug-增加自定义可复用组件widgets
闲话不多,开源仓库地址,可以观摩已经写好的代码:
https://gitee.com/youyacao/ff-flutter
lib/
├── main.dart # 应用入口文件
├── models/ # 数据模型类
├── screens/ # 页面组件
│ ├── home_screen.dart # 主页
│ ├── login_screen.dart # 登录页面
│ └── register_screen.dart# 注册页面
├── widgets/ # 可复用的小部件
│ ├── custom_button.dart # 自定义按钮
│ ├── custom_text_field.dart # 自定义文本框
│ └── ...
├── services/ # 网络请求、本地存储等服务
│ ├── api_service.dart # API 请求服务
│ └── storage_service.dart# 本地存储服务
├── utils/ # 工具类和辅助函数
│ ├── constants.dart # 常量定义
│ ├── logger.dart # 日志记录器配置
│ └── validators.dart # 表单验证逻辑
└── theme/ # 主题配置
├── app_theme.dart # 主题样式配置
这是上一章我们规划的目录,但是这里明显└── theme/ 我们是用不上的 ,因此我们先把其他的建立起来
第一步,我们做的首页是一个register_screen注册首页,因此我们建立 register_screen.dart文件,然后我们要把main.dart入口文件的内容和注册页面的内容分开,因此
把main文件中只保留入口文件应该有的内容,整个注册页面的内容均放在register_screen.dart页面,并且实现启动app后第一个页面显示为register_screen.dart页面的内容
我们main.dart的内容为
#file:g:\clone\ff-flutter\lib\main.dart
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'screens/register_screen.dart'; // 引入注册页面
void main() {
// 初始化日志记录器
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((record) {
debugPrint('${record.level.name}: ${record.time}: ${record.message}');
});
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false, // 设置这一属性为 false
title: 'freefirend',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const RegisterScreen(), // 设置启动页面为 RegisterScreen
);
}
}
然后我们注册页面的内容为:
#file:g:\clone\ff-flutter\lib\screens\register_screen.dart
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
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),
);
}).toList(),
decoration: const InputDecoration(
labelText: '选择国家地区号',
border: OutlineInputBorder(),
),
style: const TextStyle(fontSize: 16), // 设置字体大小
),
),
const SizedBox(width: 8.0),
Expanded(
flex: 2, // 给 TextField 分配更多的空间
child: TextField(
decoration: const InputDecoration(
labelText: '请输入手机号',
border: OutlineInputBorder(),
hintStyle: TextStyle(color: Color(0xffa9a9a9)),
),
keyboardType: TextInputType.phone,
),
),
],
),
const SizedBox(height: 16.0),
TextField(
decoration: const InputDecoration(
labelText: '请输入密码',
hintStyle: TextStyle(color: Color(0xffa9a9a9)),
border: OutlineInputBorder(),
),
obscureText: true,
),
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),
Text(
"You agree to our Terms",
style: TextStyle(
color: Colors.white,
fontSize: 32,
fontFamily: "PingFang SC",
fontWeight: FontWeight.w500,
),
),
],
),
const SizedBox(height: 24.0),
ElevatedButton(
onPressed: () {
// 注册按钮点击事件
logger.info('注册按钮被点击');
},
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xffe7568c), // 设置红色背景
fixedSize: Size(630, 48), // 设置按钮宽度为 630
),
child: Text(
'Register',
style: TextStyle(
color: Colors.white, // 设置文字颜色为白色
fontSize: 16.0, // 可以根据需要调整字体大小
),
),
),
const SizedBox(height: 8.0),
Expanded(
child: Align(
alignment: Alignment.bottomCenter,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"Already have an account?",
style: TextStyle(
color: Colors.white,
fontSize: 32,
fontFamily: "PingFang SC",
fontWeight: FontWeight.w800,
),
),
const SizedBox(height: 8.0),
OutlinedButton(
onPressed: () {
// 登录按钮点击事件
logger.info('登录按钮被点击');
},
style: OutlinedButton.styleFrom(
side: BorderSide(color: Colors.white, width: 2), // 设置边框颜色为白色
fixedSize: Size(630, 48), // 设置按钮宽度为 630
backgroundColor: Colors.transparent, // 去掉背景色
),
child: Text(
'login',
style: TextStyle(
color: Colors.white, // 设置文字颜色为白色
fontSize: 16.0, // 可以根据需要调整字体大小
),
),
),
],
),
),
),
],
),
),
);
}
}
这里注意下
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false, // 设置这一属性为 false
title: 'freefirend',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const RegisterScreen(), // 设置启动页面为 RegisterScreen
);
}
main中这里定义了第一个启动的页面,我们目前只有注册页面因此这里定制注册页,后续是肯定要改的,按照常规用户习惯,第一个界面应该是直播列表页面或者短视频列表页面才对。
很完美,不过我们发现最顶部的dubug标识消失了,但是debug内容没隐藏,因此
注释掉这段代码即可,因为这里是有用的有些页面看不出来是什么的时候。
我们注意到,两处细节要处理,第一处是地区选择这里不够显眼,第二处是输入手机号这里颜色也不对不显眼,
请输入手机号输入内容时候的文字需要改成 FFFFFF 颜色,把选择国家区号部分的数字也设置为FFFFFF颜色
我们需要对 register_screen.dart 文件进行相应的修改。具体步骤如下:
修改“请输入手机号”输入框的文字颜色:
在 TextField 的 style 属性中设置文字颜色为 FFFFFF。
修改“选择国家地区号”部分的数字颜色:
在 DropdownButtonFormField 的 style 属性中设置文字颜色为 FFFFFF。
decoration: const InputDecoration(
labelText: '选择国家地区号',
border: OutlineInputBorder(),
),
style: const TextStyle(
fontSize: 16,
color: Colors.white, // 设置文字颜色为 FFFFFF
),
),
),
const SizedBox(width: 8.0),
Expanded(
flex: 2, // 给 TextField 分配更多的空间
child: TextField(
decoration: const InputDecoration(
labelText: '请输入手机号',
border: OutlineInputBorder(),
hintStyle: TextStyle(color: Color(0xffa9a9a9)),
),
style: const TextStyle(color: Colors.white), // 设置文字颜色为 FFFFFF
keyboardType: TextInputType.phone,
),
),
也就是这一段代码了,
查看效果,很棒,但是这里还有问题,既然我们这个登录按钮和注册按钮在其他页面也能用的上,我们为什么不建立为自定义组件呢,因此这里我们需要复用,就要建立自定义组件,
诸多地方都要用的上,因此我们开始,等等,还发现个小问题,选择区号的,背景图颜色不对
那么修改以下代码:
decoration: const InputDecoration(
labelText: '选择国家地区号',
border: OutlineInputBorder(),
),
style: const TextStyle(
fontSize: 16,
color: Colors.white, // 设置文字颜色为 FFFFFF
),
dropdownColor: const Color(0xFF1E1E1E), // 设置弹窗背景色为 #1E1E1E
),
),
细节到位,
在组件目录下我们建立blackbutton.dart 黑色按钮 和 pinkbutton.dart 粉色按钮
先扩展知识学一下
在Flutter中,自定义组件非常重要,可以提高代码的复用性和可维护性。将自定义组件放在 widgets
目录下,可以更好地组织代码,使项目结构更加清晰。这里是如何利用 widgets
目录来自定义和复用组件的示例。
widgets
目录的作用widgets
目录通常用于存放自定义的小部件,这些小部件可能在应用的多个地方使用。通过将自定义组件集中在一个目录下,可以方便地管理和查找它们。
以下是一个简单的步骤,来创建和复用自定义组件:
widgets
目录下创建一个新的 Dart 文件,例如 custom_button.dart
。示范:
lib/
|-- widgets/
| |-- custom_button.dart
自定义组件内容:
// lib/widgets/custom_button.dart
import 'package:flutter/material.dart';
class CustomButton extends StatelessWidget {
final String text;
final VoidCallback onPressed;
CustomButton({required this.text, required this.onPressed});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
child: Text(text),
);
}
}
调用自定义组件:
import 'package:flutter/material.dart';
import 'package:ff_social_app/widgets/custom_button.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('自定义组件示例'),
),
body: Center(
child: CustomButton(
text: '点击我',
onPressed: () {
print('按钮被点击了');
},
),
),
),
);
}
}
我们需要把注册按钮和登录按钮做成自定义组件方便复用,因此把Register按钮的做成自定义组件并且放在widgets目录下的pinkbutton.dart文件内,把logo登录按钮做成自定义组件并且放在widgets目录下的blackbutton.dart文件,需要保留目前的样式,并且在原register_screen.dart文件中 原位置中正常调用组件。
粉色按钮自定义组件内容:
#file:g:\clone\ff-flutter\lib\widgets\pinkbutton.dart
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
class PinkButton extends StatelessWidget {
final String label;
final VoidCallback onPressed;
const PinkButton({
required this.label,
required this.onPressed,
super.key,
});
@override
Widget build(BuildContext context) {
final logger = Logger('PinkButton');
logger.info('Building PinkButton');
return ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xffe7568c), // 设置红色背景
fixedSize: const Size(630, 48), // 设置按钮宽度为 630
),
child: Text(
label,
style: const TextStyle(
color: Colors.white, // 设置文字颜色为白色
fontSize: 16.0, // 可以根据需要调整字体大小
),
),
);
}
}
黑色按钮自定义组件内容:
#file:g:\clone\ff-flutter\lib\widgets\blackbutton.dart
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
class BlackButton extends StatelessWidget {
final String label;
final VoidCallback onPressed;
const BlackButton({
required this.label,
required this.onPressed,
super.key,
});
@override
Widget build(BuildContext context) {
final logger = Logger('BlackButton');
logger.info('Building BlackButton');
return OutlinedButton(
onPressed: onPressed,
style: OutlinedButton.styleFrom(
side: const BorderSide(color: Colors.white, width: 2), // 设置边框颜色为白色
fixedSize: const Size(630, 48), // 设置按钮宽度为 630
backgroundColor: Colors.transparent, // 去掉背景色
),
child: Text(
label,
style: const TextStyle(
color: Colors.white, // 设置文字颜色为白色
fontSize: 16.0, // 可以根据需要调整字体大小
),
),
);
}
}
然后我们在注册页引用这两个组件,在此同时,我们就需要简化我们原先已经写好的,避免重复,代码如下:
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:ff_flutter/lib/widgets/pinkbutton.dart'; // 引入 PinkButton
import 'package:ff_flutter/lib/widgets/blackbutton.dart'; // 引入 BlackButton
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),
Expanded(
flex: 2, // 给 TextField 分配更多的空间
child: TextField(
decoration: const InputDecoration(
labelText: '请输入手机号',
border: OutlineInputBorder(),
hintStyle: TextStyle(color: Color(0xffa9a9a9)),
),
style: const TextStyle(color: Colors.white), // 设置输入文字颜色为 FFFFFF
keyboardType: TextInputType.phone,
),
),
],
),
const SizedBox(height: 16.0),
TextField(
decoration: const InputDecoration(
labelText: '请输入密码',
hintStyle: TextStyle(color: Color(0xffa9a9a9)),
border: OutlineInputBorder(),
),
obscureText: true,
style: const 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),
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: [
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('登录按钮被点击');
},
),
],
),
),
),
],
),
),
);
}
}
本文完成,本文对组件进行了完善修改,并且创建了自定义组件,其次还规划了我们整体目录并且创建了对应文件,下一篇我们即可把所有的注册页面写完,毕竟自定义组件都创建了,写几个页面还不是分分钟的事情?
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。