用 Flutter 写一个精美的登录页面(最新版)
参考了博客:用flutter写一个精美的登录页面。但是那篇文章是 18 年的,较多 API 已经更新,本篇博文在其基础上使用最新版本 Dart 和 Flutter 开发。
完整源码在文章最后,有需要可以直接看源码。
效果图:
主体结构
该页面的主体布局如下:
- 整体基于一个 Scaffold,没有 AppBar
- 由于要对输入框进行表单校验,使用了 Form
- 使用 ListView 作为外层控件
- return Scaffold(
- body: Form(
- key: _formKey,
- autovalidateMode: AutovalidateMode.onUserInteraction,
- child: ListView(
- padding: const EdgeInsets.symmetric(horizontal: 20),
- children: [
- const SizedBox(height: kToolbarHeight), // 距离顶部一个工具栏的高度
- buildTitle(), // Login
- buildTitleLine(), // 标题下面的下滑线
- const SizedBox(height: 50),
- buildEmailTextField(), // 输入邮箱
- const SizedBox(height: 30),
- buildPasswordTextField(context), // 输入密码
- buildForgetPasswordText(context), // 忘记密码
- const SizedBox(height: 50),
- buildLoginButton(context), // 登录按钮
- const SizedBox(height: 30),
- buildOtherLoginText(), // 其他账号登录
- buildOtherMethod(context), // 其他登录方式
- buildRegisterText(context), // 注册
- ],
- ),
- ),
- );
复制代码 标题
大标题 Titile 比较简单,就是设置了 边距 和 文字大小:
- Padding 组件:设置边距
- Text 组件:显示文本及控制样式
- Widget buildTitle() {
- return const Padding( // 设置边距
- padding: EdgeInsets.all(8),
- child: Text(
- 'Login',
- style: TextStyle(fontSize: 42),
- ));
- }
复制代码 Title 下面的下划线其实就是个设置了宽高的 Container:
- Align 组件:用于控制子组件的对齐方式
- Container 组件:一个包含绘画、定位、大小的组件
- Widget buildTitleLine() {
- return Padding(
- padding: const EdgeInsets.only(left: 12.0, top: 4.0),
- child: Align(
- alignment: Alignment.bottomLeft,
- child: Container(
- color: Colors.black,
- width: 40,
- height: 2,
- ),
- ));
- }
复制代码 输入框
邮箱输入框:使用正则对输入进行校验
- TextFormField 组件:用于 Form 中的文本输入框
- validator 属性用于对输入框进行表单校验
- onSave 属性当表单校验通过后执行一些操作
何时进行表单校验是 From 组件中的一个属性
- Widget buildEmailTextField() {
- return TextFormField(
- decoration: const InputDecoration(labelText: 'Email Address'),
- validator: (v) {
- var emailReg = RegExp(
- r"[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?");
- if (!emailReg.hasMatch(v!)) {
- return '请输入正确的邮箱地址';
- }
- },
- onSaved: (v) => _email = v!,
- );
- }
复制代码 密码输入框:
- obscureText 属性控制表单文字的显示与隐藏,我们使用自定义变量来实现点击按钮隐藏或显示密码功能
- suffixIcon 属性是在输入框后面加一个图标,给它一个点击方法是改变是否显示密码,并更改图标的颜色
- setState 用于通知 Flutter 框架重绘界面,此处我们利用变量 _isObscure 控制输入框密码的可见性,涉及到界面显示的更新,因此要更新 _isObscure 的代码放在 setState 中。
- Widget buildPasswordTextField(BuildContext context) {
- return TextFormField(
- obscureText: _isObscure, // 是否显示文字
- onSaved: (v) => _password = v!,
- validator: (v) {
- if (v!.isEmpty) {
- return '请输入密码';
- }
- },
- decoration: InputDecoration(
- labelText: "Password",
- suffixIcon: IconButton(
- icon: Icon(
- Icons.remove_red_eye,
- color: _eyeColor,
- ),
- onPressed: () {
- // 修改 state 内部变量, 且需要界面内容更新, 需要使用 setState()
- setState(() {
- _isObscure = !_isObscure;
- _eyeColor = (_isObscure
- ? Colors.grey
- : Theme.of(context).iconTheme.color)!;
- });
- },
- )));
- }
复制代码 密码输入框下面还有一行 “忘记密码?” 的文本,利用 Align 组件将其靠右对齐
- Widget buildForgetPasswordText(BuildContext context) {
- return Padding(
- padding: const EdgeInsets.only(top: 8),
- child: Align(
- alignment: Alignment.centerRight,
- child: TextButton(
- onPressed: () {
- // Navigator.pop(context);
- print("忘记密码");
- },
- child: const Text("忘记密码?",
- style: TextStyle(fontSize: 14, color: Colors.grey)),
- ),
- ),
- );
- }
复制代码 登录按钮
登录按钮:
- ElevatedButton 组件是一个常见的,点击后有波纹效果的按钮
- 点击登录按钮后,进行表单校验
- Widget buildLoginButton(BuildContext context) {
- return Align(
- child: SizedBox(
- height: 45,
- width: 270,
- child: ElevatedButton(
- style: ButtonStyle(
- // 设置圆角
- shape: MaterialStateProperty.all(const StadiumBorder(
- side: BorderSide(style: BorderStyle.none)))),
- child: Text('Login',
- style: Theme.of(context).primaryTextTheme.headline5),
- onPressed: () {
- // 表单校验通过才会继续执行
- if ((_formKey.currentState as FormState).validate()) {
- (_formKey.currentState as FormState).save();
- //TODO 执行登录方法
- print('email: $_email, password: $_password');
- }
- },
- ),
- ),
- );
- }
复制代码 其他登录方式
其他登录方式:
- ButtonBar 组件用于构建多个按钮的排列(方向可控)
- map 是个高阶函数,可以对 数组的每个元素进行某种操作,最后再归约成数组
- SnackBar 是一种从底部出现的轻量级弹窗
- final List _loginMethod = [
- {
- "title": "facebook",
- "icon": Icons.facebook,
- },
- {
- "title": "google",
- "icon": Icons.fiber_dvr,
- },
- {
- "title": "twitter",
- "icon": Icons.account_balance,
- },
- ];
- Widget buildOtherMethod(context) {
- return ButtonBar(
- alignment: MainAxisAlignment.center,
- children: _loginMethod
- .map((item) => Builder(builder: (context) {
- return IconButton(
- icon: Icon(item['icon'],
- color: Theme.of(context).iconTheme.color),
- onPressed: () {
- //TODO: 第三方登录方法
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: Text('${item['title']}登录'),
- action: SnackBarAction(
- label: '取消',
- onPressed: () {},
- )),
- );
- });
- }))
- .toList(),
- );
- }
复制代码 注册按钮
注册按钮:
- GestureDetector 是手势检测器,用它包裹组件后可以实现对该组件的各种手势的监听,例如:“单击”、“双击”、“长按” 等。
- Widget buildRegisterText(context) {
- return Center(
- child: Padding(
- padding: const EdgeInsets.only(top: 10),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- const Text('没有账号?'),
- GestureDetector(
- child: const Text('点击注册', style: TextStyle(color: Colors.green)),
- onTap: () {
- print("点击注册");
- },
- )
- ],
- ),
- ),
- );
- }
复制代码 完整源码
新建一个 Flutter 项目,替换其中 main.dart 的内容,即可运行起来。
- import 'package:flutter/material.dart';
- void main() => runApp(const MyApp());
- class MyApp extends StatelessWidget {
- const MyApp({Key? key}) : super(key: key);
- @override
- Widget build(BuildContext context) {
- return MaterialApp(
- debugShowCheckedModeBanner: false, // 不显示右上角的 debug
- title: 'Flutter Demo',
- theme: ThemeData(
- primarySwatch: Colors.blue,
- ),
- // 注册路由表
- routes: {
- "/": (context) => const HomePage(title: "登录"), // 首页路由
- });
- }
- }
- class HomePage extends StatefulWidget {
- const HomePage({Key? key, required this.title}) : super(key: key);
- final String title;
- @override
- _HomePageState createState() => _HomePageState();
- }
- class _HomePageState extends State<HomePage> {
- final GlobalKey _formKey = GlobalKey<FormState>();
- late String _email, _password;
- bool _isObscure = true;
- Color _eyeColor = Colors.grey;
- final List _loginMethod = [
- {
- "title": "facebook",
- "icon": Icons.facebook,
- },
- {
- "title": "google",
- "icon": Icons.fiber_dvr,
- },
- {
- "title": "twitter",
- "icon": Icons.account_balance,
- },
- ];
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- body: Form(
- key: _formKey, // 设置globalKey,用于后面获取FormStat
- autovalidateMode: AutovalidateMode.onUserInteraction,
- child: ListView(
- padding: const EdgeInsets.symmetric(horizontal: 20),
- children: [
- const SizedBox(height: kToolbarHeight), // 距离顶部一个工具栏的高度
- buildTitle(), // Login
- buildTitleLine(), // Login下面的下划线
- const SizedBox(height: 60),
- buildEmailTextField(), // 输入邮箱
- const SizedBox(height: 30),
- buildPasswordTextField(context), // 输入密码
- buildForgetPasswordText(context), // 忘记密码
- const SizedBox(height: 60),
- buildLoginButton(context), // 登录按钮
- const SizedBox(height: 40),
- buildOtherLoginText(), // 其他账号登录
- buildOtherMethod(context), // 其他登录方式
- buildRegisterText(context), // 注册
- ],
- ),
- ),
- );
- }
- Widget buildRegisterText(context) {
- return Center(
- child: Padding(
- padding: const EdgeInsets.only(top: 10),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- const Text('没有账号?'),
- GestureDetector(
- child: const Text('点击注册', style: TextStyle(color: Colors.green)),
- onTap: () {
- print("点击注册");
- },
- )
- ],
- ),
- ),
- );
- }
- Widget buildOtherMethod(context) {
- return ButtonBar(
- alignment: MainAxisAlignment.center,
- children: _loginMethod
- .map((item) => Builder(builder: (context) {
- return IconButton(
- icon: Icon(item['icon'],
- color: Theme.of(context).iconTheme.color),
- onPressed: () {
- //TODO: 第三方登录方法
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: Text('${item['title']}登录'),
- action: SnackBarAction(
- label: '取消',
- onPressed: () {},
- )),
- );
- });
- }))
- .toList(),
- );
- }
- Widget buildOtherLoginText() {
- return const Center(
- child: Text(
- '其他账号登录',
- style: TextStyle(color: Colors.grey, fontSize: 14),
- ),
- );
- }
- Widget buildLoginButton(BuildContext context) {
- return Align(
- child: SizedBox(
- height: 45,
- width: 270,
- child: ElevatedButton(
- style: ButtonStyle(
- // 设置圆角
- shape: MaterialStateProperty.all(const StadiumBorder(
- side: BorderSide(style: BorderStyle.none)))),
- child: Text('Login',
- style: Theme.of(context).primaryTextTheme.headline5),
- onPressed: () {
- // 表单校验通过才会继续执行
- if ((_formKey.currentState as FormState).validate()) {
- (_formKey.currentState as FormState).save();
- //TODO 执行登录方法
- print('email: $_email, password: $_password');
- }
- },
- ),
- ),
- );
- }
- Widget buildForgetPasswordText(BuildContext context) {
- return Padding(
- padding: const EdgeInsets.only(top: 8),
- child: Align(
- alignment: Alignment.centerRight,
- child: TextButton(
- onPressed: () {
- // Navigator.pop(context);
- print("忘记密码");
- },
- child: const Text("忘记密码?",
- style: TextStyle(fontSize: 14, color: Colors.grey)),
- ),
- ),
- );
- }
- Widget buildPasswordTextField(BuildContext context) {
- return TextFormField(
- obscureText: _isObscure, // 是否显示文字
- onSaved: (v) => _password = v!,
- validator: (v) {
- if (v!.isEmpty) {
- return '请输入密码';
- }
- },
- decoration: InputDecoration(
- labelText: "Password",
- suffixIcon: IconButton(
- icon: Icon(
- Icons.remove_red_eye,
- color: _eyeColor,
- ),
- onPressed: () {
- // 修改 state 内部变量, 且需要界面内容更新, 需要使用 setState()
- setState(() {
- _isObscure = !_isObscure;
- _eyeColor = (_isObscure
- ? Colors.grey
- : Theme.of(context).iconTheme.color)!;
- });
- },
- )));
- }
- Widget buildEmailTextField() {
- return TextFormField(
- decoration: const InputDecoration(labelText: 'Email Address'),
- validator: (v) {
- var emailReg = RegExp(
- r"[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?");
- if (!emailReg.hasMatch(v!)) {
- return '请输入正确的邮箱地址';
- }
- },
- onSaved: (v) => _email = v!,
- );
- }
- Widget buildTitleLine() {
- return Padding(
- padding: const EdgeInsets.only(left: 12.0, top: 4.0),
- child: Align(
- alignment: Alignment.bottomLeft,
- child: Container(
- color: Colors.black,
- width: 40,
- height: 2,
- ),
- ));
- }
- Widget buildTitle() {
- return const Padding(
- padding: EdgeInsets.all(8),
- child: Text(
- 'Login',
- style: TextStyle(fontSize: 42),
- ));
- }
- }
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |