发布时间:2020年8月8日-6分钟阅读
这是官方的,
Flutter 1.20来了
。在所有的好东西和改进中,移动自动填充在标语中得到了强调,甚至有一个gif展示了用户体验是多么的光滑。遗憾的是,目前还没有太多例子、教程或窍门,因此才有了这篇文章的诞生。
什么/为什么要自动填充?
自动填报在行动,功不可没的Flutter团队
来自
Android开发者指南
:
填写表格是一项耗时且容易出错的任务。用户很容易对需要这种操作的应用程序感到沮丧。自动填写框架通过提供以下好处来改善用户体验。
减少填写字段的时间
。自动填写节省了用户重新输入信息的时间。
尽量减少用户输入错误
。输入容易出错,尤其是在移动设备上。最大限度地减少输入信息的需求,也最大限度地减少输入错误。
开始使用没有自动填写的表单
我们先做一个简单的表格,里面有地址、电话,还有一个以后不要自动填写的字段。
代码可以在
这个repo
上找到。如果你关注这些字段中的任何一个,你应该看到光标闪烁,但没有自动填充弹出。
安装Android自动填充框架示例
你可能已经有了一些自动填写服务(如LastPass、Bitwarden),也可能没有。无论哪种情况,建议你在开发过程中不要使用它们,而是使用服务的参考实现。按照本
CodeLabs 第 1 步
的指示,在设备上设置 Android 自动填充框架示例。
样品安装好后,选择调试
Debug Autofill Service
。现在,进入任何一个使用OEM控件(如使用Android SDK、React Native或Ionic编写的应用,但
不能
使用Flutter也不能使用Unity),关注任何一个文本字段,你会发现自动填写服务出现了不理想的情况。一般来说,我们希望我们的应用只在某些字段可以自动填写,比如
姓名
、
地址
、
信用卡
、
密码
等,而在其他字段,比如
备注
、
搜索
等字段,则不能自动填写。
在Flutter应用程序中从未发生过意外的自动填充弹出。
自动填充服务以某种方式意外出现的原因是,如果开发者没有明确地编写代码,那么利用OEM控件的Android应用程序可以从其控件ID中推断出自动填充语义。这种推断在大多数时候是有效的,但并非总是如此。
Android XML可以从widget ID推断自动填充语义。截图来自
《确保应用程序与Android自动填充兼容的快速方法》,2:11
。
与Android XML不同,您的Flutter应用程序不会自动具有自动填充功能,即使您针对Flutter 1.20重新编译您的代码库。事实上,根本没有相当于
android:importantForAutofill
的标签。原因是,虽然在Android中,一个元素可以有
android:id
标签,但在Flutter中却不是这样。
在Flutter中没有对应的android:importantForAutofill。截图来自
《确保应用兼容Android自动填充的快速方法》,4:03
。
我们Flutter应用中的第一个自动填充弹出窗口
第一步很简单,所有由
EditableText
或其祖先组成的widget,如
TextFormField
或
TextField
都有我们可以设置的
autofillHints
参数。
@@ -35,12 +35,14 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
padding: EdgeInsets.symmetric(horizontal: 16),
children: [
TextFormField(
+ autofillHints: [AutofillHints.streetAddressLine1],
decoration: const InputDecoration(
labelText: 'Shipping address 1',
hintText: 'Number and street',
TextFormField(
+ autofillHints: [AutofillHints.streetAddressLine2],
decoration: const InputDecoration(
labelText: 'Shipping address 2',
hintText: '(optional) APT number, c/o',
尽管这个参数接受一个字符串列表(确切地说,是可迭代的),但建议你不要传入一个任意的字符串,而是使用AutofillHints
的静态值,这是一个常用的自动填充提示集合。这将帮助你避免错别字,也能保证跨平台的兼容性。
自动填写提示(AutofillHints)
,集合了常用的自动填写提示,有助于避免错别字,保证跨平台的兼容性。
现在,回到我们的应用中,关注 "送货地址1",我们会看到自动填写的弹窗出现。让我们点开后,对 "送货地址2 "进行同样的操作。
在我们的Flutter应用中首次弹出了自动填充的对话框,Yay!
AutofillGroup
到目前为止,要填写这2行地址,用户要点两次自动填写的弹窗。这比理想中的要费力,也比较容易出错。比如说,如果用户,有多个地址。
比如: 华尔街11号
纽约州纽约市10005
1600 Pennsylvania Avenue NW
Washington, DC 20500
有可能是用户误选了不同字段的不同条目,导致数据损坏。
华尔街11号
Washington, DC 20500
在这种情况下,可以使用AutofillGroup
,这样用户只需点击一次,服务就会自动填充所有相关字段,这些字段是widget树中最近的AutofillGroup
的子代。
@@ -33,6 +33,9 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
@override
Widget build(BuildContext context) => ListView(
padding: EdgeInsets.symmetric(horizontal: 16),
+ children: [
+ AutofillGroup(
+ child: Column(
children: [
TextFormField(
autofillHints: [AutofillHints.streetAddressLine1],
@@ -48,6 +51,9 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
hintText: '(optional) APT number, c/o',
+ ],
+ ),
+ ),
CheckboxListTile(
contentPadding: EdgeInsets.zero,
title: const Text("Billing address same as shipping address"),
将自动填充功能添加到我们的应用程序的其他地方
这一步很直接,和之前的步骤类似。
@@ -65,15 +65,18 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
if (!isSameAddress)
- Column(
+ AutofillGroup(
+ child: Column(
children: [
TextFormField(
+ autofillHints: [AutofillHints.streetAddressLine1],
decoration: const InputDecoration(
labelText: 'Billing address 1',
hintText: 'Number and street',
TextFormField(
+ autofillHints: [AutofillHints.streetAddressLine2],
decoration: const InputDecoration(
labelText: 'Billing address 2',
hintText: '(optional) APT number, c/o',
@@ -81,17 +84,27 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
+ ),
+ AutofillGroup(
+ child: Column(
+ children: [
TextFormField(
+ autofillHints: [AutofillHints.creditCardNumber],
decoration: const InputDecoration(
labelText: 'Credit Card #',
TextFormField(
+ autofillHints: [AutofillHints.creditCardSecurityCode],
decoration: const InputDecoration(
labelText: 'CCV',
+ ],
+ ),
+ ),
TextFormField(
+ autofillHints: [AutofillHints.telephoneNumber],
decoration: const InputDecoration(
labelText: 'Contact Phone Number',
嵌套的AutofillGroup
在这一点上,我们可以很满意我们的应用程序。但是,电话号码通常是和送货地址一起填写的。换句话说,电话号码字段应该与地址行1和地址行2在同一个AutofillGroup
中。但是,在我们的widget中,这就不太容易了,因为,在中间,我们还有账单地址字段。
这时,我正准备用GlobalKey
来获取AutofillGroupState
,但事实证明,还有一个更简单的方法。AutofillGroup
可以嵌套,子孙字段只会在widget树中最近的AutofillGroup
widget下进行分组。
@@ -31,11 +31,11 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
// has been removed
@override
- Widget build(BuildContext context) => ListView(
+ Widget build(BuildContext context) => AutofillGroup(
+ child: ListView(
padding: EdgeInsets.symmetric(horizontal: 16),
children: [
- AutofillGroup(
- child: Column(
+ Column(
children: [
TextFormField(
autofillHints: [AutofillHints.streetAddressLine1],
@@ -53,7 +53,6 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
- ),
CheckboxListTile(
contentPadding: EdgeInsets.zero,
title: const Text("Billing address same as shipping address"),
@@ -116,5 +115,6 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
+ ),
我们可以看到信用卡字段仍然被正确地分组,即使它被嵌套在另一个AutofillGroup
中,而这个的目的是用来填写送货地址和电话号码。
一些需要注意的问题
在演讲《快速确保应用兼容Android自动填充的方法》中,有一个警告,Android SDK中的一个反模式,视图层次结构是在onStart()
生命周期上创建的,会使自动填充服务循环地要求用户认证,从而破坏自动填充的用户体验。幸运的是,我们不能在Flutter中出现这种情况,因为Flutter是(半)声明式的,不可能在生命周期事件上强制创建/绘制视图。
然而,如果你有条件地构建widgets,要小心上面第4步中嵌套的AutofillGroup
,在我们的例子中,if (!isSameAddress)
。试着自动填写信用卡字段,然后取消勾选,我们可以看到信用卡字段的值会被移动到账单地址字段。
解决办法是使用UniqueKey。解释值得一整篇文章,[Flutter]Keys!它们有什么用?,但简短的回答是,由于Flutter框架为重建做树状区分的方式,如果没有额外的key
的帮助,它不能可靠地区分相同类型的widgets(在这种情况下,TextFormField
)。
@@ -27,6 +27,9 @@ class MyStatefulWidget extends StatefulWidget {
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
var isSameAddress = true;
+ final billingAddress1Key = UniqueKey();
+ final billingAddress2Key = UniqueKey();
// For the sake of simplicity, Form widget, _formkey, and textController
// has been removed
@@ -68,6 +71,7 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
child: Column(
children: [
TextFormField(
+ key: billingAddress1Key,
autofillHints: [AutofillHints.streetAddressLine1],
decoration: const InputDecoration(
labelText: 'Billing address 1',
@@ -75,6 +79,7 @@ class _MyStatefulWidgetState extends State<MyStatefulWidget> {
TextFormField(
+ key: billingAddress2Key,
autofillHints: [AutofillHints.streetAddressLine2],
decoration: const InputDecoration(
labelText: 'Billing address 2',
正如发布公告中提到的那样,自动填充是一段时间以来要求最多的Flutter功能之一,我们终于得到了它,Android和iOS都有(web在路上,我们还没有桌面上的自动填充生态系统)。事实证明,与Android不同,开发者必须明确地修改他们的代码来支持自动填充。此外,Flutter中的字段和自动填充的问题与Android中的有很大不同。
本文中使用的示例代码和差异在:github.com/truongsinh/…
www.twitter.com/FlutterComm