原文连接数组
| 做者:Guillaume Belouin微信
| 连接:blog.usejournal.com/flutter-goo…app
直入主题吧。在本教程中,咱们将使用 Flutter 来开发适用于 Android 和 iOS 的谷歌翻译应用程序。下面是程序的基本界面。less
要建立项目,咱们必须运行 Android Studio 并单击 Start a new Flutter project
,而后选择 Flutt2 er application
。咱们在表单中填写项目名称,flutter SDK
的路径,项目位置和描述,以下所示:ide
建立项目后,咱们首先清理 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
下显示应用程序内容的地方。flex
个人代码以这种方式组织,包含 components、screens、models 和 services。这只是一种组织方式,还有其它的方式,就看本身是怎么考虑。test 文件夹也以相同方式组织。ui
咱们将建立第一个组件,来显示咱们输入的文本的语言,以及要翻译成的语言。this
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
组件,该组件定义了两个变量来用于表示选择的语言。google
@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(),
),
);
}
}
复制代码
咱们如今须要建立组件来选择咱们想要翻译的文本。在 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/
复制代码
我在 pubspec.yaml
文件中添加了 - assets /
,并在个人图像的根文件夹中建立了一个 assets
文件夹。完成此操做后,可能须要从新运行该应用程序。最后,你能够像我以前那样调用你的图像,例如:AssetImage("assets/pen.png")
。
若是您想了解有关 assert 的更多信息以及咱们如何使用它们,请您阅读 Flutter 文档。
看当作果
第二个组件完成后,应用程序的 UI 如今几乎完成了。
咱们须要再建立一个新组件来显示最近的翻译列表。仍然使用相同的原理,咱们将开发一个组合行和列的 widget。List
能够像 Column 同样显示咱们的项目,不过更加自动化,而不是添加 n 次组件。此外,能够在 List 上滑动。
首先,我定义了一个 Translate
类,它由咱们在列表中显示项目所需的元素组成。
class Translate {
String text;
String translatedText;
bool isStarred;
Translate(String text, String translated, bool isStarred) {
this.text = text;
this.translatedText = translated;
this.isStarred = isStarred;
}
}
复制代码
而后,咱们能够建立一个名为 ListTranslate
的新组件。 咱们在 itemCount
中定义 ListView
将显示的行数。在 itemBuilder
中,咱们使用数组列表显示行。
import 'package:flutter/material.dart';
import '../models/translate.dart';
class ListTranslate extends StatefulWidget {
ListTranslate({Key key}) : super(key: key);
@override
_ListTranslateState createState() => _ListTranslateState();
}
class _ListTranslateState extends State<ListTranslate> {
List<Translate> _items = [];
Widget _displayCard(int index) {
return Card(
child: Container(
),
);
}
@override
Widget build(BuildContext context) {
return Expanded(
child: ListView.builder(
itemCount: _items.length,
itemBuilder: (BuildContext ctxt, int index) {
return _displayCard(index);
},
),
);
}
}
复制代码
下一步是建立列表的项目,就像咱们以前制做的草图同样。为此,咱们将使用 Row 和 Column,就像咱们为最后的组件所作的那样。不过,咱们将添加一些样式,如边框半径,边距和填充,让它更好看。
Widget _displayCard(int index) {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(0.0)),
),
margin: EdgeInsets.only(left: 8.0, right: 8.0, top: 0.5),
child: Container(
height: 80.0,
padding: EdgeInsets.only(left: 16.0, top: 16.0, bottom: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Text(
_items[index].text,
style: TextStyle(
fontWeight: FontWeight.w600,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
_items[index].translatedText,
style: TextStyle(
fontWeight: FontWeight.w400,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
IconButton(
onPressed: () {},
icon: Icon(
Icons.star_border,
size: 23.0,
color: Colors.grey[700],
),
),
],
),
),
);
}
复制代码
这将显示列表中的每一行,以展现搜索的信息。Flexible
小部件将其高度和宽度扩展到最大值。因为咱们在 Text
中添加了 maxLine
和 overflow
,若是咱们的文本太长,则会被截断。
IconButton(
onPressed: () {},
icon: Icon(
_items[index].isStarred ? Icons.star : Icons.star_border,
size: 23.0,
color: _items[index].isStarred ? Colors.blue[600] : Colors.grey[700],
),
),
复制代码
若是行的元素已加星标,则咱们想要更改颜色和图标。咱们使用三元运算符直接从列表信息中更改它。
List<Translate> _items = [
Translate(
"yellowish",
"jaunâtre",
true,
),
...,
];
复制代码
咱们暂时使用一些信息填充列表项,以查看它的外观。如今,设计终于完成了,咱们能够观察咱们在整个教程期间所作的工做。
这篇教程展现了使用 Flutter 为 Android 和 iOS 建立移动应用程序有多简单快捷。咱们还发现一些部分看起来像 Web 开发中 flexbox
。即便这是一个移动 App 的开发,Web 开发人员也能够快速地了解所作的事情。咱们目前只作了一个没有任何功能的基本应用程序,可是编写下一个部分并不困难!
欢迎关注咱们的公众号:iOS-Tips,也欢迎加入咱们的群组讨论问题。能够加微信 coldlight_hh
/wsy9871
进入咱们的 iOS
/flutter
微信群。