Flutter 学习日记 – 制作bilibili app day01
首先要做的是登录界面如下,
如果后期有空我再进行后台开发,并逐步完善app功能,现在首先先做前台样式。
打开Android Studio 新建flutter 工程 bilibili, 代码结构如下:,core 负责核心的一些功能,后期的http请求都放在core里,这里我在core目录目前新建了两个文件夹,一个是extension 给我们的屏幕做适配,也就是说类似于微信小程序rpx功能,如果你对这个工具感兴趣,请阅读codedr why Pages 文件夹里存放着我们所有的页面,目前我们首先要完成login 页面。在完成页面之前,我们首先在main.dart 文件创立我们的基本骨架
import 'package:bilibili/pages/theme_style/app_theme.dart';
import 'package:bilibili/core/extension/share.dart';
import 'package:bilibili/pages/login/login.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
SizeFit.initSize(); ## 这里调用的我们extension 里面的初始化屏幕方法,以便后期我们使用里面的方法
return MaterialApp(
home: LoginScreen(), ## 这里暂且这样书写,后期用到路由再将此替换即可
theme: AppThemeStyle.normalTheme ## 我们创建的 theme 主题 存放在一个单独文件夹里,在pages/theme_style 存放我们的主题
);
}
}
打开 pages/theme/ 创建一个新文件 app_theme.dart 在里面我们创立 一个类 AppThemeStyle 里面友静态方法 normalTheme 用来返回我们的主题 这里注意导入
import 'package:bilibili/core/extension/int_extension.dart';
import 'package:flutter/material.dart';
class AppThemeStyle {
static Color normalColor = Colors.white;
static double headline1FontSize = 25.px;
static double headline2FontSize = 30.px;
static double headline3FontSize = 45.px;
static double headline4FontSize = 15.px;
static final normalTheme = ThemeData(
primaryColor: normalColor,
accentColor: normalColor,
canvasColor: Color.fromARGB(255, 230, 230, 230),
textTheme: TextTheme(
headline1: TextStyle(fontSize: headline1FontSize, color: Colors.black87),
headline2: TextStyle(fontSize: headline2FontSize, color: Colors.black),
headline3: TextStyle(fontSize: headline3FontSize, color: Colors.black),
headline4: TextStyle(fontSize: headline4FontSize, color: Colors.black)
)
);
}
在这里我们的主题就创建完毕了,将主题单独抽放到文件,我们后期维护代码也十分方便。 打开pages/login 创立 login.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'login_content.dart';
class LoginScreen extends StatelessWidget {
const LoginScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Center(child: Text("密码登录"))),
body: LoginContent(),
);
}
}
这里注意,我们页面的内容,在新创立的文件 login_content 进行书写,这样代码看起来比较简洁,在pages/login 创立login_content 页面 新建一个StatefulWidget,取名为LoginContent() 这里 我将登录页面划分为两个模块,这是上半模块:
观察页面结构,上半模块和下半模块就是column 两个子组件,也就是说,我们只需要一个column 将两个包裹即可,中间那部分我们用sizebox 设置一个height 即可 在_LoginContentState build 方法里面 return
Column(
children: [
LoginHeader.buildLoginHeader(context, imageNum, dividerColor, focusNode),
SizedBox(height: 50.px,),
LoginBottom.buildLoginBottom(),
],
);
分别创立两个文件用来 管理两个组件, login_header 和 login_bottom
Pages 结构如下 :打开login_header 创立类LoginHeader, 里面存放静态方法buildLoginHeader , 观察 页面,我们发现 这里面也是由一个 column 构成, 最上面图片我们只需要用Image.asset,将图片从本地加载 Image.asset("images/login/login_image0$imageNum.jpg", fit: BoxFit.fitWidth,) 其中这里用了字符串拼接,这是由于页面你输入密码时登录图片会自动切换。这种效果
所以我将imageNum 作为参数进行传递
输入框我们则单独封装一个widget 输入框是明显的Row 左边是 container 包裹一个 Text 右边则是一个textfields, 实现代码如下
Container(
padding: EdgeInsets.zero,
color: Colors.white,
child: Row(
children: [
Container(
width: 150.px,
// color: Colors.red,
padding: EdgeInsets.only(left: 30.px),
child: Text(title, style: Theme.of(context).textTheme.headline2)
),
Expanded(
child: TextField(
focusNode: focusNode, ## focusNode 做为可选参数传递,判断是否点击密码输入框
cursorColor: Colors.pinkAccent, ## 光标颜色
obscureText: obscureText, ## 参数传递,若为true 则隐藏输入内容
cursorHeight: 40.px, ## 光标高度
decoration: InputDecoration(
hintText: hintText, ## 作为参数传递, 帮助提示文字
hintStyle: Theme.of(context).textTheme.headline1!.copyWith(color: dividerColor), ## 这里使用了主题里面的headline 的字体样式
border: InputBorder.none, ),
),
),
],
),
整体代码
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:bilibili/core/extension/int_extension.dart';
class LoginHeader {
static Widget buildLoginHeader(BuildContext context, imageNum, Color dividerColor, FocusNode focusNode) {
return Column(
children: [
Image.asset("images/login/login_image0$imageNum.jpg", fit: BoxFit.fitWidth,),
buildInputFields(context, "账号", "请输入手机号或邮箱", false, dividerColor),
buildDivider(dividerColor),
buildInputFields(context, "密码", "请输入密码", true, dividerColor, focusNode: focusNode),
buildDivider(dividerColor)
],
);
}
static Divider buildDivider(Color dividerColor) => Divider(indent: 30.px, height: 1.px, color: dividerColor,);
static Widget buildInputFields(BuildContext context, String title, String hintText, bool obscureText, Color dividerColor, {FocusNode? focusNode}) {
return Container(
padding: EdgeInsets.zero,
color: Colors.white,
child: Row(
children: [
Container(
width: 150.px,
// color: Colors.red,
padding: EdgeInsets.only(left: 30.px),
child: Text(title, style: Theme.of(context).textTheme.headline2)
),
Expanded(
child: TextField(
focusNode: focusNode,
cursorColor: Colors.pinkAccent,
obscureText: obscureText,
cursorHeight: 40.px,
decoration: InputDecoration(
hintText: hintText,
hintStyle: Theme.of(context).textTheme.headline1!.copyWith(color: dividerColor),
border: InputBorder.none, ),
),
),
],
),
);
}
}
封装完 header 后 我们继续封装 bottom bottom 就是两个按钮 十分简单 这里就放代码了
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:bilibili/core/extension/int_extension.dart';
class LoginBottom {
static final mainColor = Colors.pinkAccent;
static final fixedSize = Size(325.px, 70.px);
static Widget buildLoginBottom() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
OutlinedButton(
onPressed: () {},
child: Text("注册", style: TextStyle(color: mainColor),),
style: OutlinedButton.styleFrom(side: BorderSide(color: mainColor), fixedSize: fixedSize),
),
TextButton(
onPressed: () {},
child: Text("登录", style: TextStyle(color: Colors.white),),
style: TextButton.styleFrom(backgroundColor: mainColor, fixedSize: fixedSize)
)
],
);
}
}
最后我们login_content是这样的
import 'package:bilibili/pages/login/login_bottom.dart';
import 'package:bilibili/pages/login/login_header.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:bilibili/core/extension/int_extension.dart';
class LoginContent extends StatefulWidget {
const LoginContent({Key? key}) : super(key: key);
@override
_LoginContentState createState() => _LoginContentState();
}
class _LoginContentState extends State<LoginContent> {
FocusNode focusNode = FocusNode();
final dividerColor = Color.fromARGB(255, 190, 190, 190);
int imageNum = 1;
@override
void initState() {
// TODO: implement initState
super.initState();
focusNode.addListener(() {
if(focusNode.hasFocus) setState(() {imageNum = 2;});
else setState(() {imageNum = 1;});
}); ## 这里对光标焦点进行监听, 之前我们将focusNode 传递到密码框中,所以这里就可以判断focusNode.hasFocus 属性作为点击密码切换的依据
}
@override
void dispose() {
// TODO: implement dispose
super.dispose();
focusNode.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
LoginHeader.buildLoginHeader(context, imageNum, dividerColor, focusNode),
SizedBox(height: 50.px,),
LoginBottom.buildLoginBottom(),
],
);
}
}
|