這是該系列的最後一章,都是一些不是很複雜可是在Android很經常使用的功能在Flutter中對應的解決方案。 前幾章見:html
[譯]Flutter for Android Developers - Viewsgit
[譯]Flutter for Android Developers - Intentsgithub
[譯]Flutter for Android Developers - Async UI數組
[譯]Flutter for Android Developers - Gesture Detection架構
in Androidapp
in Flutter框架
in Androidless
in Flutteride
在Flutter中咱們能監聽的生命週期有如下幾種:模塊化
下面的例子展現如何經過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類來爲咱們定義的組件類提供監聽生命週期的能力。
in Android
in Flutter
下面的代碼段展現了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佈局。
in Android
in Flutter
在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。
@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的效果。
in Android
in 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
in Flutter
下面看一個簡單的例子:
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列表項的點擊事件時,能夠爲每一個列表項設置本身的事件監聽,由它們本身來監聽處理本身的點擊事件。
in Android
in Flutter
可是當咱們只是簡單的在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時咱們無需從新構造列表項數組。其實這時候是否從新構造一個數組已經不重要了,由於列表項數組已經不是表示列表項了,它內部的元素是表示列表項須要承載的內容而已。
in Android
in Flutter
首先咱們要在項目中建立字體文件(最好是建立一個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時提供了不少參數供咱們使用,好比:
小結: 在Flutter中咱們經過構造一個TextStyle對象來描述一個樣式,並將其傳遞給Text Widget將咱們指定的樣式應用到該Text Widget上。
in Android
in Flutter
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參數,該方法會在用戶完成內容輸入時回調(好比按下軟鍵盤上的回車鍵)。咱們在這個回調中經過調用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定製不少裝飾效果。
GPS sensor
Camera
Shared Preferences
SQLite
Firebase Cloud Messaging
更多插件查閱pub.dartlang.org/packages。
若是在開發社區或者Flutter框架都沒有提供咱們想要的功能的插件,咱們能夠自定義插件。關於自定義插件的詳情能夠參閱官方文檔。
簡而言之Flutter的插件架構有點相似在Android中使用Event Bus:觸發一個消息給接收者,讓接收者處理以後返回一個結果給咱們。在這裏接收者指的就是iOS層或者Android層。
咱們能夠經過自定義一個插件來實如今Flutter應用中調用native libraries的功能。 咱們自定義的插件首先須要和Android層通訊,在Android層能夠調用native方法。一旦調用完成,再發送一個消息回Flutter層去處理。
小結: 在Flutter中咱們能夠經過使用現有的插件來實現不少與平臺層通訊的功能(Android層或者iOS層)。當現有的插件沒法知足咱們的需求時咱們也能夠定義本身的插件。