B站视频
https://www.bilibili.com/vide...
本节目标
- 静态路由
- 带阴影的椭圆图标
- 输入有效性校验
- 组件抽取方法
- 通用组件、业务组件
- 程序目录组织
- 抽取透明导航栏
- toast 提示组件
1 静态路由
1.1 定义静态路由
- 登录页 lib/pages/sign_in/sign_in.dart
- 注册页 lib/pages/sign_up/sign_up.dart
- 静态路由 lib/routes.dart
import 'package:flutter_ducafecat_news/pages/sign_in/sign_in.dart';
import 'package:flutter_ducafecat_news/pages/sign_up/sign_up.dart';
/// 静态路由
var staticRoutes = {
"/sign-in": (context) => SignInPage(), // 登录
"/sign-up": (context) => SignUpPage(), // 注册
};
1.2 注册静态路由
- lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_ducafecat_news/pages/welcome/welcome.dart';
import 'package:flutter_ducafecat_news/routes.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'ducafecat.tech',
home: WelcomePage(),
routes: staticRoutes,
debugShowCheckedModeBanner: false,
);
}
}
2 登录界面
2.1 维护色彩常量
- lib/common/values/colors.dart
import 'dart:ui';
class AppColors {
/// 主背景 白色
static const Color primaryBackground = Color.fromARGB(255, 255, 255, 255);
/// 主文本 灰色
static const Color primaryText = Color.fromARGB(255, 45, 45, 47);
/// 主控件-背景 蓝色
static const Color primaryElement = Color.fromARGB(255, 41, 103, 255);
/// 主控件-文本 白色
static const Color primaryElementText = Color.fromARGB(255, 255, 255, 255);
// *****************************************
/// 第二种控件-背景色 淡灰色
static const Color secondaryElement = Color.fromARGB(255, 246, 246, 246);
/// 第二种控件-文本 浅蓝色
static const Color secondaryElementText = Color.fromARGB(255, 41, 103, 255);
// *****************************************
/// 第三种控件-背景色 石墨色
static const Color thirdElement = Color.fromARGB(255, 45, 45, 47);
}
2.2 程序结构
- lib/pages/sign_in/sign_in.dart
import 'package:flutter/material.dart';
import 'package:flutter_ducafecat_news/common/utils/utils.dart';
import 'package:flutter_ducafecat_news/common/values/values.dart';
import 'package:flutter_ducafecat_news/common/widgets/widgets.dart';
class SignInPage extends StatefulWidget {
SignInPage({Key key}) : super(key: key);
@override
_SignInPageState createState() => _SignInPageState();
}
class _SignInPageState extends State {
// logo
Widget _buildLogo() {
return Container();
}
// 登录表单
Widget _buildInputForm() {
return Container();
}
// 第三方登录
Widget _buildThirdPartyLogin() {
return Container();
}
// 注册按钮
Widget _buildSignupButton() {
return Container();
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Center(
child: Column(
children: [
_buildLogo(),
_buildInputForm(),
Spacer(),
_buildThirdPartyLogin(),
_buildSignupButton(),
],
),
),
);
}
}
2.3 画带阴影的椭圆图标
- lib/pages/sign_in/sign_in.dart
// logo
Widget _buildLogo() {
return Container(
width: duSetWidth(110),
margin: EdgeInsets.only(top: duSetHeight(40 + 44.0)), // 顶部系统栏 44px
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
height: duSetWidth(76),
width: duSetWidth(76),
margin: EdgeInsets.symmetric(horizontal: duSetWidth(15)),
child: Stack(
alignment: Alignment.center,
children: [
Positioned(
left: 0,
top: 0,
right: 0,
child: Container(
height: duSetWidth(76),
decoration: BoxDecoration(
color: AppColors.primaryBackground,
boxShadow: [
Shadows.primaryShadow,
],
borderRadius: BorderRadius.all(
Radius.circular(duSetWidth(76 * 0.5))), // 父容器的50%
),
child: Container(),
),
),
Positioned(
top: duSetWidth(13),
child: Image.asset(
"assets/images/logo.png",
fit: BoxFit.none,
),
),
],
),
),
Container(
margin: EdgeInsets.only(top: duSetHeight(15)),
child: Text(
"SECTOR",
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.primaryText,
fontFamily: "Montserrat",
fontWeight: FontWeight.w600,
fontSize: duSetFontSize(24),
height: 1,
),
),
),
Text(
"news",
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.primaryText,
fontFamily: "Avenir",
fontWeight: FontWeight.w400,
fontSize: duSetFontSize(16),
height: 1,
),
),
],
),
);
}
2.4 抽取输入框
- lib/common/widgets/input.dart
/// 输入框
Widget inputTextEdit({
@required TextEditingController controller,
TextInputType keyboardType = TextInputType.text,
String hintText,
bool isPassword = false,
double marginTop = 15,
}) {
return Container(
height: duSetHeight(44),
margin: EdgeInsets.only(top: duSetHeight(marginTop)),
decoration: BoxDecoration(
color: AppColors.secondaryElement,
borderRadius: Radii.k6pxRadius,
),
child: TextField(
controller: controller,
keyboardType: keyboardType,
decoration: InputDecoration(
hintText: hintText,
contentPadding: EdgeInsets.fromLTRB(20, 10, 0, 9),
border: InputBorder.none,
),
style: TextStyle(
color: AppColors.primaryText,
fontFamily: "Avenir",
fontWeight: FontWeight.w400,
fontSize: duSetFontSize(18),
),
maxLines: 1,
autocorrect: false, // 自动纠正
obscureText: isPassword, // 隐藏输入内容, 密码框
),
);
}
2.5 抽取扁平按钮
- lib/common/widgets/button.dart
/// 扁平圆角按钮
Widget btnFlatButtonWidget({
@required VoidCallback onPressed,
double width = 140,
double height = 44,
Color gbColor = AppColors.primaryElement,
String title = "button",
Color fontColor = AppColors.primaryElementText,
double fontSize = 18,
String fontName = "Montserrat",
FontWeight fontWeight = FontWeight.w400,
}) {
return Container(
width: duSetWidth(width),
height: duSetHeight(height),
child: FlatButton(
onPressed: onPressed,
color: gbColor,
shape: RoundedRectangleBorder(
borderRadius: Radii.k6pxRadius,
),
child: Text(
title,
textAlign: TextAlign.center,
style: TextStyle(
color: fontColor,
fontFamily: fontName,
fontWeight: fontWeight,
fontSize: duSetFontSize(fontSize),
height: 1,
),
),
),
);
}
2.6 抽取社交登录按钮
- lib/common/widgets/button.dart
/// 第三方按钮
Widget btnFlatButtonBorderOnlyWidget({
@required VoidCallback onPressed,
double width = 88,
double height = 44,
String iconFileName,
}) {
return Container(
width: duSetWidth(width),
height: duSetHeight(height),
child: FlatButton(
onPressed: onPressed,
shape: RoundedRectangleBorder(
side: Borders.primaryBorder,
borderRadius: Radii.k6pxRadius,
),
child: Image.asset(
"assets/images/icons-$iconFileName.png",
),
),
);
}
2.7 封装 toast 提示框
- lib/common/widgets/toast.dart
Future toastInfo({
@required String msg,
Color backgroundColor = Colors.black,
Color textColor = Colors.white,
}) async {
return await Fluttertoast.showToast(
msg: msg,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.TOP,
timeInSecForIos: 1,
backgroundColor: backgroundColor,
textColor: textColor,
fontSize: duSetFontSize(16),
);
}
2.8 数据有效性检验
- lib/pages/sign_in/sign_in.dart
...
class _SignInPageState extends State {
//email的控制器
final TextEditingController _emailController = TextEditingController();
//密码的控制器
final TextEditingController _passController = TextEditingController();
...
// 执行登录操作
_handleSignIn() {
if (!duIsEmail(_emailController.value.text)) {
toastInfo(msg: '请正确输入邮件');
return;
}
if (!duCheckStringLength(_passController.value.text, 6)) {
toastInfo(msg: '密码不能小于6位');
return;
}
}
...
// 登录表单
Widget _buildInputForm() {
return Container(
width: duSetWidth(295),
// height: 204,
margin: EdgeInsets.only(top: duSetHeight(49)),
child: Column(
children: [
// email input
inputTextEdit(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
hintText: "Email",
marginTop: 0,
),
// password input
inputTextEdit(
controller: _passController,
keyboardType: TextInputType.visiblePassword,
hintText: "Password",
isPassword: true,
),
// 注册、登录 横向布局
Container(
height: duSetHeight(44),
margin: EdgeInsets.only(top: duSetHeight(15)),
child: Row(
children: [
// 注册
btnFlatButtonWidget(
onPressed: _handleNavSignUp,
gbColor: AppColors.thirdElement,
title: "Sign up",
),
Spacer(),
// 登录
btnFlatButtonWidget(
onPressed: _handleSignIn,
gbColor: AppColors.primaryElement,
title: "Sign in",
),
],
),
),
// Spacer(),
// Fogot password
Container(
height: duSetHeight(22),
margin: EdgeInsets.only(top: duSetHeight(20)),
child: FlatButton(
onPressed: () => {},
child: Text(
"Fogot password?",
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.secondaryElementText,
fontFamily: "Avenir",
fontWeight: FontWeight.w400,
fontSize: duSetFontSize(16),
height: 1, // 设置下行高,否则字体下沉
),
),
),
),
],
),
);
}
3 注册界面
3.1 程序结构
- lib/pages/sign_up/sign_up.dart
import 'package:flutter/material.dart';
import 'package:flutter_ducafecat_news/common/utils/utils.dart';
import 'package:flutter_ducafecat_news/common/values/values.dart';
import 'package:flutter_ducafecat_news/common/widgets/widgets.dart';
class SignUpPage extends StatefulWidget {
SignUpPage({Key key}) : super(key: key);
@override
_SignUpPageState createState() => _SignUpPageState();
}
class _SignUpPageState extends State {
// logo
Widget _buildLogo() {
return Container();
}
// 注册表单
Widget _buildInputForm() {
return Container();
}
// 第三方
Widget _buildThirdPartyLogin() {
return Container();
}
// 有账号
Widget _buildHaveAccountButton() {
return Container();
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
...,
body: Center(
child: Column(
children: [
Divider(height: 1),
_buildLogo(),
_buildInputForm(),
Spacer(),
_buildThirdPartyLogin(),
_buildHaveAccountButton(),
],
),
),
);
}
}
3.2 透明导航栏
- lib/common/widgets/app.dart
/// 透明背景 AppBar
Widget transparentAppBar({
@required BuildContext context,
List actions,
}) {
return AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
title: Text(''),
leading: IconButton(
icon: Icon(
Icons.arrow_back,
color: AppColors.primaryText,
),
onPressed: () {
Navigator.pop(context);
},
),
actions: actions,
);
}
- lib/pages/sign_up/sign_up.dart
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: transparentAppBar(
context: context,
actions: [
IconButton(
icon: Icon(
Icons.info_outline,
color: AppColors.primaryText,
),
onPressed: () {
toastInfo(msg: '这是注册界面');
},
)
],
),
3.2 注册表单
- lib/pages/sign_up/sign_up.dart
// 注册表单
Widget _buildInputForm() {
return Container(
width: duSetWidth(295),
// height: 204,
margin: EdgeInsets.only(top: duSetHeight(49)),
child: Column(
children: [
// fullName input
inputTextEdit(
controller: _fullnameController,
keyboardType: TextInputType.text,
hintText: "Full name",
marginTop: 0,
),
// email input
inputTextEdit(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
hintText: "Email",
),
// password input
inputTextEdit(
controller: _passController,
keyboardType: TextInputType.visiblePassword,
hintText: "Password",
isPassword: true,
),
// 创建
Container(
height: duSetHeight(44),
margin: EdgeInsets.only(top: duSetHeight(15)),
child: btnFlatButtonWidget(
onPressed: () {
if (!duCheckStringLength(_fullnameController.value.text, 5)) {
toastInfo(msg: '用户名不能小于5位');
return;
}
if (!duIsEmail(_emailController.value.text)) {
toastInfo(msg: '请正确输入邮件');
return;
}
if (!duCheckStringLength(_passController.value.text, 6)) {
toastInfo(msg: '密码不能小于6位');
return;
}
Navigator.pop(context);
},
width: 295,
fontWeight: FontWeight.w600,
title: "Create an account",
),
),
// Spacer(),
// Fogot password
Container(
height: duSetHeight(22),
margin: EdgeInsets.only(top: duSetHeight(20)),
child: FlatButton(
onPressed: _handleSignUp,
child: Text(
"Fogot password?",
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.secondaryElementText,
fontFamily: "Avenir",
fontWeight: FontWeight.w400,
fontSize: duSetFontSize(16),
height: 1, // 设置下行高,否则字体下沉
),
),
),
),
],
),
);
}
3.3 检验有效性
- lib/pages/sign_up/sign_up.dart
// 执行注册操作
_handleSignUp() {
if (!duCheckStringLength(_fullnameController.value.text, 5)) {
toastInfo(msg: '用户名不能小于5位');
return;
}
if (!duIsEmail(_emailController.value.text)) {
toastInfo(msg: '请正确输入邮件');
return;
}
if (!duCheckStringLength(_passController.value.text, 6)) {
toastInfo(msg: '密码不能小于6位');
return;
}
Navigator.pop(context);
}
3.4 社交按钮
- lib/pages/sign_up/sign_up.dart
// 第三方
Widget _buildThirdPartyLogin() {
return Container(
width: duSetWidth(295),
margin: EdgeInsets.only(bottom: duSetHeight(40)),
child: Column(
children: [
// title
Text(
"Or sign in with social networks",
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.primaryText,
fontFamily: "Avenir",
fontWeight: FontWeight.w400,
fontSize: duSetFontSize(16),
),
),
// 按钮
Padding(
padding: EdgeInsets.only(top: duSetHeight(20)),
child: Row(
children: [
btnFlatButtonBorderOnlyWidget(
onPressed: () {},
width: 88,
iconFileName: "twitter",
),
Spacer(),
btnFlatButtonBorderOnlyWidget(
onPressed: () {},
width: 88,
iconFileName: "google",
),
Spacer(),
btnFlatButtonBorderOnlyWidget(
onPressed: () {},
width: 88,
iconFileName: "facebook",
),
],
),
),
],
),
);
}
3.5 返回按钮
- lib/pages/sign_up/sign_up.dart
// 返回上一页
_handleNavPop() {
Navigator.pop(context);
}
Widget _buildHaveAccountButton() {
return Container(
margin: EdgeInsets.only(bottom: duSetHeight(20)),
child: btnFlatButtonWidget(
onPressed: _handleNavPop,
width: 294,
gbColor: AppColors.secondaryElement,
fontColor: AppColors.primaryText,
title: "I have an account",
fontWeight: FontWeight.w500,
fontSize: 16,
),
);
}
git 代码
https://github.com/ducafecat/...
蓝湖设计稿
https://lanhuapp.com/url/lYuz1
密码: gSKl
蓝湖现在收费了,所以查看标记还请自己上传 xd 设计稿
商业设计稿文件不好直接分享, 可以加微信联系 ducafecat