【Flutter 極速指南】部件篇

Flutter 提供了大量的 Widget,種類繁多,就是布局組件就多達 30 個,我們就不一一介紹,只介紹比較常用的一些。在介紹組件之前,我們先了解一下 Flutter 部件的不同種類,在 Flutter 的官方文檔中,對所有部件的分類比較混亂,不同類型的部件之間有很多交叉現(xiàn)象,例如:Container 既屬于基礎部件又屬于布局類型的部件;Image 部件同時在基礎類型和 Material 類型中,并且還專門為資源類型的部件(Icon、RowImage 等)定義了一個類別。個人感覺它的分類比較混亂,因此我試著重新梳理一下 Flutter 的常用部件類型,以便于學習使用。

  • Basics
  • Forms and Inputs
  • Layout
  • Dialogs and Alerts
  • Interaction Models
  • Animation and Motion
  • Painting and effects
Flutter Widgets

StatelessWidget vs StatefulWidget

在介紹各種不同的部件之前,我們先了解一下什么是無狀態(tài)部件有狀態(tài)部件。無狀態(tài)部件,它內部的狀態(tài)是來自父組件,并且使用 final 類型的變量來存儲,當部件被創(chuàng)建的時候,UI 是由這些不可改變的數(shù)據(jù)構建的,他們都是最終態(tài)。那么有狀態(tài)部件剛好和無狀態(tài)部件相反,它內部的狀態(tài)在部件的整個生命周期是可以發(fā)生變化的。

Flutter 詭異的兩個基類,這是在做動靜分離嗎?性能的考慮嗎?個人覺得大可不必,在實際項目中絕大多數(shù) UI 都需要

常用 Widgets

  • Basics:
    • Text & Image & Icon
    • ListView & ListTile
    • Buttons
    • Scaffold & Appbar & Drawer & PopupMenuButton
  • Forms and Inputs:
    • Form
    • TextFormField & TextField
    • Checkbox & Radio & Switch
    • Date & Time Pickers
  • Layout:
    • Container & Padding & Center & Positioned
    • Column & Row & Stack & GridView
  • Dialogs and Alerts:
    • AlertDialog
    • SnackBar
    • BottomSheet
  • Interaction Models
  • Animation and Motion
  • Painting and effects

常用基礎部件(Basics)

Text & Icon & Image

Text & Image & Icon 是 UI 中最基礎的信息顯示部件,這些部件就不做詳細的介紹,因為作為前端工程師,用的最多最熟悉的也就是這些部件,直接看代碼:

Column(
    children: <Widget>[
        Text('Text Widget'),
        Image.network('https://metaimg.baichanghui.com/appdownload.jpg'),
        Icon(Icons.help),
    ]
)

ListView & ListTile

ListView & ListTile 這類組件,主要是解決大量列表數(shù)據(jù)的展示和交互。ListView 為列表的主體部分,它可以綁定數(shù)據(jù)集合,并且滾動時可以和遠程數(shù)據(jù)進行交互,當然也可以對列表項的數(shù)據(jù)進行不同的布局展示。ListTile 為列表項的布局,它是基于 Material Design 設計出來的一種特殊布局的部件。


List<String> listData = ['a', 'b', 'c', 'd'];

//...

ListView.builder(
    itemCount: 4,
    itemBuilder: (context, index) {
        String item = listData[index];

        return ListTile(
            title: Text(item),
            subtitle: Text(index.toString())
        );
    }
);

Buttons

在 Flutter 部件中有各種不同風格的按鈕,例如:IconButton、FlatButton、RaisedButton、RawMaterialButton 等等,其實它們僅僅是風格不同,用處沒有太大的差別:

Column(
    children: <Widget>[
        FlatButton(
            color: Colors.black,
            textColor: Colors.white,
            child: Text('Button'),
            onPressed: () { },
        ),
        IconButton(
            icon: Icon(Icons.help),
            iconSize: 20,
            color: Colors.black,
            onPressed: () { },
        )
    ]
)

他們在初始化是,有一個必須要初始化的屬性 onPressed

Scaffold & Appbar & Drawer & PopupMenuButton

這一組部件也是基于 Material Design 設計出來的,它們主要解決 App 的導航、輔助功能等問題,當然它也會幫你適配頁面在不同設備下的表現(xiàn)。下面的代碼中,會出現(xiàn)上述所有組件,讓我們分享一下代碼:

Scaffold(
    appBar: Appbar(
        title: Text('Title'),
        actions: <Widget> [
            PopupMenuButton(
                itemBuilder: (context) {
                    return [
                        PopupMenuItem(
                            value: '',
                            child: Text('Item1')
                        )
                    ];
                }
            )
        ]
    ),
    drawer: Drawer(
        child: Column(),
    ),
    body: Container(),
)
  • Scaffold:Material Design 布局結構的基本實現(xiàn)
    • appbar: Appbar:引用程序欄
      • title:標題
      • actions:操作部件
        • PopupMenuButton:彈出菜單按鈕
          • PopupMenuItem:菜單項
    • drawer: Drawer:側邊抽屜導航
    • body:應用程序主體
Appbar

表單部件(Forms and Inputs)

我們通過表單來收集用戶填寫的相關信息,和 Web 中的表單類似,F(xiàn)lutter 提供了表單中常用的文本輸入、單選、多選和開關等部件,并且它也提供了基本的表單校驗的功能,以及滿足我們通常的業(yè)務。

Form(
    key: _formKey,
    child: Column(
        children: <Widget>[
            TextFormField(
                validator: (value) {
                    if(value.isEmpty) {
                    return 'Please enter some text';
                    }
                },
            ),
            Checkbox(
                value: false,
                onChanged: (value) { },
            ),
            Radio(
                value: false,
                groupValue: 0,
                onChanged: (value) { },
            ),
            Switch(
                value: false,
                onChanged: (value) {},
            ),
            RaisedButton(
                child: Text('Submit'),
                color: Colors.black,
                textColor: Colors.white,
                onPressed: (){
                if(_formKey.currentState.validate()) {
                        Scaffold.of(context).showSnackBar(SnackBar(
                            content: Text('OK'),
                        ));
                    }
                },
            )
        ],
    ),
);

布局部件(Layout)

布局部件在 Flutter 中多達 30 個,個人對這種復雜繁多的設計并不是非常滿意(或許它有自己的好處),但是對于開發(fā)人員來說非常不友好。在布局部件中 Flutter 分兩大類:一類是單個子元素的布局部件;另一類是多個子元素的布局部件。我們簡單介紹幾個較為常用的部件:

  • Single-child layout widgets
    • Container:
    • Padding:
    • Center:
    • Positioned:
  • Multi-child layout widgets
    • Column:
    • Row:
    • Stack:
    • GridView:

Container

Container 類似于 Web 中的 <div>,我們可以給它設置 heightwidthpaddingmargin 等屬性:

Container(
    child: Text('Container'),
    padding: EdgeInsets.all(10),
    margin: EdgeInsets.all(30),
    height: 250,
    width: 200,
    alignment: Alignment.center,
    decoration: BoxDecoration(
        color: Colors.red,
        border: Border.all(
            width: 3, 
            color: Colors.blue
        ),
        borderRadius: BorderRadius.all(
            Radius.circular(20)
        ),
        image: DecorationImage(
            image: NetworkImage('https://metaimg.baichanghui.com/appdownload.jpg')
        )
    ),
    transform: Matrix4.rotationZ(-0.1),
)

Padding

Padding 可以用來給部件設置內邊距,搞不懂 Flutter 的設計思想:

Padding(
    padding: EdgeInsets.all(20),
    child: Container(
        child: Text('Padding'),
        color: Colors.green,
    ),
)

Center

What?

Center(
    child: Text('Center'),
)

Positioned

Positioned 部件可以對它設置相對位置,讓它位于屏幕的不同地方,不過它需要與 Stack 部件配合使用:

Positioned(
    left: 100,
    top: 100,
    child: Container(
        color: Colors.green,
        height: 80,
        width: 80,
    ),
)

Column

Column 是縱向布局部件,可以讓你對多個部件進行縱向布局,在它內部的子部件會沿屏幕的縱向一個一個顯示出來:

Column(
    children: <Widget>[
            Container(
            color: Colors.red,
            height: 100,
            width: 100,
        ),
        Container(
            color: Colors.blue,
            height: 100,
            width: 100,
        ),
        Container(
            color: Colors.green,
            height: 100,
            width: 100,
        ),
    ],
)

Row

Row 剛好和 Column 相反,可以讓你對多個部件進行橫向布局,在它內部的子部件會沿屏幕的橫向一個一個顯示出來:

Row(
    children: <Widget>[
            Container(
            color: Colors.red,
            height: 100,
            width: 100,
        ),
        Container(
            color: Colors.blue,
            height: 100,
            width: 100,
        ),
        Container(
            color: Colors.green,
            height: 100,
            width: 100,
        ),
    ],
)

Stack

Stack 堆疊布局,在 Stack 內部的子部件,會堆疊在一起,我們可以配合 Positioned 部件來設置堆疊于 Stack 內部的位置:

Stack(
    children: <Widget>[
        Container(
            color: Colors.red,
            height: 250,
            width: 250,
        ),
        Container(
            color: Colors.blue,
            height: 150,
            width: 150,
        ),
        Positioned(
            left: 100,
            top: 100,
            child: Container(
                color: Colors.green,
                height: 80,
                width: 80,
            ),
        )
    ],
)

GridView

GridView 珊格布局,它在 Web 的響應式設計中經常使用,他的好處在于,可以適應不同的屏幕:

GridView.count(
    crossAxisCount: 2,
    children: List.generate(20, (index) {
        return Text(index.toString());
    }),
)

提醒彈出框部件(Dialogs and Alerts)

提醒和彈出框部件,App 相比起 Web 更加的豐富多樣,Web 原生提供的也比較少,我們看看 App 中比較常用的幾個:

  • AlertDialog
  • SnackBar
  • BottomSheet

AlertDialog

AlertDialog 類似于瀏覽器中的 alert,只不過它可以自定義一些按鈕來做不同的事情:

FlatButton(
    child: Text('Show Dialog'),
    onPressed: () {
        showDialog(
            context: context,
            builder: (context) => AlertDialog(
                title: Text('Title2'),
                content: Text('Content2'),
                actions: <Widget>[
                    FlatButton(
                        child: Text('OK'),
                        onPressed: () {
                            // do something
                        }
                    )
                ]
            )
        );
    },
)

SnackBar

SnackBar 是類似于很多 UI 框架中的提醒框,它屬于非阻斷性的提醒:

RaisedButton(
    child: Text('Show SnackBar'),
    color: Colors.black,
    textColor: Colors.white,
    onPressed: (){
        Scaffold.of(context).showSnackBar(SnackBar(
            content: Text('Hello!'),
        ));
    },
)

BottomSheet

BottomSheet 是從屏幕底部彈出來的頁面,你可以通過它來做一些類似與 Select 部件的功能,當然它內部的內容是可以自定義的:

RaisedButton(
    child: Text('Show Bottom Sheet'),
    color: Colors.black,
    textColor: Colors.white,
    onPressed: () {
        showBottomSheet(
            context: context,
            builder: (context){
                return Container(
                    width: MediaQuery.of(context).size.width,
                    child: Text('BottomSheet'),
                );
            }
        );
    },
)

代碼:

總結

Interaction Models、Animation and Motion、Painting and effects 這些都屬于比較高級的部件,我們在未來文章中再做介紹。回顧 Flutter 如此繁多的部件,再加上它的有狀態(tài)部件和無狀態(tài)部件之分,學習成本相對來說比較高,每一種部件還有這不同的功能屬性,這種設計的確講究軟件設計的職責單一,但是在復雜的 UI 場景會導致大量的代碼嵌套,代碼的維護成本也會隨之上升,在 Web 的 Table 布局時代就出現(xiàn)過這樣的災難。作為 Web 工程師,可能對 Flutter 這樣的設計非常抱怨,HTML 和 CSS 將組件和樣式分離開來,從學習的角度上看所有組件有著它自己的功能,而樣式只是組件的附加值,從某種角度上來說這是兩個是解藕的,但是 Flutter 部件的學習過程中需要去了解各個部件自己的長相如何設置,在這一點 Web 學習的復雜成本會降低。另外將樣式分離開來后,代碼嵌套也會隨之減少,同樣是 DIV 我們可以給它賦予布局、邊框、背景、陰影、邊距、剪切等樣式,但是 Flutter 部件要實現(xiàn)這些功能需要嵌套多個部件才能完成。個人認為 Flutter 在這方面需要繼續(xù)改進,已迎合現(xiàn)在互聯(lián)網工程師的口味,這樣才能推廣開來。

〖堅持的一俢〗

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 國慶后面兩天在家學習整理了一波flutter,基本把能擼過能看到的代碼都過了一遍,此文篇幅較長,建議保存(star...
    Nealyang閱讀 4,367評論 1 17
  • 以下內容基本翻譯自A Tour of the Flutter Widget Framework,翻譯的可能并不完全...
    INeil閱讀 10,284評論 0 4
  • 本文對Flutter的29種布局控件進行了總結分類,講解一些布局上的優(yōu)化策略,以及面對具體的布局時,如何去選擇控件...
    Q吹個大氣球Q閱讀 10,600評論 15 153
  • 本文對Flutter的29種布局控件進行了總結分類,講解一些布局上的優(yōu)化策略,以及面對具體的布局時,如何去選擇控件...
    chilim閱讀 1,303評論 0 19
  • 湯力水是那么的苦澀,畢竟不是蘇打水。為什么用湯力水來形容友情,因為依米追求了大半輩子的友情了,畢竟自己的性...
    莉娜的靜謐時光閱讀 361評論 2 3