Flutter入门指南(五)输入处理及登录界面实战

前面提到基础部件的时候,忘了提输入内容处理部件,这里补上,然后顺带撸个实际的界面吧

1、TextField

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const TextField({
Key key,
this.controller, // 定义一个 `TextEditingController` 实例,用来获取输入框内容等操作
this.focusNode, // 定义一个 `FocusNode` 实例,判断当前输入框是否获取到焦点等操作
this.decoration = const InputDecoration(), // 输入框样式,包括提醒字样,hint 等等
TextInputType keyboardType, // 输入文本类型,例如 数字,email 等等
this.textInputAction, // 键盘确认按钮的事件类型
this.textCapitalization = TextCapitalization.none,
this.style, // 文字样式
this.textAlign = TextAlign.start, // 对齐方式
this.textDirection, // 文字方向
this.autofocus = false, // 是否自动获取焦点
this.obscureText = false, // 文字是否隐藏,多用于密码
this.autocorrect = true,
this.maxLines = 1, //
this.maxLength, // 最大长度
this.maxLengthEnforced = true, // 设置最大长度后,输入内容超出后是否强制不给输入
this.onChanged, // 输入内容发生变化时候的回调
this.onEditingComplete, // 输入完毕的回调
this.onSubmitted, // 提交内容的回调
this.inputFormatters, //
this.enabled, // 是否可输入,false 不可输入
this.cursorWidth = 2.0, // 游标宽度
this.cursorRadius, // 游标半径
this.cursorColor, // 游标颜色
this.keyboardAppearance, // 该属性只在 iOS 设备有效
this.scrollPadding = const EdgeInsets.all(20.0),
this.enableInteractiveSelection,
this.onTap, // 点击事件
})

那么,简单的来个输入框示例吧,然后通过 Text 展示结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
// 可以传入初始值
TextEditingController _editController = TextEditingController();
FocusNode _editNode = FocusNode();
// 保存按钮点击后的输入内容值
String _content = '';
// 监听输入内容变化的内容值
String _spyContent = '';

@override
void initState() {
super.initState();
// 当输入框获取到焦点或者失去焦点的时候回调用
_editNode.addListener(() {
print('edit has focus? => ${_editNode.hasFocus}');
});
}

@override
void dispose() {
// 记得销毁,防止内存溢出
_editController.dispose();
_editNode.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Input Content'),
),
body: Container(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: Column(
children: <Widget>[
TextField(
controller: _editController,
focusNode: _editNode,
decoration: InputDecoration(
icon: Icon(Icons.phone_iphone, color: Theme.of(context).primaryColor),
labelText: '请输入手机号',
helperText: '手机号',
hintText: '手机号...在这儿输入呢'),
keyboardType: TextInputType.number,
// 输入类型为数字类型
textInputAction: TextInputAction.done,
style: TextStyle(color: Colors.redAccent, fontSize: 18.0),
textDirection: TextDirection.ltr,
maxLength: 11, // 最大长度为 11
maxLengthEnforced: true, // 超过长度的不显示
onChanged: (v) { // 输入的内容发生改变会调用
setState(() => _spyContent = v);
},
onSubmitted: (s) { // 点击确定按钮时候会调用
setState(() => _spyContent = _editController.value.text);
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: RaisedButton(
onPressed: () {
// 获取输入的内容
setState(() => _content = _editController.value.text);
// 清理输入内容
_editController.clear();
setState(() => _spyContent = '');
},
child: Text('获取输入内容'))),
// 展示输入的内容,点击按钮会显示
Text(_content.isNotEmpty ? '获取到输入内容: $_content' : '还未获取到任何内容...'),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
// 监听输入内容的变化,会跟随输入的内容进行改变
child: Text('我是文字内容监听:$_spyContent'),
)
],
)),
);
}
}

这边需要提下的是 setState 方法,该方法只有 StatefulWidget 才有,当需要修改某个值的内容的时候,通过该方法进行修改,最后的效果图如下,当输入框文字发生变化的时候,监听的 Text 内容会随之改变,获取内容的 Text 当点击按钮了才发生变化

该部分代码查看 text_field_main.dart 文件

那么如果有个需求,在点击按钮的时候需要对输入的内容的合理性进行检测,当然可以通过 TextEditingController 的结果进行检测,但是还有个更加方便的方法,可以直接使用部件 TextFormField 来实现,不过需要我们在外层加一个 Form 部件,接下来,就要准备通过 TextFormField 来撸一个登录界面,但是这之前,前面有个坑需要先解决下

2、导入自定义的图标

在这之前,涉及到 Icon 部件,都是使用的系统自带的图标,那么如何导入第三方自定义图标呢,马上为你揭晓答案,首先我们需要打开「阿里妈妈」也就是 iconfont,不知道的小伙伴通过链接打开,然后需要注册个账户,也可以直接通过 Github 等三方登录,然后就可以搜索我们需要的图标了,接下来需要撸一个登录,那我们就找一个 用户 和 密码 的图标吧,选择喜欢的图标,然后鼠标放到图标会出现三个按钮,直接点击 购物车 那个按钮,然后就可以通过顶部的 购物车 按钮查看添加的图标,点击下载代码,把资源文件下载到本地。

解压后,需要用到的文件有两个,别的可以忽略

  1. demo_index.html 这边用来查看图标的 unicode
  2. iconfont.ttf 这边就是图标资源文件了

回到项目,创建一个文件夹 fonts ,和 images 同级,将 iconfont.ttf 文件放到该文件夹下,然后打开 pubspec.ymal 文件,注册下导入的资源,可以自己命名 iconfont.ttf 文件名,便于自己发现就行,例如我命名为 third_part_icon.ttf,在注册图片下面继续添加

1
2
3
4
fonts:
- family: ThirdPartIcons
fonts:
- asset: fonts/third_part_icon.ttf

注册完了记得点击 Package get,否则会找不到资源。接着新建个 third_icons.dart文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'package:flutter/material.dart';

class ThirdIcons {
// codePoint 值通过打开 `demo_index.html` 获取
// 会在相应 icon 下带有相应的 code,把 `&#` 替换成 `0`,然后去掉最后的 `;` 即可
// 例如 &#xe672; 对应我们需要的图标就是 0xe672
static const IconData username = ThirdIconData(0xe672);
static const IconData password = ThirdIconData(0xe62f);
}

class ThirdIconData extends IconData {
// fontFamily 就是我们在 `pubspec.yaml` 中注册的 family 值
const ThirdIconData(int codePoint) : super(codePoint, fontFamily: 'ThirdPartIcons');
}

接下来就可以通过该类导入需要的第三方图标了。

3、导入第三方插件

其实 Flutter 中缺少很多功能,需要通过导入第三方插件来实现功能,插件就是 Flutter 和原生交互的桥梁,也就是说,要写 Flutter 的插件,需要写 Android 和 iOS 两端代码才可,否则只有在其中一个端能够实现功能。好在有很多现成的插件已经开源,可以通过 FlutterPackage 搜索到,例如等会我们会需要用到 FlutterToast 这个插件,用来做提醒用,在 FlutterPackage 中搜索到插件后,打开项目中的 pubspec.ymal 文件,在 dependencies 类目下将 fluttertoast 插件引入,如图:

然后点击 Package get 让其导入即可,别的插件也是这样导入。做好准备工作,我们就可以撸一个登录界面了~

4、撸一个登录界面

在开撸之前,我们先看下最终的效果图吧,虽然是比较常用的界面

因为两个界面比较相似,所以这边只贴外层的代码和登录的代码,具体的代码,可以查看源码,已经推到 Github

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
void main() {
runApp(LoginApp());

if (Platform.isAndroid) {
var style = SystemUiOverlayStyle(statusBarColor: Colors.transparent);
SystemChrome.setSystemUIOverlayStyle(style);
}
}

/// 外层界面,包裹登录界面和注册界面,使用的都是前面讲过的,忘记可以查看之前的章节
class LoginApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Login Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.lightBlue),
home: LoginHomePage(),
);
}
}

class LoginHomePage extends StatefulWidget {
@override
_LoginHomePageState createState() => _LoginHomePageState();
}

class _LoginHomePageState extends State<LoginHomePage> with SingleTickerProviderStateMixin {
TabController _tabController;
List<String> _pageIndicators = ['登录', '注册'];
List<Widget> _pages = [];
int _position = 0;

@override
void initState() {
super.initState();
_tabController = TabController(length: _pageIndicators.length, vsync: this);
// 将登录界面和注册界面添加到列表,用于放到 IndexStack 的 children 属性
_pages..add(LoginPage())..add(RegisterPage());

_tabController.addListener(() {
// 当 tab 切换的时候,联动 IndexStack 的 child 页面也进行修改,通过 setState 来修改值
if (_tabController.indexIsChanging) setState(() => _position = _tabController.index);
});
}

@override
void dispose() {
super.dispose();
}

@override
Widget build(BuildContext context) {
// 先忽略...
return Theme(
data: ThemeData(primarySwatch: Colors.pink, iconTheme: IconThemeData(color: Colors.pink)),
child: Scaffold(
body: Container(
padding: const EdgeInsets.all(20.0),
alignment: Alignment.center,
decoration:
BoxDecoration(image: DecorationImage(image: AssetImage('images/login_bg.png'), fit: BoxFit.cover)),

// 先忽略...下面会讲,主要是解决软键盘弹出的时候,界面内容会溢出的问题
child: SingleChildScrollView(
child: SafeArea(
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
// 顶部页面切换指示器,代码可以参考 `app_bar_main.dart` 文件
TabBar(
indicatorSize: TabBarIndicatorSize.label,
controller: _tabController,
indicatorWeight: 4.0,
indicatorColor: Colors.white,
// 返回 tab 列表
tabs: _pageIndicators
.map((v) => Text(v, style: TextStyle(color: Colors.white, fontSize: 24.0)))
.toList()),
Padding(
padding: const EdgeInsets.only(top: 30.0),
child: SizedBox(
// 切换界面列表
child: IndexedStack(children: _pages, index: _position),
// 指定高度
height: MediaQuery.of(context).size.height / 2))
])),
),
),
));
}
}

/// 登录界面
class LoginPage extends StatefulWidget {
@override
_LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
// 用于后面判断表单内容是否有效
GlobalKey<FormState> _formKey = GlobalKey();
// 用于获取输入框的内容
TextEditingController _usernameController = TextEditingController();
TextEditingController _passwordController = TextEditingController();

@override
void initState() {
super.initState();
}

@override
void dispose() {
// 防止内存溢出,记得销毁..销毁..销毁
_usernameController.dispose();
_passwordController.dispose();
super.dispose();
}

_login() {
// 取消焦点
FocusScope.of(context).requestFocus(FocusNode());

// 判断表单是否有效
if (_formKey.currentState.validate()) {
// 获取输入框内容
var username = _usernameController.value.text;
var password = _passwordController.value.text;

// 判断登录条件
if (username == 'kuky' && password == '123456')
// 引入的三方插件方法,`Flutter` 没有自带的 `Taost`
Fluttertoast.showToast(msg: '登录成功');
else
Fluttertoast.showToast(msg: '登录失败');
}
}

@override
Widget build(BuildContext context) {
return Form(
// 将 key 设置给表单,用于判断表单是否有效
key: _formKey,
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
// 表单输入框,参数同 TextField 基本类似
child: TextFormField(
controller: _usernameController,
style: TextStyle(color: Colors.white, fontSize: 16.0),
decoration: InputDecoration(
icon: Icon(ThirdIcons.username, size: 24.0, color: Colors.white),
labelText: '请输入用户名',
labelStyle: TextStyle(color: Colors.white),
helperStyle: TextStyle(color: Colors.white)),
// 有效条件(为空不通过,返回提示语,通过返回 null)
validator: (value) => value.trim().isEmpty ? '用户名不能为空' : null,
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: TextFormField(
obscureText: true,
controller: _passwordController,
style: TextStyle(color: Colors.white, fontSize: 16.0),
decoration: InputDecoration(
icon: Icon(ThirdIcons.password, size: 24.0, color: Colors.white),
labelText: '请输入密码',
labelStyle: TextStyle(color: Colors.white),
helperStyle: TextStyle(color: Colors.white)),
validator: (value) => value.trim().length < 6 ? '密码长度不能小于6位' : null,
),
),
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: SizedBox(
// 主要用于使 RaisedButton 和上层容器同宽
width: MediaQuery.of(context).size.width,
child: RaisedButton(
color: Colors.pink,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))),
onPressed: _login,
child: Text(
'登录',
style: TextStyle(color: Colors.white, fontSize: 20.0),
)),
),
)
],
));
}
}

撸完界面后,可以试下登录效果,如果输入框的内容,和 TextFormField 的 validator的条件不符合,则会显示错误文字的提示

如果按照条件用户名为 kuky 密码为 123456 (条件可以根据自己进行修改)则会显示登录成功的逻辑

以上代码查看 login_home_page.dart 文件

注册界面的逻辑和登录界面的逻辑几乎一样,算是第一次实战了,望小伙伴能够好好的写一遍

代码地址:

https://github.com/kukyxs/flutter_arts_demos_app

坚持原创技术分享,您的支持将鼓励我继续创作!