| 作者:Guillaume Belouin
| 链接: blog.usejournal.com/flutter-goo…
直入主题吧。在本教程中,我们将使用 Flutter 来开发适用于 Android 和 iOS 的谷歌翻译应用程序。下面是程序的基本界面。
创建工程
要创建项目,我们必须运行 Android Studio 并单击
Start a new Flutter project
,然后选择
Flutt2 er application
。我们在表单中填写项目名称,
flutter SDK
的路径,项目位置和描述,如下所示:
清空代码
创建项目后,我们首先清理
main.dart
文件中生成的代码。原本有一些代码,但我们希望尽可能简单。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Google Translate',
theme: ThemeData(
primarySwatch: Colors.blue,
primaryColor: Colors.blue[600],
),
home: Scaffold(
appBar: AppBar(
title: Text('Google Translate'),
elevation: 0.0,
),
body: Center(
child: Text("We are going to translate everything !"),
),
),
);
}
}
复制代码
这段代码更干净且更容易理解。
MaterialApp
类为我们的应用程序定义了设计外观。然后在
ThemeData
类中添加蓝色主题,以便更接近真实的
Google Translate
应用程序。
Scaffold
创建应用程序的全局结构,它包含一个
AppBar
和一个
body
。
body
部分是我们在
AppBar
下显示应用程序内容的地方。
组织代码
我的代码以这种方式组织,包含 components、screens、models 和 services。这只是一种组织方式,还有其它的方式,就看自己是怎么考虑。test 文件夹也以相同方式组织。
创建第一个组件
我们将创建第一个组件,来显示我们输入的文本的语言,以及要翻译成的语言。
import 'package:flutter/material.dart';
class ChooseLanguage extends StatefulWidget {
ChooseLanguage({Key key}) : super(key: key);
@override
_ChooseLanguageState createState() => _ChooseLanguageState();
}
class _ChooseLanguageState extends State<ChooseLanguage> {
String _firstLanguage = "English";
String _secondLanguage = "French";
@override
Widget build(BuildContext context) {
return Container();
}
}
复制代码
这段代码创建了一个
ChooseLanguage
组件,该组件定义了两个变量来用于表示选择的语言。
@override
Widget build(BuildContext context) {
return Container(
height: 55.0,
decoration: BoxDecoration(
color: Colors.white,
border: Border(
bottom: BorderSide(
width: 0.5,
color: Colors.grey[500],
),
),
),
);
}
复制代码
我们为容器定义特定高度和装饰以对组件进行样式设置。在
BoxDecoration
中,我们定义背景颜色和容器边框,以在组件底部显示一条分隔线。
接着我们将添加一个
Row
元素,它将帮助我们在一行上显示我们的
widgets
。然后,我们可以确定如何对齐此行中的元素。实际上,row 有两个轴,主轴与 row 方向相同,横轴与其方向交叉。操纵这两个轴,我们可以很容易地以我们想要的方式显示我们的组件。以下模式显示了
widget
如何使用不同属性进行显示。
在 Flutter 中,可以很容易重建这个显示,我们所需要的只是一个
Row
元素,并定义主轴和横轴对齐方式。
Expanded
元素将被添加到
Row
的子
widget
中。
@override
Widget build(BuildContext context) {
return Container(
...
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
...
],
),
);
}
复制代码
现在是时候在我们的
Row
中添加
widget
了。我们需要在
InkWell
组件内简单地创建一个
Text
组件。
InkWell
组件将在文本周围创建一个可单击的区域,并显示
splash
效果。
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: Material(
color: Colors.white,
child: InkWell(
onTap: () {},
child: Center(
child: Text(
this._firstLanguage,
style: TextStyle(
color: Colors.blue[600],
fontSize: 15.0,
),
),
),
),
),
),
...
Expanded(
child: Material(
color: Colors.white,
child: InkWell(
onTap: () {},
child: Center(
child: Text(
this._secondLanguage,
style: TextStyle(
color: Colors.blue[600],
fontSize: 15.0,
),
),
),
),
),
),
],
),
复制代码
我们使用
Expanded
元素来构建包含白色背景颜色的
Text
。
InkWell
元素需要子树中的
Material
来显示 splash 效果。
onTap
事件暂时不使用,我们稍后会在点击
Text
时更改语言。我们将
Text
显示在
Center
中,因为
Expanded
将占用所有空间,而我们希望将文本居中。最后,我们添加了变量
firstLanguage
和
secondLanguage
来显示语言。
最后一步是创建一个图标按钮,以像谷歌翻译应用程序中那样切换语言。这很简单,我们将创建一个带有白色背景的
Material
,来包含我们的
IconButton
。我们在 Flutter 库中已经存在的图标列表中选择我们的图标。我选择使用
Icons.compare_arrows
,它与实际应用程序中的不完全相同。
<Widget>[
Expanded(
...
),
Material(
color: Colors.white,
child: IconButton(
icon: Icon(
Icons.compare_arrows,
color: Colors.grey[700],
),
onPressed: () {},
),
),
Expanded(
...
),
]
复制代码
现在我们的组件终于完成了,我们需要将它添加到页面中。然后我们应该能看到下面的效果。
import 'package:flutter/material.dart';
import '../components/choose-language.dart';
class HomePage extends StatefulWidget {
HomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
elevation: 0.0,
),
body: Column(
children: <Widget>[
ChooseLanguage(),
),
);
}
}
复制代码
翻译操作的 UI
我们现在需要创建组件来选择我们想要翻译的文本。在 Google 应用程序中,我们必须输入文本或使用其它的方式,例如拍照。我们遵循与 iOS 应用程序相同的设计。因此,我们将在本教程的下一部分中创建一个可点击的 widget。
我们将使用
Column
和
Row
创建具有此结构的组件以显示我们的 UI。
Column
的工作方式与
Row
的工作方式相同,唯一的区别是它显示元素的方向。我们现在将在一个新组件中编写我们想要实现的结构。
import 'package:flutter/material.dart';
class TranslateText extends StatefulWidget {
TranslateText({Key key}) : super(key: key);
@override
_TranslateTextState createState() => _TranslateTextState();
}
class _TranslateTextState extends State<TranslateText> {
@override
Widget build(BuildContext context) {
return Card(
color: Colors.white,
margin: EdgeInsets.all(0.0),
elevation: 2.0,
child: Container(
height: 150.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(...),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Material(
color: Colors.white,
child: Column(
children: <Widget>[...],
),
),
],
),
],
),
),
);
}
}
复制代码
我们还为
Column
和
Row
定义了主轴和交叉轴的对齐方式。在这里,我们选择了
MainAxisAlignment.spaceBetween
,因为我们希望在可点击的图标之间留出一些空间。
由于基本结构已完成,现在可以专注于输入部分。输入部分不是我们将在其中输入文本的 widget。在实际的应用中,当我们点击这部分时,会出现一个输入框来输入文本。
class _TranslateTextState extends State<TranslateText> {
@override
Widget build(BuildContext context) {
return Card(
...,
child: Container(
height: 150.0,
child: Column(
...,
children: <Widget>[
Expanded(
child: InkWell(
onTap: () {},
child: Container(
width: double.infinity,
padding: EdgeInsets.only(
left: 16.0,
right: 16.0,
top: 16.0
),
child: Text(
"Enter text",
style: TextStyle(
color: Colors.grey[700],
),
),
),
),
),
Row(...),
],
),
),
);
}
}
复制代码
这段代码与我们创建的第一个组件非常相似。是一个简单的
InkWell
内部包含一个
Text
。我们将保留
onTap
函数,就像我们在创建的第一个组件中所做的那样。我们现在要在
Row
中创建可点击的图标。
我们将使用相同的代码来显示带有描述性文本的 4 个图标。我们可以在 Row 中编写相同 4 份代码,但是如果我们这样做,就会有很多重复代码。
使用此方法,在更改代码必须重复修改很多处。最好的解决方案是创建另一个组件,我们将使用不同的参数调用四次。
import 'package:flutter/material.dart';
class ActionButton extends StatefulWidget {
ActionButton({Key key, this.icon, this.text, this.imageIcon}) : super(key: key);
final IconData icon;
final AssetImage imageIcon;
final String text;
@override
_ActionButtonState createState() => _ActionButtonState();
}
class _ActionButtonState extends State<ActionButton> {
@override
Widget build(BuildContext context) {
return Material(
color: Colors.white,
child: FlatButton(
padding: EdgeInsets.only(
left: 8.0,
right: 8.0,
top: 2.0,
bottom: 2.0,
),
onPressed: () {},
child: Column(
children: <Widget>[...],
),
),
);
}
}
复制代码
创建可点击图标
我们使用了新的组件
ActionButton
,与其他组件不同,我们添加了参数 icon,imageIcon 和 text。 我使用的一些图标不在谷歌的库中,因此我创建了自己的图标。
这就是我区分图标和
ImageIcon
的原因。我们将创建一个函数来显示
IconData
或
AssetImage
中的图标。
Widget _displayIcon() {
if (this.widget.icon != null) {
return Icon(
this.widget.icon,
size: 23.0,
color: Colors.blue[800],
);
} else if (this.widget.imageIcon != null) {
return ImageIcon(
this.widget.imageIcon,
size: 23.0,
color: Colors.blue[800],
);
} else {
return Container();
}
}
复制代码
此函数检查
icon
或
imageIcon
变量是否为 null,当其中一个变量不为 null 时,我们将使用右侧组件显示图像。事实上,我们通过
Icon
组件显示
IconData
。
AssetImage
基于
ImageIcon
组件。最后如果两者都为 null,则显示一个空容器。
@override
Widget build(BuildContext context) {
return Material(
color: Colors.white,
child: FlatButton(
...,
child: Column(
children: <Widget>[
_displayIcon(),
Text(
this.widget.text,
style: TextStyle(fontSize: 12),
),
],
),
),
);
}
复制代码
我们在
Column
中添加了
_displayIcon
函数。它将使用我们要传递给组件的图标或图像图标中显示正确的 widget。我们现在可以在之前创建的
TranslateText
中调用我们的组件了。
import 'package:flutter/material.dart';
import 'ActionButton.dart';
class _TranslateTextState extends State<TranslateText> {
@override
Widget build(BuildContext context) {
return Card(
...,
child: Container(
height: 150.0,
child: Column(
...,
children: <Widget>[
Expanded(
...,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
ActionButton(
icon: Icons.camera_alt,
text: "Camera",
),
ActionButton(
imageIcon: AssetImage("assets/pen.png"),
text: "Handwriting",
),
ActionButton(
imageIcon: AssetImage("assets/conversation.png"),
text: "Conversation",
),
ActionButton(
icon: Icons.keyboard_voice,
text: "Voice",
),
],
),
],
),
),
);
}
}
复制代码
我们导入组件,使用不同的参数调用 ActionButton 4 次,以便为每个
ActionButton
提供唯一的渲染。
添加图片资源(可选)
我在我们刚制作的一些按钮中添加了自己的图像,但我们需要更改根文件夹中的文件
pubspec.yaml
来引入这些图像。
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
assets:
- assets/