[譯]Flutter for Android Developers - Others

catsuo發表於2018-03-08

寫在前面

這是該系列的最後一章,都是一些不是很複雜但是在Android很常用的功能在Flutter中對應的解決方案。 前幾章見:

[譯]Flutter for Android Developers - Views

[譯]Flutter for Android Developers - Intents

[譯]Flutter for Android Developers - Async UI

[譯]Flutter for Android Developers - Gesture Detection

Activity和Fragment

在Flutter中Activity和Fragment等價於什麼

  • in Android

    • Activity和Fragment都是一個介面的抽象,最大的區別在於Fragment在Activity的基礎上更好的實現了模組化,並且針對大屏的裝置能實現更靈活的介面設計。
  • in Flutter

    • 在Flutter中Activity和Fragment都用Widget來表示。

監聽生命週期事件

  • in Android

    • 我們可以重寫Activity或者Fragment的生命週期回撥方法來監聽它們的生命週期並做相應的處理。
  • in Flutter

    • 我們可以利用WidgetsBindingObserver來監聽Widget的生命週期,具體怎麼使用後面有例子說明。

在Flutter中我們能監聽的生命週期有以下幾種:

  • resumed - 應用程式處於可見狀態,並且可以響應使用者的輸入事件。它相當於Android中Activity的onResume。
  • inactive - 應用程式處於閒置狀態並且沒有收到使用者的輸入事件。這個狀態對Android來說是沒用的,它只用於iOS。
  • paused - 應用程式處於不可見狀態,並且不能夠響應使用者的輸入事件。它相當於Android中Activity的onPause。
  • suspending - 應用程式將馬上被掛起。這個狀態對iOS來說沒用。

下面的例子展示如何通過WidgetsBindingObserver來監聽一個Widget的生命週期:

import 'package:flutter/widgets.dart';

class LifecycleWatcher extends StatefulWidget {
  @override
  _LifecycleWatcherState createState() => new _LifecycleWatcherState();
}

class _LifecycleWatcherState extends State<LifecycleWatcher> with WidgetsBindingObserver {
  AppLifecycleState _lastLifecyleState;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    setState(() {
      _lastLifecyleState = state;
    });
  }

  @override
  Widget build(BuildContext context) {
    if (_lastLifecyleState == null)
      return new Text('This widget has not observed any lifecycle changes.', textDirection: TextDirection.ltr);
    return new Text('The most recent lifecycle state this widget observed was: $_lastLifecyleState.',
        textDirection: TextDirection.ltr);
  }
}

void main() {
  runApp(new Center(child: new LifecycleWatcher()));
}
複製程式碼

首先通過mixin(讀作mix in,詳情參閱mixin)的方式擴充套件_LifecycleWatcherState類的功能,即在定義_LifecycleWatcherState類時使用關鍵字with來宣告_LifecycleWatcherState類需要擴充套件WidgetsBindingObserver中的功能。這裡的with其實可以簡單類比到Java中的implements關鍵字,目的都是為了避免多繼承帶來的問題,但又同時想利用多繼承的優點。

接著來看initState和dispose方法,它們是State類中提供的方法。它們其實本身也是兩個生命週期的回撥。 系統在State物件被建立好之後並且其對應的Widget已經被插入到Widget Tree中時呼叫initState方法。所以我們可以在initState方法中完成一些初始化的工作。比如這個例子我們在initState中就通過WidgetsBinding.instance獲取到WidgetsBinding例項後呼叫其addObserver方法來註冊一個WidgetsBindingObserver。 系統在State物件對應的Widget從Widget Tree中永久的刪除後呼叫dispose方法。一個State物件呼叫dispose方法之後它被認為處於一個unmounted狀態,這時候State物件的mounted屬性值返回false。在這個時候去呼叫State的setState方法將觸發一個錯誤。一個處於unmounted狀態的State物件沒法再回到remounted狀態。所以我們可以在dispose方法中完成一些資源的釋放工作,比如這個例子中我們就通過WidgetsBinding.instance獲取到WidgetsBinding例項後呼叫其removeObserver方法來登出之前註冊的WidgetsBindingObserver。

現在我們已經在initState中註冊好了WidgetsBindingObserver,所以在Widget的生命週期發生變化時系統就會呼叫WidgetsBindingObserver的didChangeAppLifecycleState方法來通知我們,因此只要重寫這個方法來實現我們在收到生命週期狀態改變的通知時需要處理的邏輯就可以了。在這裡就是簡單的儲存狀態,並且通過setState方法觸發介面重新整理。

小結: 在Flutter中除了State本身提供的生命週期回撥方法initState和dispose外,還可以通過WidgetsBindingObserver類來幫助我們實現Widget生命週期的監聽。具體使用方式是通過with關鍵字擴充套件WidgetsBindingObserver類來為我們定義的元件類提供監聽生命週期的能力。

佈局

LinearLayout等價於什麼

  • in Android

    • LinearLayout用於將我們的元件水平或垂直的線性排列。
  • in Flutter

    • 我們用Row Widget或者Column Widget實現與LinearLayout相同的效果。

下面的程式碼段展示了Row和Column的簡單使用:

override
Widget build(BuildContext context) {
  return new Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      new Text('Row One'),
      new Text('Row Two'),
      new Text('Row Three'),
      new Text('Row Four'),
    ],
  );
}
複製程式碼
override
Widget build(BuildContext context) {
  return new Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      new Text('Column One'),
      new Text('Column Two'),
      new Text('Column Three'),
      new Text('Column Four'),
    ],
  );
}
複製程式碼

Row和Column的使用比較簡單,這裡的程式碼段分別通過Row和Column實現了四個Text在水平方向和垂直方向上的簡單排列。 Row和Column的使用基本相同,但是有一些引數在它們中表示的意義是有所區別的,比如這裡的mainAxisAlignment,它表示的是主軸的對齊方式,針對Row而言,主軸是水平軸。而針對Column而言,主軸是垂直軸。關於Flutter中佈局的更多資訊可以參閱官方文件。這篇官方文件對Flutter中的很多佈局都做了說明,簡單明瞭,如果你對Flutter中的佈局比較疑惑,看完之後應該能解開你大部分的謎團。

小結: 在Flutter中我們使用Row和Column來實現Android的LinearLayout佈局。

RelativeLayout等價於什麼

  • in Android

    • RelativeLayout用於將我們的元件按照他們彼此之間的相互關係進行排列。
  • in Flutter

    • 我們可以結合Column,Row和Stack實現與RelativeLayout相同的效果。

在StackOverflow上有一個在Flutter中實現RelativeLayout的例子[https://stackoverflow.com/questions/44396075/equivalent-of-relativelayout-in -flutter](https://stackoverflow.com/questions/44396075/equivalent-of-relativelayout-in -flutter),程式碼不算複雜,主要是利用自定義Widget,Widget佈局引數和佈局的巢狀來實現RelativeLayout。

小結: 在Flutter中我們使用Column,Row和Stack等Widget的組合來實現RelativeLayout。

ScrollView等價於什麼

  • in Android
    • ScrollView作為一個容納元件的容器,能夠在要展示的內容超出螢幕範圍時實現滾動效果。
  • in Flutter
    • 最簡單的實現方式就是使用ListView Widget。
@override
Widget build(BuildContext context) {
  return new ListView(
    children: <Widget>[
      new Text('Row One'),
      new Text('Row Two'),
      new Text('Row Three'),
      new Text('Row Four'),
    ],
  );
}
複製程式碼

上面的程式碼片段使用ListView來實現Android中ScrollView的效果。將四個Text Widget組成一個陣列賦值給ListView的children引數,實現了四個Text的垂直排列,並且當內容超出螢幕範圍時通過ListView提供了滾動效果。

小結: 在Flutter中可以使用ListView Widget來實現Android中ScrollView的效果。

ListView & Adapter

在Flutter中簡單使用ListView來渲染一個列表

  • in Android

    • 我們通常構造一個Adapter,並將它傳遞給ListView,由ListView來渲染Adapter返回的每一行內容。但是我們必須自己管理ListView中的列表項,包括複用和及時的釋放等,否則會帶來意想不到的記憶體問題。
  • in Flutter

    • 由於Flutter的Widget被設計為不可變的,所以我們只要傳遞一個用於表示每行列表項的Widget陣列給ListView就行了,Flutter將完成剩下的工作並保證整個滑動的流暢性。

其實在本文前面的一節使用ListView實現ScrollView的時候已經看到了如何簡單的使用ListView,無非就是傳遞給ListView的children引數一個表示列表項的陣列。下面展示一個完整的例子:

import 'package:flutter/material.dart';

void main() {
  runApp(new SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Sample App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => new _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Sample App"),
      ),
      body: new ListView(children: _getListData()),
    );
  }

  _getListData() {
    List<Widget> widgets = [];
    for (int i = 0; i < 100; i++) {
      widgets.add(new Padding(padding: new EdgeInsets.all(10.0), child: new Text("Row $i")));
    }
    return widgets;
  }
}
複製程式碼

這個例子與前面實現ScrollView的例子很相似,通過_getListData方法返回一個Widget的陣列傳遞給ListView的children引數。這種使用ListView的方式其實是比較低效的,因為ListView的children引數被我們賦值為一個表示列表項的陣列,在本例中這個陣列中就表示100個Widget,也就是說系統確實會為ListView生成100個表示其列表項的Widget。這有點類似在Android中不使用ViewHolder直接使用Adapter。在後面我們會講到更高效的使用ListView的方法。

小結: 在Flutter中要使用ListView來渲染一個列表最簡單直接的方式就是構造一個ListView,並且傳遞給它一個描述每個列表項的Widget陣列。剩下的工作交給Flutter就可以了。

處理列表項的點選事件

  • in Android

    • 我們可以通過ListView的onItemClickListener方法得知使用者點選的列表項是哪一個。
  • in Flutter

    • 因為我們傳遞給ListView的就是包含所有列表項Widget的陣列,所以我們可以直接處理陣列中的每個列表項Widget的點選事件來實現與onItemClickListener類似的效果。

下面看一個簡單的例子:

import 'package:flutter/material.dart';

void main() {
  runApp(new SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Sample App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => new _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Sample App"),
      ),
      body: new ListView(children: _getListData()),
    );
  }

  _getListData() {
    List<Widget> widgets = [];
    for (int i = 0; i < 100; i++) {
      widgets.add(new GestureDetector(
        child: new Padding(
            padding: new EdgeInsets.all(10.0),
            child: new Text("Row $i")),
        onTap: () {
          print('row tapped');
        },
      ));
    }
    return widgets;
  }
}
複製程式碼

這個例子在前一個簡單列表項展示的例子基礎上做了一點點的改動實現列表項點選事件的監聽處理。 在_getListData方法中,構造每一個列表項時使用GestureDetector來包裹Text為其實現onTap點選事件的監聽(關於事件的監聽處理可參閱FFAD-Gesture Detection)。也就是說這裡構造的100個列表項在構造的時候同時就為它們設定好了各自的點選事件監聽的處理邏輯。在點選ListView中每一個列表項時將直接呼叫它們自己的事件處理方法。

小結: 在Flutter中處理ListView列表項的點選事件時,可以為每個列表項設定自己的事件監聽,由它們自己來監聽處理自己的點選事件。

怎樣動態更新ListView

  • in Android

    • 我們可以更新Adapter中的資料內容並呼叫其notifyDataSetChanged方法來實現資料執行時的動態更新。
  • in Flutter

    • 與所有Flutter中的Widget更新一樣,我們依然通過修改State來實現Widget的更新。即在setState中去更新傳遞給ListView的列表項陣列。

但是當我們只是簡單的在setState方法中往列表項陣列中新增或者刪除列表項時,會發現介面並沒有重新整理。這是因為setState方法的呼叫會導致Flutter的渲染引擎開始遍歷所有的Widgets去確認它們是否有變化,只有有變化的Widget才會被Flutter重新渲染重新整理。當遍歷到ListView的children時會通過==operator方法去比較前後兩個ListView的children,該方法的比較邏輯類似Java中直接使用等號比較,比較的是物件引用,所以如果直接向老的列表項陣列插入或者刪除列表項,陣列本身的引用是沒有改變的,Flutter會認為前後兩個ListView的children是相同的沒有發生變化。所以導致介面沒有重新整理。

為了能夠實現ListView的更新我們需要在setState中建立一個新的列表項陣列例項,並且將列表項資料從老的陣列拷貝到新的陣列中。然後再在新的陣列中新增或者刪除列表項,而不是像上面說的那樣直接更新老的列表項陣列。下面是一個實現動態更新的例子:

import 'package:flutter/material.dart';

void main() {
  runApp(new SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Sample App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => new _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  List widgets = [];

  @override
  void initState() {
    super.initState();
    for (int i = 0; i < 100; i++) {
      widgets.add(getRow(i));
    }
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Sample App"),
      ),
      body: new ListView(children: widgets),
    );
  }

  Widget getRow(int i) {
    return new GestureDetector(
      child: new Padding(
          padding: new EdgeInsets.all(10.0),
          child: new Text("Row $i")),
      onTap: () {
        setState(() {
          widgets = new List.from(widgets);
          widgets.add(getRow(widgets.length + 1));
          print('row $i');
        });
      },
    );
  }
}
複製程式碼

上面的例子實現每點選列表項就在ListView中增加一條記錄的效果。 在initState方法中初始化存放列表項的陣列widgets。然後監聽每個列表項的點選事件,在監聽處理函式中通過呼叫setState來觸發介面更新。 特別注意的是在setState方法內部首先是基於舊的widgets構造一個新的widgets陣列例項,然後再往新構造的widgets陣列中新增一條記錄以此來實現有效的動態更新。

到目前為止我們使用ListView的方式都很簡單,首先我們構造列表項陣列,然後將列表項陣列傳遞給ListView的children引數來展示列表項陣列中的Widget。然而當我們列表項的數量非常龐大時,建議利用ListView.Builder來提高ListView的效率。因為它能夠像Android中的RecyclerView一樣幫助我們自動的重用的釋放列表項。

下面的例子展示了怎樣通過ListView.builder來高效使用ListView:

import 'package:flutter/material.dart';

void main() {
  runApp(new SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Sample App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => new _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  List widgets = [];

  @override
  void initState() {
    super.initState();
    for (int i = 0; i < 100; i++) {
      widgets.add(getRow(i));
    }
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
          title: new Text("Sample App"),
        ),
        body: new ListView.builder(
            itemCount: widgets.length,
            itemBuilder: (BuildContext context, int position) {
              return getRow(position);
            }));
  }

  Widget getRow(int i) {
    return new GestureDetector(
      child: new Padding(
          padding: new EdgeInsets.all(10.0),
          child: new Text("Row $i")),
      onTap: () {
        setState(() {
          widgets.add(getRow(widgets.length + 1));
          print('row $i');
        });
      },
    );
  }
}
複製程式碼

與之前我們直接構造ListView不同的是這裡我們建立了一個ListView.builder,並傳遞給它兩個引數,itemCount表示列表項的長度,itemBuilder是一個方法,它很像Android中Adapter的getView方法,itemBuilder方法有一個表示列表項位置的引數position,然後返回這個位置對應的列表項。

另外這裡看到我們並沒有像之前使用ListView那樣直接對ListView的children賦值,也就是說使用ListView.builder的方式來構造ListView時,ListView的children並不是在構造時靜態寫死的,而是在執行時動態更新的。也正是因為這個原因,所以這個例子中實現動態更新時setState方法中並不需要重新構造列表項陣列了。

其實再細心點你會發現利用ListView.builder的方式來構造ListView很多地方跟Android中ListView和Adapter的搭配很像。比如itemCount引數和itemBuilder引數,itemCount很像Adapter的getCount方法,itemBuilder很像Adapter的getView方法。再比如widgets陣列,你會發現這個例子中widgets陣列中的元素其實完全可以不是Widget型別,它們可以是任何表示資料內容的實體型別,就類似Adapter中承載的資料集一樣。每一個列表項由itemBuilder方法動態構造出來,而widgets陣列這裡的作用無非就是提供每個列表項要承載的內容資料。

小結: 在Flutter中建議通過ListView.builder來使用ListView。因為通過ListView.builder的方式系統能夠幫助我們自動完成列表項的釋放或重用等工作,它是一個更加高效的選擇。 ListView的動態更新關鍵在於其children是否有變化,當直接構造ListView時由於我們靜態的將一個表示列表項的陣列賦值給其children,所以在setState時我們需要手動去重新建立列表項陣列,以保證前後的物件引用不同。當使用ListView.builder的方式實現ListView時其children是在執行時動態生成的,所以在setState時我們無需重新構造列表項陣列。其實這時候是否重新構造一個陣列已經不重要了,因為列表項陣列已經不是表示列表項了,它內部的元素是表示列表項需要承載的內容而已。

關於Text

Text樣式

  • in Android

    • 我們可以建立一個字型資原始檔,然後把它傳遞給TextView的FontFamily引數來實現自定義TextView的字型。
  • in Flutter

    • Text Widget等價於Android中的TextView。我們可以通過TextStyle類來實現自定義字型。

首先我們要在專案中建立字型檔案(最好是建立一個assets目錄,將字型檔案放在該目錄中)。然後在pubspec.yaml中宣告我們需要使用的字型:

fonts:
   - family: MyCustomFont
     fonts:
       - asset: fonts/MyCustomFont.ttf
       - style: italic
複製程式碼

上面需要注意的是第2行family的配置,後面Text Widget中使用字型的時候就是通過這裡配置的family name來指定。 接下來就可以在Text中使用字型了:

@override
Widget build(BuildContext context) {
  return new Scaffold(
    appBar: new AppBar(
      title: new Text("Sample App"),
    ),
    body: new Center(
      child: new Text(
        'This is a custom font text',
        style: new TextStyle(fontFamily: 'MyCustomFont'),
      ),
    ),
  );
}
複製程式碼

上面的程式碼片段在構造Text時除了要顯示的內容外還傳入了一個style引數,該引數是一個TextStyle型別,構造TextStyle型別時通過指定前面配置的family name來指定我們要使用的字型。

除了自定義字型外我們還可以為Text Widget自定義很多樣式。構造TextStyle時提供了很多引數供我們使用,比如:

  • color
  • decoration
  • decorationColor
  • decorationStyle
  • fontFamily
  • fontSize
  • fontStyle
  • fontWeight
  • hashCode
  • height
  • inherit
  • letterSpacing
  • textBaseline
  • wordSpacing

小結: 在Flutter中我們通過構造一個TextStyle物件來描述一個樣式,並將其傳遞給Text Widget將我們指定的樣式應用到該Text Widget上。

關於TextField

實現輸入框的hint

  • in Android

    • hint就是EditText的一個屬性,我們直接設定該屬性來實現輸入框在無輸入狀態時顯示的預設文字。
  • in Flutter

    • TextField Widget用於表示一個讓使用者輸入文字的控制元件,我們可以通過InputDecoration類簡單的實現Android中hint的效果。
body: new Center(
  child: new TextField(
    decoration: new InputDecoration(hintText: "This is a hint"),
  )
)
複製程式碼

這個例子構造了一個InputDecoration物件,在構造時可以傳入一個hintText引數,該引數則表示輸入框無輸入狀態時顯示的預設內容。最後將構造好的InputDecoration物件傳遞給TextField的decoration引數使得TextField具有hint功能。

小結: 在Flutter中通過TextField來表示一個讓使用者輸入文字的控制元件。首先構造一個InputDecoration物件來描述hint的內容,接著在構造TextField時傳遞該InputDecoration物件來實現TextField的hint功能。

提示輸入內容錯誤

就像我們實現hint一樣,同樣通過InputDecoration來實現提示輸入內容錯誤的效果,在構造InputDecoration時傳入另一個errorText引數,該引數描述的一個文字資訊會被當做錯誤資訊展示給使用者。利用該引數我們就可以實現在使用者輸入內容錯誤時提示使用者的效果,如下例:

import 'package:flutter/material.dart';

void main() {
  runApp(new SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Sample App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => new _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  String _errorText;

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Sample App"),
      ),
      body: new Center(
        child: new TextField(
          onSubmitted: (String text) {
            setState(() {
              if (!isEmail(text)) {
                _errorText = 'Error: This is not an email';
              } else {
                _errorText = null;
              }
            });
          },
          decoration: new InputDecoration(hintText: "This is a hint", errorText: _getErrorText()),
        ),
      ),
    );
  }

  _getErrorText() {
    return _errorText;
  }

  bool isEmail(String em) {
    String emailRegexp =
        r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';

    RegExp regExp = new RegExp(p);

    return regExp.hasMatch(em);
  }
}
複製程式碼

這個例子在使用者完成輸入時去驗證使用者的輸入內容是否是一個郵箱地址格式,若不是則提示使用者。

首先還是通過TextField來實現一個輸入框,傳遞一個方法給其onSubmitted引數,該方法會在使用者完成內容輸入時回撥(比如按下軟鍵盤上的Enter鍵)。我們在這個回撥中通過呼叫setState來觸發介面更新。在setState中我們更新的是_errorText成員變數,它由使用者輸入的內容決定。 同時還傳遞給TextField一個InputDecoration物件,InputDecoration物件在構造時除了之前用過的hintText引數外我們還將_errorText的值傳遞給其errorText引數。_errorText是在setState方法中動態改變的,當使用者輸入的內容驗證是一個郵件地址時,_errorText被賦值為null,此時TextField不會顯示錯誤提示。當使用者輸入的內容驗證錯誤時則_errorText被賦值為Error: This is not an email,此時TextField會將該文字作文錯誤提示展示給使用者。

這個例子是通過TextField的onSubmitted回撥來觸發檢查的,我們還可以使用TextField的另一個回撥onChanged來觸發檢查,onChanged會在輸入內容發生改變時便立即回撥。關於TextField的更多資訊可以參閱官方文件。另外構造InputDecoration時可傳入的引數也還有很多,關於InputDecoration的更多資訊可以參閱官方文件

小結: InputDecoration正如其名字一樣,是一個裝飾類。利用InputDecoration類可以為TextField定製很多裝飾效果。

Flutter外掛

常用外掛

更多外掛查閱pub.dartlang.org/packages

自定義外掛

如果在開發社群或者Flutter框架都沒有提供我們想要的功能的外掛,我們可以自定義外掛。關於自定義外掛的詳情可以參閱官方文件

簡而言之Flutter的外掛架構有點類似在Android中使用Event Bus:觸發一個訊息給接收者,讓接收者處理之後返回一個結果給我們。在這裡接收者指的就是iOS層或者Android層。

在Flutter中使用NDK

我們可以通過自定義一個外掛來實現在Flutter應用中呼叫native libraries的功能。 我們自定義的外掛首先需要和Android層通訊,在Android層可以呼叫native方法。一旦呼叫完成,再傳送一個訊息回Flutter層去處理。

小結: 在Flutter中我們可以通過使用現有的外掛來實現很多與平臺層通訊的功能(Android層或者iOS層)。當現有的外掛無法滿足我們的需求時我們也可以定義自己的外掛。

相關文章