综合运用布局Widget,打造一个美观实用的登录界面。
你好,欢迎回到《Flutter入门到精通》专栏。通过前七讲的学习,我们已经掌握了Flutter的核心概念和主要布局Widget。今天,我们将通过构建一个完整的登录页面,把所有的知识点串联起来,让你体验真实的开发流程。
一、项目分析与设计
在开始编码之前,让我们先规划一下登录页面需要包含哪些元素:
页面组成元素
-
顶部区域:Logo和应用名称
-
表单区域:
-
用户名/邮箱输入框
-
密码输入框(带显示/隐藏切换)
-
记住我选项
-
忘记密码链接
-
-
按钮区域:
-
登录按钮
-
第三方登录选项
-
注册链接
-
-
装饰元素:
-
背景设计
-
间距和边框
-
布局结构规划
text
Column (整个页面滚动布局) ├── SizedBox (顶部间距) ├── Logo区域 ├── SizedBox (间距) ├── 表单Card │ ├── 标题文本 │ ├── 邮箱输入框 │ ├── 密码输入框 │ ├── Row (记住我和忘记密码) │ └── 登录按钮 ├── 分割线 ├── 第三方登录按钮 └── 注册链接
二、逐步实现登录页面
让我们一步一步地构建这个登录页面。
步骤1:创建基础页面结构
首先创建一个新的Dart文件 login_page.dart:
dart
import 'package:flutter/material.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
// 控制输入框的控制器
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
// 状态变量
bool _rememberMe = false;
bool _obscurePassword = true; // 控制密码是否明文显示
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[50],
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 这里将逐步添加各个组件
],
),
),
),
);
}
}
步骤2:实现Logo和标题区域
在Column的children中添加Logo区域:
dart
// 返回按钮和Logo区域 _buildHeader(context), SizedBox(height: 40), // 欢迎文本 _buildWelcomeText(), SizedBox(height: 32),
然后实现对应的私有方法:
dart
// 构建头部区域
Widget _buildHeader(BuildContext context) {
return Row(
children: [
// 返回按钮
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: IconButton(
icon: const Icon(Icons.arrow_back, size: 20),
onPressed: () => Navigator.of(context).pop(),
),
),
const Spacer(),
// Logo
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(12),
),
child: const Icon(
Icons.flutter_dash,
color: Colors.white,
size: 30,
),
),
const Spacer(),
const SizedBox(width: 40), // 用于平衡布局的占位空间
],
);
}
// 构建欢迎文本
Widget _buildWelcomeText() {
return const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'欢迎回来',
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
SizedBox(height: 8),
Text(
'请登录您的账户继续使用',
style: TextStyle(
fontSize: 16,
color: Colors.grey,
),
),
],
);
}
步骤3:构建表单卡片
现在创建主要的表单区域:
dart
// 表单卡片 _buildLoginForm(), SizedBox(height: 24),
实现表单构建方法:
dart
Widget _buildLoginForm() {
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 15,
offset: const Offset(0, 5),
),
],
),
child: Column(
children: [
// 邮箱输入框
_buildEmailField(),
const SizedBox(height: 20),
// 密码输入框
_buildPasswordField(),
const SizedBox(height: 16),
// 记住我和忘记密码
_buildRememberAndForgot(),
const SizedBox(height: 24),
// 登录按钮
_buildLoginButton(),
],
),
);
}
步骤4:实现表单字段
现在实现各个表单字段的详细构建方法:
dart
// 构建邮箱输入框
Widget _buildEmailField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'邮箱地址',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
const SizedBox(height: 8),
Container(
decoration: BoxDecoration(
color: Colors.grey[50],
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[300]!),
),
child: TextField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
prefixIcon: Icon(Icons.email_outlined, color: Colors.grey),
border: InputBorder.none,
hintText: '请输入您的邮箱',
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 16),
),
),
),
],
);
}
// 构建密码输入框
Widget _buildPasswordField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'密码',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
const SizedBox(height: 8),
Container(
decoration: BoxDecoration(
color: Colors.grey[50],
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[300]!),
),
child: TextField(
controller: _passwordController,
obscureText: _obscurePassword,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.lock_outline, color: Colors.grey),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined,
color: Colors.grey,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
border: InputBorder.none,
hintText: '请输入您的密码',
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
),
),
),
],
);
}
// 构建记住我和忘记密码行
Widget _buildRememberAndForgot() {
return Row(
children: [
// 记住我复选框
Row(
children: [
SizedBox(
width: 24,
height: 24,
child: Checkbox(
value: _rememberMe,
onChanged: (value) {
setState(() {
_rememberMe = value!;
});
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
),
),
const SizedBox(width: 8),
const Text(
'记住我',
style: TextStyle(fontSize: 14, color: Colors.grey),
),
],
),
const Spacer(),
// 忘记密码链接
GestureDetector(
onTap: () {
// 处理忘记密码逻辑
_showForgotPasswordDialog();
},
child: const Text(
'忘记密码?',
style: TextStyle(
fontSize: 14,
color: Colors.blue,
fontWeight: FontWeight.w500,
),
),
),
],
);
}
步骤5:实现登录按钮和其他操作区域
dart
// 构建登录按钮
Widget _buildLoginButton() {
return SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _performLogin,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
),
child: const Text(
'登录',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
);
}
// 构建第三方登录和注册区域
Widget _buildThirdPartyAndRegister() {
return Column(
children: [
// 分割线
_buildDivider(),
const SizedBox(height: 24),
// 第三方登录
_buildThirdPartyLogin(),
const SizedBox(height: 32),
// 注册链接
_buildRegisterLink(),
],
);
}
// 构建分割线
Widget _buildDivider() {
return Row(
children: [
Expanded(
child: Container(height: 1, color: Colors.grey[300]),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
'或使用其他方式登录',
style: TextStyle(
fontSize: 14,
color: Colors.grey[500],
),
),
),
Expanded(
child: Container(height: 1, color: Colors.grey[300]),
),
],
);
}
// 构建第三方登录按钮
Widget _buildThirdPartyLogin() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildThirdPartyButton(
icon: Icons.g_mobiledata,
onPressed: () => _loginWithGoogle(),
),
const SizedBox(width: 16),
_buildThirdPartyButton(
icon: Icons.apple,
onPressed: () => _loginWithApple(),
),
const SizedBox(width: 16),
_buildThirdPartyButton(
icon: Icons.wechat,
onPressed: () => _loginWithWechat(),
),
],
);
}
// 构建单个第三方登录按钮
Widget _buildThirdPartyButton({
required IconData icon,
required VoidCallback onPressed,
}) {
return Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.grey[300]!),
),
child: IconButton(
icon: Icon(icon, color: Colors.grey[600]),
onPressed: onPressed,
),
);
}
// 构建注册链接
Widget _buildRegisterLink() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'还没有账户?',
style: TextStyle(fontSize: 14, color: Colors.grey),
),
const SizedBox(width: 4),
GestureDetector(
onTap: () {
// 跳转到注册页面
_navigateToRegister();
},
child: const Text(
'立即注册',
style: TextStyle(
fontSize: 14,
color: Colors.blue,
fontWeight: FontWeight.w600,
),
),
),
],
);
}
步骤6:实现业务逻辑方法
最后,实现所有的业务逻辑方法:
dart
// 执行登录操作
void _performLogin() {
final email = _emailController.text.trim();
final password = _passwordController.text;
// 简单的表单验证
if (email.isEmpty || password.isEmpty) {
_showErrorDialog('请输入邮箱和密码');
return;
}
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(email)) {
_showErrorDialog('请输入有效的邮箱地址');
return;
}
// 显示加载指示器
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const Center(
child: CircularProgressIndicator(),
),
);
// 模拟网络请求
Future.delayed(const Duration(seconds: 2), () {
Navigator.of(context).pop(); // 关闭加载指示器
// 模拟登录成功
if (email == 'test@example.com' && password == '123456') {
_showSuccessDialog();
} else {
_showErrorDialog('邮箱或密码错误');
}
});
}
// 显示成功对话框
void _showSuccessDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Row(
children: [
Icon(Icons.check_circle, color: Colors.green),
SizedBox(width: 8),
Text('登录成功'),
],
),
content: const Text('欢迎回来!您已成功登录。'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('确定'),
),
],
),
);
}
// 显示错误对话框
void _showErrorDialog(String message) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Row(
children: [
Icon(Icons.error_outline, color: Colors.red),
SizedBox(width: 8),
Text('登录失败'),
],
),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('确定'),
),
],
),
);
}
// 显示忘记密码对话框
void _showForgotPasswordDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('忘记密码'),
content: const Text('我们将向您的注册邮箱发送密码重置链接。'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('取消'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
// 这里可以添加发送重置邮件的逻辑
},
child: const Text('发送'),
),
],
),
);
}
// 第三方登录方法
void _loginWithGoogle() {
// 实现Google登录逻辑
}
void _loginWithApple() {
// 实现Apple登录逻辑
}
void _loginWithWechat() {
// 实现微信登录逻辑
}
void _navigateToRegister() {
// 跳转到注册页面
}
三、在main.dart中使用登录页面
修改 lib/main.dart 来使用我们的登录页面:
dart
import 'package:flutter/material.dart';
import 'login_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter登录演示',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const LoginPage(), // 设置登录页面为首页
);
}
}
四、功能亮点总结
通过这个完整的登录页面,我们实践了:
-
复杂布局:综合运用了Column、Row、Stack、Container、SizedBox等布局Widget
-
表单处理:使用TextEditingController管理输入框状态
-
交互反馈:实现密码显示/隐藏、复选框、按钮点击等交互
-
状态管理:使用setState管理页面状态
-
对话框:显示加载指示器、成功/错误提示
-
样式美化:使用BoxDecoration创建卡片阴影、圆角等视觉效果
结语
恭喜!你已经成功构建了一个功能完整、界面美观的登录页面。这个项目综合运用了前面所有课程的知识点,是你Flutter学习之路上的一个重要里程碑。
在下一讲中,我们将开始学习如何在Flutter中展示动态数据,重点学习 ListView和GridView 的使用,让你能够构建可以滚动和网格布局的列表界面。

被折叠的 条评论
为什么被折叠?



