做自(zì)由與創造的先行者

Flutter Widget框架概述

Flutter開(kāi)發手冊

介紹

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 products;

// 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 _shoppingCart = new 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)發
下(xià)一篇:Flutter Widget目錄
上(shàng)一篇:Flutter 編寫第一個Flutter應用(yòng)