我是 Zero,今天我们要用 Flutter 敲出上面?的效果
粗略一看
嗯?… 很漂亮… 很笔优特佛(Beautiful
)?
再粗略一看
?小老弟,你唬我呢?不就是个登录注册页面吗?
再仔细一看
不对?,居然有下面?这么多的知识点
核心知识点
- Container
- Text
- Image
- Column
- Row
- Stack
- SizedBox
- TextField
- Padding
- Spacer
- Positioned
- GestureDetector
- Divider
- ClipPath
- Path
- Bezier(贝塞尔曲线)
- Flutter 组件抽离
看完文章,点赞后再点击对应的 Widget 可直接进入 Flutter 官方文档?,(超级贴心?
)
- 先赞后看,更新永不断?
- 好的,我们进入正题
? 项目介绍 ?
这是我的第 2 个 Speed Code 视频项目文章,通过此文章你可以学习到如上 Widget
的基础
或进阶
用法,更重要的你可以学习到如何将这些 Widget
灵活的组合,最终实现上面?的效果。
如果觉得对你有帮助可以点个赞? ,我会更有动力录制分享更多 Flutter 优质内容,谢谢你的赞
定义通用主题
- 大小
/// theme/app_size.dart
import 'package:flutter/material.dart';
// 标题文字大小
const double kTitleTextSize = 24;
// 内容体文字大小
const double kBodyTextSize = 14;
// 按钮文字大小
const double kBtnTextSize = 18;
// 按钮圆角半径
const double kBtnRadius = 24;
// 输入框边框圆角半径
const double kInputBorderRadius = 5;
// icon 大小
const double kIconSize = 24;
// icon 盒子大小
const double kIconBoxSize = 56;
// Light 字重
const FontWeight kLightFontWeight = FontWeight.w300;
// Medium 字重
const FontWeight kMediumFontWeight = FontWeight.w500;
复制代码
- 颜色
/// theme/app_colors.dart
import 'package:flutter/widgets.dart';
// 背景颜色
const Color kBgColor = Color(0xFFFEDCE0);
// 文字颜色
const Color kTextColor = Color(0xFF3D0007);
// 按钮开始颜色
const Color kBtnColorStart = Color(0xFFF89500);
// 按钮结束颜色
const Color kBtnColorEnd = Color(0xFFFA6B74);
// 按钮投影颜色
const Color kBtnShadowColor = Color(0x33D83131);
// 输入框边框颜色
const Color kInputBorderColor = Color(0xFFECECEC);
// 按钮渐变背景色
const LinearGradient kBtnLinearGradient = LinearGradient(
colors: [
kBtnColorStart,
kBtnColorEnd,
],
);
复制代码
- 样式
/// theme/app_style.dart
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'app_colors.dart';
import 'app_size.dart';
// 按钮投影
const List<BoxShadow> kBtnShadow = [
BoxShadow(
color: kBtnShadowColor,
offset: Offset(0, 8),
blurRadius: 20,
)
];
// 按钮文字样式
const TextStyle kBtnTextStyle = TextStyle(
color: kBtnColorStart,
fontSize: kBtnTextSize,
fontWeight: kMediumFontWeight,
);
// 标题文字样式
const TextStyle kTitleTextStyle = TextStyle(
fontSize: kTitleTextSize,
color: kTextColor,
fontWeight: kMediumFontWeight,
);
// 内容文字样式
const TextStyle kBodyTextStyle = TextStyle(
fontSize: kBodyTextSize,
color: kTextColor,
fontWeight: kLightFontWeight,
);
// 输入框边框
OutlineInputBorder kInputBorder = OutlineInputBorder(
borderRadius: BorderRadius.circular(5),
borderSide: BorderSide(
color: kInputBorderColor,
width: 1,
),
);
复制代码
构建欢迎页面
绘制头部内容
- 1、绘制头部背景
// WelBgHeader
Image.asset(
'assets/images/bg_welcome_header.png'
)
复制代码
- 2、绘制 App Icon
// AppIconWidget
Container(
width: kIconBoxSize,
height: kIconBoxSize,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
alignment: Alignment.center,
child: Image.asset(
'assets/icons/app_icon.png',
width: 24,
height: 32,
),
)
复制代码
- 3、绘制 Icon 下的文字
/// Icon Text
Positioned(
top: 194,
left: 40,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AppIconWidget(),
SizedBox(height: 8),
Text(
'Sour',
style: kTitleTextStyle,
),
SizedBox(height: 8),
Text(
'Best drink\nreceipes',
style: kBodyTextStyle,
),
],
),
),
复制代码
- 4、设置整体背景色
/// WelcomePage
Scaffold(
backgroundColor: kBgColor,
body: Column(
children: [
WelcomeHeaderWidget(),
],
),
)
复制代码
绘制底部内容
仔细看注释,很重要
- 1、绘制按钮渐变色背景
// 按钮渐变背景色
const LinearGradient kBtnLinearGradient = LinearGradient(
colors: [
kBtnColorStart,
kBtnColorEnd,
],
);
// 按钮投影
const List<BoxShadow> kBtnShadow = [
BoxShadow(
color: kBtnShadowColor,
offset: Offset(0, 8),
blurRadius: 20,
)
];
// 渐变色按钮
// GradientBtnWidget
SizedBox(
width: width,
height: 48,
child: GestureDetector(
onTap: onTap,
child: Container(
decoration: BoxDecoration(
// 设置渐变色
gradient: kBtnLinearGradient,
// 设置投影
boxShadow: kBtnShadow,
// 设置圆角半径
borderRadius: BorderRadius.circular(kBtnRadius),
),
alignment: Alignment.center,
child: child,
),
),
)
复制代码
- 2、绘制文字
// 按钮文字样式
const TextStyle kBtnTextStyle = TextStyle(
color: kBtnColorStart,
fontSize: kBtnTextSize,
fontWeight: kMediumFontWeight,
);
// 白色按钮文字
// BtnTextWhiteWidget
Text(
text,
style: kBtnTextStyle.copyWith(
color: Colors.white,
),
)
复制代码
- 3、组合
// Sign up 按钮
GradientBtnWidget(
width: 208,
child: BtnTextWhiteWidget(text: 'Sign up'),
onTap: () {},
)
复制代码
- 4、绘制登录按钮
// LoginBtnWidget
Container(
height: 48,
width: 208,
decoration: BoxDecoration(
// 设置白色
color: Colors.white,
// 设置圆角半径
borderRadius: BorderRadius.circular(kBtnRadius),
// 设置投影
boxShadow: kBtnShadow,
),
alignment: Alignment.center,
child: Text(
'Login',
style: kBtnTextStyle,
),
)
复制代码
- 5、添加忘记密码文字
// 忘记密码
Text(
'Forgot password?',
style: TextStyle(
fontSize: 18,
color: kTextColor,
),
)
复制代码
- 6、绘制底部社交媒体第三方登录
// 登录方式图标
class LoginTypeIconWidget extends StatelessWidget {
const LoginTypeIconWidget({
Key key,
this.icon,
}) : super(key: key);
final String icon;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Image.asset(
icon,
width: 16,
height: 16,
),
);
}
}
// 横线
class LineWidget extends StatelessWidget {
const LineWidget({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
child: Divider(color: kTextColor),
width: 80,
);
}
}
/// 组合起来
Row(
children: [
Spacer(),
LineWidget(),
LoginTypeIconWidget(icon: 'assets/icons/logo_ins.png'),
LoginTypeIconWidget(icon: 'assets/icons/logo_fb.png'),
LoginTypeIconWidget(icon: 'assets/icons/logo_twitter.png'),
LineWidget(),
Spacer(),
],
)
复制代码
登录页面
绘制头部区域
- 1、绘制背景
Scaffold(
// 设置背景为白色
backgroundColor: Colors.white,
body: Stack(
children: [
Image.asset(
'assets/images/bg_login_header.png'
),
],
),
);
复制代码
- 2、绘制返回按钮
// BackIcon
GestureDetector(
onTap: () {
// 返回
Navigator.pop(context);
},
child: Container(
width: 56,
height: 56,
decoration: BoxDecoration(
// 白色背景
color: Colors.white,
// 设置圆形
shape: BoxShape.circle,
),
// 【这里很重要】设置居中
alignment: Alignment.center,
child: Image.asset(
'assets/icons/icon_back.png',
width: 24,
height: 24,
),
),
)
复制代码
绘制输入区域内容
- 1、绘制文字
// 登录文字内容,可以看上面全局定义的样式
Text(
'Login',
style: kTitleTextStyle,
),
SizedBox(height: 20),
Text(
'Your Email',
style: kBodyTextStyle,
),
复制代码
- 2、绘制输入框
// LoginInput
TextField(
decoration: InputDecoration(
// 缺省文字
hintText: hintText,
// 边框
border: kInputBorder,
focusedBorder: kInputBorder,
enabledBorder: kInputBorder,
// 输入框前面的邮件图标
prefixIcon: Container(
width: kIconBoxSize,
height: kIconBoxSize,
// 【这里很重要,再次强调】不然会拉升
alignment: Alignment.center,
child: Image.asset(
prefixIcon,
width: kIconSize,
height: kIconSize,
),
),
// 设置是否为密码样式
obscureText: obscureText,
// 设置文字样式
style: kBodyTextStyle.copyWith(
fontSize: 18,
),
)
复制代码
- 3、组合样式
// LoginBodyWidget - Column
SizedBox(height: 4),
LoginInput(
hintText: 'Email',
prefixIcon: 'assets/icons/icon_email.png',
),
SizedBox(height: 16),
Text(
'Your Password',
style: kBodyTextStyle,
),
SizedBox(height: 4),
LoginInput(
hintText: 'Password',
prefixIcon: 'assets/icons/icon_pwd.png',
),
复制代码
- 4、绘制登录按钮
// 登录按钮
Row(
children: [
Spacer(),
// 渐变背景组件
GradientBtnWidget(
child: Row(
children: [
SizedBox(width: 34),
// 白色文字
BtnTextWhiteWidget(text: 'Login'),
Spacer(),
// 向右图标
Image.asset(
'assets/icons/icon_arrow_right.png',
width: kIconSize,
height: kIconSize,
),
SizedBox(width: 24),
],
),
width: 160,
onTap: () {
// 点击登录,这里模拟返回了
Navigator.pop(context);
},
),
],
)
复制代码
**
绘制曲线剪裁
可以先看看这个曲线设计的整体路径,找出6个控制点
和 4个坐标点
- p:坐标点
- c:控制点
// 我们是用路径剪裁
ClipPath(
clipper: LoginCliper(),
child: LoginBodyWidget(),
),
// 登录页面剪裁曲线
class LoginCliper extends CustomClipper<Path> {
// 第一个点
Point p1 = Point(0.0, 54.0);
Point c1 = Point(20.0, 25.0);
Point c2 = Point(81.0, -8.0);
// 第二个点
Point p2 = Point(160.0, 20.0);
Point c3 = Point(216.0, 38.0);
Point c4 = Point(280.0, 73.0);
// 第三个点
Point p3 = Point(280.0, 44.0);
Point c5 = Point(280.0, -11.0);
Point c6 = Point(330.0, 8.0);
@override
Path getClip(Size size) {
// 第四个点
Point p4 = Point(size.width, .0);
Path path = Path();
// 移动到起始点
path.moveTo(p1.x, p1.y);
// 第 1 段三阶贝塞尔曲线
path.cubicTo(c1.x, c1.y, c2.x, c2.y, p2.x, p2.y);
// 第 2 段三阶贝塞尔曲线
path.cubicTo(c3.x, c3.y, c4.x, c4.y, p3.x, p3.y);
// 第 3 段三阶贝塞尔曲线
path.cubicTo(c5.x, c5.y, c6.x, c6.y, p4.x, p4.y);
// 右下角
path.lineTo(size.width, size.height);
// 左下角
path.lineTo(0, size.height);
// 闭合
path.close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return oldClipper.hashCode != this.hashCode;
}
}
复制代码
最终效果
源码
base 分支:定义了基础全局样式、图片,自己练习使用
master 分支:视频内容对应的完整代码
视频
建议克隆下来 base 分支代码,按照视频自己动手敲几遍,才可以变成自己的东西
动手实战是快速学习的最好方法
关于我
- 15 年~18 年,使用
Android
原生做智能硬件相关的 App 研发 - 18 年 5 月,一次偶然的机会接触到了
Flutter
,然后开始自学,可以看 weather_flutter 是我练习 Flutter 的入门实战项目(我现在依然觉得他非常适合 Flutter 入门练习使用) - 18 年 8 月,顶着巨大的压力(Flutter 当时还没有 Release 1.0)开始使用 Flutter 开发企业级项目,并且开发维护了十几个 Flutter 插件包(因为当时插件资源非常的匮乏)
- 截止目前主导并参与上线了 4 款企业级
Flutter
App,当前正在负责的一款 App 累计用户120W+
,使用Flutter
得到了极佳的体验
致谢
- 感谢
Elizabeth Arostegui
提供的非常漂亮的设计图,这是她的 Figma 主页 - 如果你也有很棒的设计图,那么可以联系我制作出 App 分享给大家
? 掘金官方活动 ?
- 推荐
本文
到沸点,掘金徽章、100元京东卡、掘金搪瓷杯、五折小册码
,好礼拿不停 - 查看活动详情
本文推荐语示例
- 示例 1
【好文一起看】为这篇文章点赞???,有很多 Flutter Widget 的基础和进阶用法,把这些 Widget 灵活巧妙的组合在一起,完成了超级漂亮的欢迎、登录页面,快和我一起来围观吧 #好文推荐#
复制代码
- 示例 2
【好文一起看】在 Flutter 上贝塞尔曲线居然可以这么简单了?,文章超详细的注释和配图,让我很轻松就理解了优美的贝塞尔曲线剪裁,最重要还附带视频和源码,强烈推荐 ??? #好文推荐#
复制代码
其他推荐语,由你尽情发挥吧
? 欢迎点赞➕关注➕转发,有任何问题随时在下面?评论,我会第一时间回复哦
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END