使用 Flutter 開發 Google Translate 程式

知識小集發表於2019-02-22

原文連結

| 作者:Guillaume Belouin

| 連結:blog.usejournal.com/flutter-goo…

直入主題吧。在本教程中,我們將使用 Flutter 來開發適用於 Android 和 iOS 的谷歌翻譯應用程式。下面是程式的基本介面。

使用 Flutter 開發 Google Translate 程式

建立工程

要建立專案,我們必須執行 Android Studio 並單擊 Start a new Flutter project,然後選擇 Flutt2 er application。我們在表單中填寫專案名稱,flutter SDK 的路徑,專案位置和描述,如下所示:

使用 Flutter 開發 Google Translate 程式

清空程式碼

建立專案後,我們首先清理 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 和一個 bodybody 部分是我們在 AppBar 下顯示應用程式內容的地方。

組織程式碼

使用 Flutter 開發 Google Translate 程式

我的程式碼以這種方式組織,包含 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 開發 Google Translate 程式

在 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 元素來構建包含白色背景顏色的 TextInkWell 元素需要子樹中的 Material 來顯示 splash 效果。onTap 事件暫時不使用,我們稍後會在點選 Text 時更改語言。我們將 Text 顯示在 Center 中,因為 Expanded 將佔用所有空間,而我們希望將文字居中。最後,我們新增了變數 firstLanguagesecondLanguage 來顯示語言。

最後一步是建立一個圖示按鈕,以像谷歌翻譯應用程式中那樣切換語言。這很簡單,我們將建立一個帶有白色背景的 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(),
      ),
    );
  }
}
複製程式碼

使用 Flutter 開發 Google Translate 程式

翻譯操作的 UI

我們現在需要建立元件來選擇我們想要翻譯的文字。在 Google 應用程式中,我們必須輸入文字或使用其它的方式,例如拍照。我們遵循與 iOS 應用程式相同的設計。因此,我們將在本教程的下一部分中建立一個可點選的 widget。

使用 Flutter 開發 Google Translate 程式

我們將使用 ColumnRow 建立具有此結構的元件以顯示我們的 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>[...],
                   ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
複製程式碼

我們還為 ColumnRow 定義了主軸和交叉軸的對齊方式。在這裡,我們選擇了 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 的原因。我們將建立一個函式來顯示 IconDataAssetImage 中的圖示。

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();
  }
}
複製程式碼

此函式檢查 iconimageIcon 變數是否為 null,當其中一個變數不為 null 時,我們將使用右側元件顯示影象。事實上,我們通過 Icon 元件顯示 IconDataAssetImage 基於 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 現在幾乎完成了。

使用 Flutter 開發 Google Translate 程式

顯示最近翻譯的列表

我們需要再建立一個新元件來顯示最近的翻譯列表。仍然使用相同的原理,我們將開發一個組合行和列的 widget。List 可以像 Column 一樣顯示我們的專案,不過更加自動化,而不是新增 n 次元件。此外,可以在 List 上滑動。

使用 Flutter 開發 Google Translate 程式

首先,我定義了一個 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 中新增了 maxLineoverflow,如果我們的文字太長,則會被截斷。

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 開發 Google Translate 程式

結論

這篇教程展示了使用 Flutter 為 Android 和 iOS 建立移動應用程式有多簡單快捷。我們還發現一些部分看起來像 Web 開發中 flexbox。即使這是一個移動 App 的開發,Web 開發人員也可以快速地瞭解所做的事情。我們目前只做了一個沒有任何功能的基本應用程式,但是編寫下一個部分並不困難!

關注我們

歡迎關注我們的公眾號:iOS-Tips,也歡迎加入我們的群組討論問題。可以加微信 coldlight_hh/wsy9871 進入我們的 iOS/flutter 微信群。

使用 Flutter 開發 Google Translate 程式

相關文章