| 作者: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/
複製程式碼
我在 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
微信群。