Flutter 中幾乎所有的東西都是一個小部件,當你編寫小部件時,你可以構建一個佈局。例如,您可以在列小部件中新增多個小部件以建立垂直佈局。隨著您繼續新增更多小部件,您的 Flutter 應用程式佈局將變得越複雜。
在本文中,我將介紹一些在佈局 Flutter 應用程式時要實施的最佳實踐。
在 Flutter 中使用 SizedBox 代替 Container
有許多使用情況下,你需要使用佔位符。讓我們看一下下面的例子:
return _isLoaded ? Container() : YourAwesomeWidget();
Container
是一個很棒的小部件,您將在 Flutter 中廣泛使用它。 Container()
擴充套件以適應父級提供的約束,並且不是 const
建構函式。
另一方面,SizedBox
是一個建構函式,建立一個固定尺寸的盒子。寬度和高度引數可以為空,以表示盒子的尺寸不應受到相應維度的限制。
因此,當我們實現佔位符時,應該使用 SizedBox
而不是 Container
。
return _isLoaded ? SizedBox() : YourAwesomeWidget();
使用 if 條件而不是三元運算子語法
在佈局 Flutter 應用程式時,通常需要有條件地渲染不同的小部件。您可能需要根據平臺生成一個小部件,例如:
Row(
children: [
Text("Majid"),
Platform.isAndroid ? Text("Android") : SizeBox(),
Platform.isIOS ? Text("iOS") : SizeBox(),
]
);
在這種情況下,您可以刪除三元運算子並利用 Dart 的內建語法在陣列中新增 if
語句。
Row(
children: [
Text("Majid"),
if (Platform.isAndroid) Text("Android"),
if (Platform.isIOS) Text("iOS"),
]
);
您還可以使用擴充套件運算子擴充套件此功能,並根據需要載入多個小部件。
Row(
children: [
Text("Majid"),
if (Platform.isAndroid) Text("Android"),
if (Platform.isIOS) ...[
Text("iOS_1")
Text("iOS_2")
],
]
);
考慮 Flutter 中 build() 方法的成本
Flutter 小部件中的 build
方法可能會在祖先小部件重建小部件時被頻繁呼叫。避免在 build()
方法中重複和昂貴的工作很重要。
例如,當您使用方法而不是在應用程式中建立小部件時。讓我詳細說明:
class MyAwesomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildHeaderWidget(),
_buildBodyWidget(context),
_buildFooterWidget(),
],
),
);
}
Widget _buildHeaderWidget() {
return Padding(
padding: const EdgeInsets.all(10.0),
child: FlutterLogo(
size: 50.0,
),
);
}
Widget _buildBodyWidget(BuildContext context) {
return Expanded(
child: Container(
child: Center(
child: Text(
'Majid Hajian, Flutter GDE',
),
),
),
);
}
Widget _buildFooterWidget() {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Text('Footer'),
);
}
}
這種方法的缺點是,當 MyAwesomeWidget
需要再次重建時--這可能會經常發生--所有在方法中建立的widget也會被重建,導致浪費CPU週期,也可能浪費記憶體。
因此,最好通過以下方式將這些方法轉換為 StatelessWidgets
:
class MyAwesomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisSize: MainAxisSize.min,
children: [
HeaderWidget(),
BodyWidget(),
FooterWidget(),
],
),
);
}
}
class HeaderWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: FlutterLogo(
size: 50.0,
),
);
}
}
class BodyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Expanded(
child: Container(
child: Center(
child: Text(
'Majid Hajian, Flutter GDE',
),
),
),
);
}
}
class FooterWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Text('Footer'),
);
}
}
所有 StatefulWidgets
或 StatelessWidgets
,基於 key、widget 型別和屬性,都有一個特殊的快取機制,只在必要時重建。我們甚至可以通過新增 const
來優化這些小部件,這將導致我們進入本文的下一部分。
儘可能使用 const 小部件
在Dart中,儘可能使用 const
建構函式是很好的做法,記住編譯器會優化你的程式碼。現在,讓我們回顧一下上面的例子。通過一個直接的步驟,我們可以使 build 方法更有效地工作。
class MyAwesomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisSize: MainAxisSize.min,
children: [
const HeaderWidget(),
const BodyWidget(),
const FooterWidget(),
],
),
);
}
}
class HeaderWidget extends StatelessWidget {
const HeaderWidget();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: FlutterLogo(
size: 50.0,
),
);
}
}
class BodyWidget extends StatelessWidget {
const BodyWidget();
@override
Widget build(BuildContext context) {
return Expanded(
child: Container(
child: Center(
child: Text(
'Majid Hajian, Flutter GDE',
),
),
),
);
}
}
class FooterWidget extends StatelessWidget {
const FooterWidget();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10.0),
child: Text('Footer'),
);
}
}
此更改可能看起來很簡單,但它可以幫助我們避免重新構建 const
小部件。
在 ListView 中為長列表編碼 itemExtent
為了理解如何最好地使用 itemExtent
,假設我們有一個有幾千個元素的列表,當一個動作被觸發時,例如點選一個按鈕,我們需要跳到最後一個元素。這時 itemExtent
可以極大地提高 ListView
的佈局效能。
指定 itemExtent
比讓 children元素 確定他們的範圍更有效,因為滾動機器可以使用對children元素範圍的預知來節省工作,如下所示:
class LongListView extends StatelessWidget {
final _scrollController = ScrollController();
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(onPressed:() {
_scrollController.jumpTo(
_scrollController.position.maxScrollExtent,
);
}),
body: ListView(
controller: _scrollController,
children: List.generate(10000, (index) => Text('Index: $index')),
itemExtent: 400,
),
);
}
}
避免使用大Tree
對於何時將小部件拆分為較小的小部件,沒有硬性規定。但是,最好避免使用大樹,因為它有以下好處:
- 促進可重用性
- 提供更簡潔的程式碼
- 增強可讀性
- 啟用封裝
- 提供快取機制
因此,你應該儘可能地將你的程式碼分割成不同的小部件。
瞭解 Flutter 中的約束
每個 Flutter 開發人員都必須知道的 Flutter 佈局的黃金法則是:約束下降,尺寸上升,父級設定位置。
讓我們來分析一下。
小部件從其父級獲得自己的約束。一個約束只是一組四個雙精度:最小和最大寬度,以及最小和最大高度。
然後,小部件會遍歷其自己的子級列表。小部件一個接一個地告訴它的孩子他們的約束是什麼(每個孩子可能不同),然後詢問每個孩子它想要的大小。
接下來,小部件將其子項(水平在 x
軸上,垂直在 y
軸上)一一定位。最後,小部件告訴它的父級它自己的大小(當然是在原始約束範圍內)。
在Flutter中,所有的小部件都是基於父級或它們的盒子約束來渲染自己。這帶來了一些限制。例如,想象一下,你在一個父部件內有一個子部件,你想決定它的大小。這個小元件不能有任何尺寸! 它的尺寸必須在它的父物件所設定的約束範圍內。
與第一個示例類似,小部件無法知道自己在螢幕中的位置,因為這是父小部件的決定。
也就是說,如果一個子部件決定了與它的父部件不同的尺寸,而父部件沒有足夠的資訊來對齊它,那麼子部件的尺寸可能被忽略。
好的,讓我們看看這個。
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MyWidget();
}
}
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: const BoxConstraints(
maxHeight: 400,
minHeight: 100,
minWidth: 100,
maxWidth: 400,
),
child: Container(
color: Colors.green,
),
);
}
}
如果您願意,您可以忽略 ConstrainedBox
並將高度和小部件新增到 Container
中。
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 400,
width: 400,
color: Colors.green,
);
}
}
您可能希望上面的程式碼渲染一個最大高度和寬度為 400
的綠色 Container
。但是,當您執行此程式碼時,您會感到驚訝。
整個螢幕將是純綠色!我不會在這裡深入探討細節,但在構建 Flutter 佈局時,您可能會遇到幾個與此類似的問題。
讓我們看看這裡發生了什麼。在上面的示例中,樹如下所示:
- `MyApp`
- `MyWidget`
- `ConstrainedBox`
- `Container`
約束規則將從父控制元件傳遞給子控制元件,因此子控制元件可以在其父控制元件的給定約束範圍內決定其大小。因此,約束適用。
因此,Flutter 將嚴格約束傳遞給 MyApp()
,然後 MyApp()
將其嚴格約束傳遞給 ConstrainedBox
。然後,ConstrainedBox
被迫忽略它自己的約束並使用它的父級,在這種情況下,它是全屏大小,這就是為什麼你會看到一個全屏的綠色框。
通常,你會發現新增一個 Center
小部件可能會解決這個問題。讓我們試一試吧。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: MyWidget()
);
}
}
瞧!這就對了。它已經修好了!
Center
小部件從 MyApp()
中獲取一個嚴格的約束,並將其轉換為對其子項的鬆散約束,即 ConstrainedBox
。因此,Container
遵循 ConstrainedBox
給出的約束,以便 Container
應用最小和最大大小。
在我們完成本節之前,讓我快速解釋一下什麼是緊約束和鬆約束。
一個嚴格的約束提供了一種可能性——一個精確的尺寸,這意味著它的最大寬度等於它的最小寬度,它的最大高度等於它的最小高度。
如果你轉到 Flutter 的 box.dart
檔案並搜尋 BoxConstraints
建構函式,你會發現以下內容:
BoxConstraints.tight(Size size)
: minWidth = size.width,
maxWidth = size.width,
minHeight = size.height,
maxHeight = size.height;
另一方面,鬆散約束設定最大寬度和高度,但允許小部件儘可能小。它的最小寬度和高度都等於 0
:
BoxConstraints.loose(Size size)
: minWidth = 0.0,
maxWidth = size.width,
minHeight = 0.0,
maxHeight = size.height;
如果你再看上面的例子,它告訴我們 Center
允許綠色 Container
比螢幕更小,但不能更大。當然,Center
通過將鬆散的約束傳遞給 Container
來做到這一點。
結束
在這篇文章中,我提到了一些你在開始構建Flutter應用程式時應該實施的許多最佳實踐。然而,還有更多的--更高階的--實踐需要考慮,我建議你去看看Flutter的詳盡文件。編碼愉快。