簡介
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到底是怎麼使用的。
假如我們想建構一個下面樣式的界面,該怎麼做呢?
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsQTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SO4ITMyM2MzAzMmR2YxYTOyYzX5ETO0EjMxMzLcVDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjL0M3Lc9CX6MHc0RHaiojIsJye.png)
這個界面可以分為兩部分,上面的一般稱之為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的簡單使用情況。後續我們将會這些元件進行深入,敬請期待。