flutter系列之:widgets,構成flutter的基石

簡介

flutter中所有的組件都是由widgets組成的,flutter中有各種各樣的widgets,這些widgets構成了flutter這個大廈。

那麼flutter中的widget有什麼特點呢?我們應該怎麼學習widget呢? 一起來看看吧。

StatelessWidget和StatefulWidget

實時上,flutter中的widgets是受到React的啟發來實現的。flutter中的widget可以分為StatefulWidget和StatelessWidget,分別代表有狀態的Widgets和無狀態的Widgets。

有狀態和無狀態,大家聽起來是不是很熟悉,我們在應用程式中也經常會用到有狀態的Bean和無狀態的Bean。他們的原理和flutter的兩類Widget其實是差不多的。

StatelessWidget因為是無狀態的,所以它只會根據初始傳入的配置資訊來構建Widget,因為Widget是不可變的,所以StatelessWidget創建出來就不會再變化。

對於StatefulWidget來說,除了根據初始傳入的配置來創建Widget之外,它內部還包含了一個State。這個State用來和用戶的行為進行交互,從而對State中的值進行修改。當State被修改後,和其綁定的Widget會根據特定的演算法進行比較,看是否需要進行重繪,從而將用戶的交互反映在用戶介面上。

widget提供了一個build方法,build方法返回一個Widget,用於生成最後的RenderObject對象。

build方法的定義如下:

Widget build(BuildContext context);

但事實上,只有StatelessWidget中才有build方法。那麼StatefulWidget為什麼沒有build方法呢?

StatefulWidget雖然沒有build方法,但是它有一個createState方法用來創建跟它關聯的State:

State createState(); 

而這個build方法是放在State裡面的。

StatelessWidget詳解

什麼樣的組件可以做成無狀態的組件呢?那些不需要和用戶交互的組件就可以。

flutter中的無狀態Widget都有那些呢?

這裡列出幾個flutter中基本和經常使用的StatelessWidget:

Text: 用來創建文本。

Row和Column: 表示的是縱向擴展和橫向擴展的行和列。Row和Column是基於web的flexbox布局。

還有一個基於web的絕對定位的布局叫做Positioned,Positioned通常是和Stack一起使用的。

Stack就是一個棧的結構,在Stack中你可以將一個widget放在另外一個widget的上面。

Positioned用在Stack中,可以相對於top, right, bottom或者left邊界進行相對定位,非常好用。

另外一個常用的組件就是Container,它表示的是一個長方形的元素,Container可以用BoxDecoration來修飾,用來表示背景、邊框和陰影等。

Container還可以包含margins,padding和尺寸限制等特性。

接下來我們來通過一個具體的例子來說明StatelessWidget到底是怎麼使用的。

假如我們想構建一個下面樣式的介面,該怎麼做呢?

這個介面可以分為兩部分,上面的一般稱之為appBar,下面的一般叫做content。

appBar按列的布局又可以分為三部分,第一部分是一個IconButton表示導航菜單,第二部分是一個Text表示頁面標題,第三部分也是一個IconButton表示搜索按鈕。 這三部分按照Row來進行組合.

那麼按照Flutter的widget的構建原則,我們可以把appBar構建成一個Widget。因為這個Widget的行為只跟初始化狀態有關,所以可以將其設置成為StatelessWidget:

class MyAppBar extends StatelessWidget {
  const MyAppBar({required this.title, Key? key}) : super(key: key);

  final Widget title;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 56.0, // bar的高度
      padding: const EdgeInsets.symmetric(horizontal: 8.0),
      decoration: BoxDecoration(color: Colors.blue[500]),
      // 按Row來進行布局
      child: Row(
        children: [
          const IconButton(
            icon: Icon(Icons.menu),
            tooltip: '導航菜單',
            onPressed: null, // 目前不可點擊
          ),
          // Expanded組件,用於填充所有可用的空間
          Expanded(
            child: title,
          ),
          const IconButton(
            icon: Icon(Icons.search),
            tooltip: '搜索',
            onPressed: null,
          ),
        ],
      ),
    );
  }
}

上面的程式碼中,我們把Row包含在一個Container中,然後將這個Container返回作為appBar的實際內容。

UI下面的部分比較簡單, 就是一個居中的Text。我們將其合和appBar合併起來,放在一個Column中,按行進行分割:

class MyScaffold extends StatelessWidget {
  const MyScaffold({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Material(
      // 構建一個兩行的column,一個是bar, 一個是具體的內容
      child: Column(
        children: [
          MyAppBar(
            title: Text(
              'StatelessWidget',
              textAlign: TextAlign.center,
              style: Theme.of(context)
                  .primaryTextTheme
                  .headline6,
            ),
          ),
          const Expanded(
            child: Center(
              child: Text('這是一個Text組件!'),
            ),
          ),
        ],
      ),
    );
  }
}

它也是一個StatelessWidget,在build方法中返回了Material這個widget。

然後,我們將MyScaffold包裝在一個MaterialApp中,作為最後返回的MyApp:

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // 這是應用程式的根widget
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '第一個StatelessWidget',
      theme: ThemeData(
        primarySwatch: Colors.green,
      ),
      home: const SafeArea(
        child: MyScaffold(),
      ),
    );
  }
}

最後在runApp方法中運行MyApp即可:

void main() {
  runApp(const MyApp());
}

StatefulWidget詳解

上面我們講解了一個如何使用StatelessWidget來構造一個app的方法。大家應該對基本的流程有所熟悉。

這裡要注意的是,StatelessWidget並不是說widget中不能存儲任何變數,如上面的例子所示,MyAppBar這個StatelessWidget其實是包含一個title的Widget,但是這個widget是final的,也就是說定義過一次之後就不能夠再變化,所以叫做StatelessWidget。

StatefulWidget和StatelessWidget不同的地方在於,StatefulWidget可以和一個State進行關聯。State中可以包含一些可變的屬性,這些屬性可以跟用戶的操作進行交互,從而完成一些比較複雜的功能。

假如我們需要下面的一個介面,介面右下方有一個按鈕,點擊一次,可以將中間的數字加一。

這是一個很明顯的和用戶交互的行為。這裡我們就可以用到StatefulWidget。

這裡我們創建一個MyHomePage的StatefulWidget,並創建一個_MyHomePageState的state和其進行關聯:

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

注意,可變屬性是存在和StatefulWidget關聯的state中的,而不是StatefulWidget本身。

所以我們需要在_MyHomePageState中定義一個int的_counter變數,用來存儲用戶點擊次數。然後定義一個_incrementCounter用來對_counter進行累加。

在_incrementCounter需要調用setState方法用來對State的狀態進行刷新。

  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

然後在State中的build方法中就可以返回對應UI的Widget了。這裡我們使用Scaffold組件,這個組件自帶了appBar和body:

Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              '按鈕被點了:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
            const Text(
              '次',
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }

這裡body中我們選擇使用Center組件用來展示內容資訊。而浮動的按鈕則使用FloatingActionButton,它的onPressed方法會觸發我們前面寫的_incrementCounter方法,用來將_counter加一。

最後將我們構建的組件傳入MaterialApp中,如下所示:

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // 根Widget
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '第一個StatefulWidget',
      theme: ThemeData(
        primarySwatch: Colors.green,
      ),
      home: const MyHomePage(title: 'StatefulWidget'),
    );
  }
}

總結

以上,我們簡單的講解了StatelessWidget和StatefulWidget的簡單使用情況。後續我們將會這些組件進行深入,敬請期待。

本文的程式碼可以參考://github.com/ddean2009/learn-flutter, 覺得好的話,請點個贊,謝謝。

更多內容請參考 //www.flydean.com/02-flutter-widget/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!