天天看點

flutter系列之:widgets,構成flutter的基石簡介StatelessWidget和StatefulWidgetStatelessWidget詳解StatefulWidget詳解總結

簡介

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到底是怎麼使用的。

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

flutter系列之:widgets,構成flutter的基石簡介StatelessWidget和StatefulWidgetStatelessWidget詳解StatefulWidget詳解總結

這個界面可以分為兩部分,上面的一般稱之為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中可以包含一些可變的屬性,這些屬性可以跟使用者的操作進行互動,進而完成一些比較複雜的功能。

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

flutter系列之:widgets,構成flutter的基石簡介StatelessWidget和StatefulWidgetStatelessWidget詳解StatefulWidget詳解總結

這是一個很明顯的和使用者互動的行為。這裡我們就可以用到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的簡單使用情況。後續我們将會這些元件進行深入,敬請期待。