使用 Flutter 開發 Google Translate 程序

原文連接數組

| 做者: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 和一個 bodybody 部分是咱們在 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 元素來構建包含白色背景顏色的 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(),
      ),
    );
  }
}
複製代碼

翻譯操做的 UI

咱們如今須要建立組件來選擇咱們想要翻譯的文本。在 Google 應用程序中,咱們必須輸入文本或使用其它的方式,例如拍照。咱們遵循與 iOS 應用程序相同的設計。所以,咱們將在本教程的下一部分中建立一個可點擊的 widget。

咱們將使用 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 如今幾乎完成了。

顯示最近翻譯的列表

咱們須要再建立一個新組件來顯示最近的翻譯列表。仍然使用相同的原理,咱們將開發一個組合行和列的 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 中添加了 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 爲 Android 和 iOS 建立移動應用程序有多簡單快捷。咱們還發現一些部分看起來像 Web 開發中 flexbox。即便這是一個移動 App 的開發,Web 開發人員也能夠快速地瞭解所作的事情。咱們目前只作了一個沒有任何功能的基本應用程序,可是編寫下一個部分並不困難!

關注咱們

歡迎關注咱們的公衆號:iOS-Tips,也歡迎加入咱們的羣組討論問題。能夠加微信 coldlight_hh/wsy9871 進入咱們的 iOS/flutter 微信羣。

相關文章
相關標籤/搜索