介紹
Flutter Widget采用(yòng)現(xiàn)代響應式框架構建,這(zhè)是從(cóng) React 中獲得的靈感,中心思想是用(yòng)widget構建你(nǐ)的UI。 Widget描述了(le)他(tā)們的視(shì)圖在給定其當前配置和(hé)狀态時(shí)應該看(kàn)起來(lái)像什(shén)麽。當widget的狀态發生變化時(shí),widget會(huì)重新構建UI,Flutter會(huì)對(duì)比前後變化的不同, 以确定底層渲染樹從(cóng)一個狀态轉換到(dào)下(xià)一個狀态所需的最小(xiǎo)更改(譯者語:類似于React/Vue中虛拟DOM的diff算(suàn)法)。
注意: 如果您想通過代碼來(lái)深入了(le)解Flutter,請(qǐng)查看(kàn) 構建Flutter布局 和(hé) 爲Flutter App添加交互功能(néng)。
Hello World
一個最簡單的Flutter應用(yòng)程序,隻需一個widget即可!如下(xià)面示例:将一個widget傳給runApp函數即可:
import 'package:flutter/material.dart';
void main() {
runApp(
new Center(
child: new Text(
'Hello, world!',
textDirection: TextDirection.ltr,
),
),
);
}
該runApp函數接受給定的Widget并使其成爲widget樹的根。 在此示例中,widget樹由兩個widget:Center(及其子widget)和(hé)Text組成。框架強制根widget覆蓋整個屏幕,這(zhè)意味着文(wén)本“Hello, world”會(huì)居中顯示在屏幕上(shàng)。文(wén)本顯示的方向需要在Text實例中指定,當使用(yòng)MaterialApp時(shí),文(wén)本的方向将自(zì)動設定,稍後将進行演示。
在編寫應用(yòng)程序時(shí),通常會(huì)創建新的widget,這(zhè)些(xiē)widget是無狀态的StatelessWidget或者是有狀态的StatefulWidget, 具體的選擇取決于您的widget是否需要管理(lǐ)一些(xiē)狀态。widget的主要工(gōng)作(zuò)是實現(xiàn)一個build函數,用(yòng)以構建自(zì)身。一個widget通常由一些(xiē)較低(dī)級别widget組成。Flutter框架将依次構建這(zhè)些(xiē)widget,直到(dào)構建到(dào)最底層的子widget時(shí),這(zhè)些(xiē)最低(dī)層的widget通常爲RenderObject,它會(huì)計(jì)算(suàn)并描述widget的幾何形狀。
基礎 Widget
主要文(wén)章: widget概述-布局模型
Flutter有一套豐富、強大(dà)的基礎widget,其中以下(xià)是很(hěn)常用(yòng)的:
Text:該 widget 可讓創建一個帶格式的文(wén)本。
Row、 Column: 這(zhè)些(xiē)具有彈性空(kōng)間的布局類Widget可讓您在水(shuǐ)平(Row)和(hé)垂直(Column)方向上(shàng)創建靈活的布局。其設計(jì)是基于web開(kāi)發中的Flexbox布局模型。
Stack: 取代線性布局 (譯者語:和(hé)Android中的LinearLayout相似),Stack允許子 widget 堆疊, 你(nǐ)可以使用(yòng) Positioned 來(lái)定位他(tā)們相對(duì)于Stack的上(shàng)下(xià)左右四條邊的位置。Stacks是基于Web開(kāi)發中的絕度定位(absolute positioning )布局模型設計(jì)的。
Container: Container 可讓您創建矩形視(shì)覺元素。container 可以裝飾爲一個BoxDecoration, 如 background、一個邊框、或者一個陰影。 Container 也(yě)可以具有邊距(margins)、填充(padding)和(hé)應用(yòng)于其大(dà)小(xiǎo)的約束(constraints)。另外(wài), Container可以使用(yòng)矩陣在三維空(kōng)間中對(duì)其進行變換。
以下(xià)是一些(xiē)簡單的Widget,它們可以組合出其它的Widget:
import 'package:flutter/material.dart';
class MyAppBar extends StatelessWidget {
MyAppBar({this.title});
// Widget子類中的字段往往都會(huì)定義爲"final"
final Widget title;
@override
Widget build(BuildContext context) {
return new Container(
height: 56.0, // 單位是邏輯上(shàng)的像素(并非真實的像素,類似于浏覽器中的像素)
padding: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: new BoxDecoration(color: Colors.blue[500]),
// Row 是水(shuǐ)平方向的線性布局(linear layout)
child: new Row(
//列表項的類型是
children:
new IconButton(
icon: new Icon(Icons.menu),
tooltip: 'Navigation menu',
onPressed: null, // null 會(huì)禁用(yòng) button
),
// Expanded expands its child to fill the available space.
new Expanded(
child: title,
),
new IconButton(
icon: new Icon(Icons.search),
tooltip: 'Search',
onPressed: null,
),
],
),
);
}
}
class MyScaffold extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Material 是UI呈現(xiàn)的“一張紙(zhǐ)”
return new Material(
// Column is 垂直方向的線性布局.
child: new Column(
children:
new MyAppBar(
title: new Text(
'Example title',
style: Theme.of(context).primaryTextTheme.title,
),
),
new Expanded(
child: new Center(
child: new Text('Hello, world!'),
),
),
],
),
);
}
}
void main() {
runApp(new MaterialApp(
title: 'My app', // used by the OS task switcher
home: new MyScaffold(),
));
}
請(qǐng)确保在pubspec.yaml文(wén)件中,将flutter的值設置爲:uses-material-design: true。這(zhè)允許我們可以使用(yòng)一組預定義Material icons。
name: my_app
flutter:
uses-material-design: true
爲了(le)繼承主題數據,widget需要位于MaterialApp内才能(néng)正常顯示, 因此我們使用(yòng)MaterialApp來(lái)運行該應用(yòng)。
在MyAppBar中創建一個Container,高(gāo)度爲56像素(像素單位獨立于設備,爲邏輯像素),其左側和(hé)右側均有8像素的填充。在容器内部, MyAppBar使用(yòng)Row 布局來(lái)排列其子項。 中間的title widget被标記爲Expanded, ,這(zhè)意味着它會(huì)填充尚未被其他(tā)子項占用(yòng)的的剩餘可用(yòng)空(kōng)間。Expanded可以擁有多個children, 然後使用(yòng)flex參數來(lái)确定他(tā)們占用(yòng)剩餘空(kōng)間的比例。
MyScaffold 通過一個Column widget,在垂直方向排列其子項。在Column的頂部,放(fàng)置了(le)一個MyAppBar實例,将一個Text widget作(zuò)爲其标題傳遞給應用(yòng)程序欄。将widget作(zuò)爲參數傳遞給其他(tā)widget是一種強大(dà)的技術,可以讓您創建各種複雜(zá)的widget。最後,MyScaffold使用(yòng)了(le)一個Expanded來(lái)填充剩餘的空(kōng)間,正中間包含一條message。
使用(yòng) Material 組件
主要文(wén)章: Widgets 總覽 - Material 組件
Flutter提供了(le)許多widgets,可幫助您構建遵循Material Design的應用(yòng)程序。Material應用(yòng)程序以MaterialApp widget開(kāi)始, 該widget在應用(yòng)程序的根部創建了(le)一些(xiē)有用(yòng)的widget,其中包括一個Navigator, 它管理(lǐ)由字符串标識的Widget棧(即頁面路由棧)。Navigator可以讓您的應用(yòng)程序在頁面之間的平滑的過渡。 是否使用(yòng)MaterialApp完全是可選的,但(dàn)是使用(yòng)它是一個很(hěn)好(hǎo)的做法。
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
title: 'Flutter Tutorial',
home: new TutorialHome(),
));
}
class TutorialHome extends StatelessWidget {
@override
Widget build(BuildContext context) {
//Scaffold是Material中主要的布局組件.
return new Scaffold(
appBar: new AppBar(
leading: new IconButton(
icon: new Icon(Icons.menu),
tooltip: 'Navigation menu',
onPressed: null,
),
title: new Text('Example title'),
actions:
new IconButton(
icon: new Icon(Icons.search),
tooltip: 'Search',
onPressed: null,
),
],
),
//body占屏幕的大(dà)部分
body: new Center(
child: new Text('Hello, world!'),
),
floatingActionButton: new FloatingActionButton(
tooltip: 'Add', // used by assistive technologies
child: new Icon(Icons.add),
onPressed: null,
),
);
}
}
現(xiàn)在我們已經從(cóng)MyAppBar和(hé)MyScaffold切換到(dào)了(le)AppBar和(hé) Scaffold widget, 我們的應用(yòng)程序現(xiàn)在看(kàn)起來(lái)已經有一些(xiē)“Material”了(le)!例如,應用(yòng)欄有一個陰影,标題文(wén)本會(huì)自(zì)動繼承正确的樣式。我們還添加了(le)一個浮動操作(zuò)按鈕,以便進行相應的操作(zuò)處理(lǐ)。
請(qǐng)注意,我們再次将widget作(zuò)爲參數傳遞給其他(tā)widget。該 Scaffold widget 需要許多不同的widget的作(zuò)爲命名參數,其中的每一個被放(fàng)置在Scaffold布局中相應的位置。 同樣,AppBar 中,我們給參數leading、actions、title分别傳一個widget。 這(zhè)種模式在整個框架中會(huì)經常出現(xiàn),這(zhè)也(yě)可能(néng)是您在設計(jì)自(zì)己的widget時(shí)會(huì)考慮到(dào)一點。
處理(lǐ)手勢
主要文(wén)章: Flutter中的手勢
大(dà)多數應用(yòng)程序包括某種形式與系統的交互。構建交互式應用(yòng)程序的第一步是檢測輸入手勢。讓我們通過創建一個簡單的按鈕來(lái)了(le)解它的工(gōng)作(zuò)原理(lǐ):
class MyButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new GestureDetector(
onTap: () {
print('MyButton was tapped!');
},
child: new Container(
height: 36.0,
padding: const EdgeInsets.all(8.0),
margin: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: new BoxDecoration(
borderRadius: new BorderRadius.circular(5.0),
color: Colors.lightGreen[500],
),
child: new Center(
child: new Text('Engage'),
),
),
);
}
}
該GestureDetector widget并不具有顯示效果,而是檢測由用(yòng)戶做出的手勢。 當用(yòng)戶點擊Container時(shí), GestureDetector會(huì)調用(yòng)它的onTap回調, 在回調中,将消息打印到(dào)控制台。您可以使用(yòng)GestureDetector來(lái)檢測各種輸入手勢,包括點擊、拖動和(hé)縮放(fàng)。
許多widget都會(huì)使用(yòng)一個GestureDetector爲其他(tā)widget提供可選的回調。 例如,IconButton、 RaisedButton、 和(hé)FloatingActionButton ,它們都有一個onPressed回調,它會(huì)在用(yòng)戶點擊該widget時(shí)被觸發。
根據用(yòng)戶輸入改變widget
主要文(wén)章: StatefulWidget, State.setState
到(dào)目前爲止,我們隻使用(yòng)了(le)無狀态的widget。無狀态widget從(cóng)它們的父widget接收參數, 它們被存儲在final型的成員變量中。 當一個widget被要求構建時(shí),它使用(yòng)這(zhè)些(xiē)存儲的值作(zuò)爲參數來(lái)構建widget。
爲了(le)構建更複雜(zá)的體驗 - 例如,以更有趣的方式對(duì)用(yòng)戶輸入做出反應 - 應用(yòng)程序通常會(huì)攜帶一些(xiē)狀态。 Flutter使用(yòng)StatefulWidgets來(lái)滿足這(zhè)種需求。StatefulWidgets是特殊的widget,它知(zhī)道(dào)如何生成State對(duì)象,然後用(yòng)它來(lái)保持狀态。 思考下(xià)面這(zhè)個簡單的例子,其中使用(yòng)了(le)前面提到(dào)RaisedButton:
class Counter extends StatefulWidget {
// This class is the configuration for the state. It holds the
// values (in this nothing) provided by the parent and used by the build
// method of the State. Fields in a Widget subclass are always marked "final".
@override
_CounterState createState() => new _CounterState();
}
class _CounterState extends State
int _counter = 0;
void _increment() {
setState(() {
// This call to setState tells the Flutter framework that
// something has changed in this State, which causes it to rerun
// the build method below so that the display can reflect the
// updated values. If we changed _counter without calling
// setState(), then the build method would not be called again,
// and so nothing would appear to happen.
_counter++;
});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance
// as done by the _increment method above.
// The Flutter framework has been optimized to make rerunning
// build methods fast, so that you can just rebuild anything that
// needs updating rather than having to individually change
// instances of widgets.
return new Row(
children:
new RaisedButton(
onPressed: _increment,
child: new Text('Increment'),
),
new Text('Count: $_counter'),
],
);
}
}
您可能(néng)想知(zhī)道(dào)爲什(shén)麽StatefulWidget和(hé)State是單獨的對(duì)象。在Flutter中,這(zhè)兩種類型的對(duì)象具有不同的生命周期: Widget是臨時(shí)對(duì)象,用(yòng)于構建當前狀态下(xià)的應用(yòng)程序,而State對(duì)象在多次調用(yòng)build()之間保持不變,允許它們記住信息(狀态)。
上(shàng)面的例子接受用(yòng)戶點擊,并在點擊時(shí)使_counter自(zì)增,然後直接在其build方法中使用(yòng)_counter值。在更複雜(zá)的應用(yòng)程序中,widget結構層次的不同部分可能(néng)有不同的職責; 例如,一個widget可能(néng)呈現(xiàn)一個複雜(zá)的用(yòng)戶界面,其目标是收集特定信息(如日期或位置),而另一個widget可能(néng)會(huì)使用(yòng)該信息來(lái)更改整體的顯示。
在Flutter中,事(shì)件流是“向上(shàng)”傳遞的,而狀态流是“向下(xià)”傳遞的(譯者語:這(zhè)類似于React/Vue中父子組件通信的方式:子widget到(dào)父widget是通過事(shì)件通信,而父到(dào)子是通過狀态),重定向這(zhè)一流程的共同父元素是State。讓我們看(kàn)看(kàn)這(zhè)個稍微複雜(zá)的例子是如何工(gōng)作(zuò)的:
class CounterDisplay extends StatelessWidget {
CounterDisplay({this.count});
final int count;
@override
Widget build(BuildContext context) {
return new Text('Count: $count');
}
}
class CounterIncrementor extends StatelessWidget {
CounterIncrementor({this.onPressed});
final VoidCallback onPressed;
@override
Widget build(BuildContext context) {
return new RaisedButton(
onPressed: onPressed,
child: new Text('Increment'),
);
}
}
class Counter extends StatefulWidget {
@override
_CounterState createState() => new _CounterState();
}
class _CounterState extends State
int _counter = 0;
void _increment() {
setState(() {
++_counter;
});
}
@override
Widget build(BuildContext context) {
return new Row(children:
new CounterIncrementor(onPressed: _increment),
new CounterDisplay(count: _counter),
]);
}
}
注意我們是如何創建了(le)兩個新的無狀态widget的!我們清晰地分離了(le) 顯示 計(jì)數器(CounterDisplay)和(hé) 更改 計(jì)數器(CounterIncrementor)的邏輯。 盡管最終效果與前一個示例相同,但(dàn)責任分離允許将複雜(zá)性邏輯封裝在各個widget中,同時(shí)保持父項的簡單性。
整合所有
讓我們考慮一個更完整的例子,将上(shàng)面介紹的概念彙集在一起。我們假設一個購物應用(yòng)程序,該應用(yòng)程序顯示出售的各種産品,并維護一個購物車。 我們先來(lái)定義ShoppingListItem:
class Product {
const Product({this.name});
final String name;
}
typedef void CartChangedCallback(Product product, bool inCart);
class ShoppingListItem extends StatelessWidget {
ShoppingListItem({Product product, this.inCart, this.onCartChanged})
: product = product,
super(key: new ObjectKey(product));
final Product product;
final bool inCart;
final CartChangedCallback onCartChanged;
Color _getColor(BuildContext context) {
// The theme depends on the BuildContext because different parts of the tree
// can have different themes. The BuildContext indicates where the build is
// taking place and therefore which theme to use.
return inCart ? Colors.black54 : Theme.of(context).primaryColor;
}
TextStyle _getTextStyle(BuildContext context) {
if (!inCart) return null;
return new TextStyle(
color: Colors.black54,
decoration: TextDecoration.lineThrough,
);
}
@override
Widget build(BuildContext context) {
return new ListTile(
onTap: () {
onCartChanged(product, !inCart);
},
leading: new CircleAvatar(
backgroundColor: _getColor(context),
child: new Text(product.name[0]),
),
title: new Text(product.name, style: _getTextStyle(context)),
);
}
}
該ShoppingListItem widget是無狀态的。它将其在構造函數中接收到(dào)的值存儲在final成員變量中,然後在build函數中使用(yòng)它們。 例如,inCart布爾值表示在兩種視(shì)覺展示效果之間切換:一個使用(yòng)當前主題的主色,另一個使用(yòng)灰色。
當用(yòng)戶點擊列表項時(shí),widget不會(huì)直接修改其inCart的值。相反,widget會(huì)調用(yòng)其父widget給它的onCartChanged回調函數。 此模式可讓您在widget層次結構中存儲更高(gāo)的狀态,從(cóng)而使狀态持續更長的時(shí)間。在極端情況下(xià),存儲傳給runApp應用(yòng)程序的widget的狀态将在的整個生命周期中持續存在。
當父項收到(dào)onCartChanged回調時(shí),父項将更新其内部狀态,這(zhè)将觸發父項使用(yòng)新inCart值重建ShoppingListItem新實例。 雖然父項ShoppingListItem在重建時(shí)創建了(le)一個新實例,但(dàn)該操作(zuò)開(kāi)銷很(hěn)小(xiǎo),因爲Flutter框架會(huì)将新構建的widget與先前構建的widget進行比較,并僅将差異部分應用(yòng)于底層RenderObject。
我們來(lái)看(kàn)看(kàn)父widget存儲可變狀态的示例:
class ShoppingList extends StatefulWidget {
ShoppingList({Key key, this.products}) : super(key: key);
final List
// The framework calls createState the first time a widget appears at a given
// location in the tree. If the parent rebuilds and uses the same type of
// widget (with the same key), the framework will re-use the State object
// instead of creating a new State object.
@override
_ShoppingListState createState() => new _ShoppingListState();
}
class _ShoppingListState extends State
Set
void _handleCartChanged(Product product, bool inCart) {
setState(() {
// When user changes what is in the cart, we need to change _shoppingCart
// inside a setState call to trigger a rebuild. The framework then calls
// build, below, which updates the visual appearance of the app.
if (inCart)
_shoppingCart.add(product);
else
_shoppingCart.remove(product);
});
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Shopping List'),
),
body: new ListView(
padding: new EdgeInsets.symmetric(vertical: 8.0),
children: widget.products.map((Product product) {
return new ShoppingListItem(
product: product,
inCart: _shoppingCart.contains(product),
onCartChanged: _handleCartChanged,
);
}).toList(),
),
);
}
}
void main() {
runApp(new MaterialApp(
title: 'Shopping App',
home: new ShoppingList(
products:
new Product(name: 'Eggs'),
new Product(name: 'Flour'),
new Product(name: 'Chocolate chips'),
],
),
));
}
ShoppingList類繼承自(zì)StatefulWidget,這(zhè)意味着這(zhè)個widget可以存儲狀态。 當ShoppingList首次插入到(dào)樹中時(shí),框架會(huì)調用(yòng)其 createState 函數以創建一個新的_ShoppingListState實例來(lái)與該樹中的相應位置關聯(請(qǐng)注意,我們通常命名State子類時(shí)帶一個下(xià)劃線,這(zhè)表示其是私有的)。 當這(zhè)個widget的父級重建時(shí),父級将創建一個新的ShoppingList實例,但(dàn)是Flutter框架将重用(yòng)已經在樹中的_ShoppingListState實例,而不是再次調用(yòng)createState創建一個新的。
要訪問當前ShoppingList的屬性,_ShoppingListState可以使用(yòng)它的widget屬性。 如果父級重建并創建一個新的ShoppingList,那麽 _ShoppingListState也(yě)将用(yòng)新的widget值重建(譯者語:這(zhè)裏原文(wén)檔有錯誤,應該是_ShoppingListState不會(huì)重新構建,但(dàn)其widget的屬性會(huì)更新爲新構建的widget)。 如果希望在widget屬性更改時(shí)收到(dào)通知(zhī),則可以覆蓋didUpdateWidget函數,以便将舊的oldWidget與當前widget進行比較。
處理(lǐ)onCartChanged回調時(shí),_ShoppingListState通過添加或删除産品來(lái)改變其内部_shoppingCart狀态。 爲了(le)通知(zhī)框架它改變了(le)它的内部狀态,需要調用(yòng)setState。調用(yòng)setState将該widget标記爲”dirty”(髒的),并且計(jì)劃在下(xià)次應用(yòng)程序需要更新屏幕時(shí)重新構建它。 如果在修改widget的内部狀态後忘記調用(yòng)setState,框架将不知(zhī)道(dào)您的widget是”dirty”(髒的),并且可能(néng)不會(huì)調用(yòng)widget的build方法,這(zhè)意味着用(yòng)戶界面可能(néng)不會(huì)更新以展示新的狀态。
通過以這(zhè)種方式管理(lǐ)狀态,您不需要編寫用(yòng)于創建和(hé)更新子widget的單獨代碼。相反,您隻需實現(xiàn)可以處理(lǐ)這(zhè)兩種情況的build函數。
響應widget生命周期事(shì)件
主要文(wén)章: State
在StatefulWidget調用(yòng)createState之後,框架将新的狀态對(duì)象插入樹中,然後調用(yòng)狀态對(duì)象的initState。 子類化State可以重寫initState,以完成僅需要執行一次的工(gōng)作(zuò)。 例如,您可以重寫initState以配置動畫(huà)或訂閱platform services。initState的實現(xiàn)中需要調用(yòng)super.initState。
當一個狀态對(duì)象不再需要時(shí),框架調用(yòng)狀态對(duì)象的dispose。 您可以覆蓋該dispose方法來(lái)執行清理(lǐ)工(gōng)作(zuò)。例如,您可以覆蓋dispose取消定時(shí)器或取消訂閱platform services。 dispose典型的實現(xiàn)是直接調用(yòng)super.dispose。
Key
主要文(wén)章: Key_
您可以使用(yòng)key來(lái)控制框架将在widget重建時(shí)與哪些(xiē)其他(tā)widget匹配。默認情況下(xià),框架根據它們的runtimeType和(hé)它們的顯示順序來(lái)匹配。 使用(yòng)key時(shí),框架要求兩個widget具有相同的key和(hé)runtimeType。
Key在構建相同類型widget的多個實例時(shí)很(hěn)有用(yòng)。例如,ShoppingList構建足夠的ShoppingListItem實例以填充其可見區(qū)域:
如果沒有key,當前構建中的第一個條目将始終與前一個構建中的第一個條目同步,即使在語義上(shàng),列表中的第一個條目如果滾動出屏幕,那麽它将不會(huì)再在窗口中可見。
通過給列表中的每個條目分配爲“語義” key,無限列表可以更高(gāo)效,因爲框架将同步條目與匹配的語義key并因此具有相似(或相同)的可視(shì)外(wài)觀。 此外(wài),語義上(shàng)同步條目意味着在有狀态子widget中,保留的狀态将附加到(dào)相同的語義條目上(shàng),而不是附加到(dào)相同數字位置上(shàng)的條目。
全局 Key
主要文(wén)章: GlobalKey
您可以使用(yòng)全局key來(lái)唯一标識子widget。全局key在整個widget層次結構中必須是全局唯一的,這(zhè)與局部key不同,後者隻需要在同級中唯一。由于它們是全局唯一的,因此可以使用(yòng)全局key來(lái)檢索與widget關聯的狀态。
網站(zhàn)建設開(kāi)發|APP設計(jì)開(kāi)發|小(xiǎo)程序建設開(kāi)發