“本文主要介紹Flutter一切皆widget但是不要将所有東西放入一個widget
作為 Flutter 開發人員,我相信您在您的開發生活中至少聽說過這句流行的句子:“**一切都是widget”。這是 Flutter 的口頭禅,它揭示了這個非常好的 SDK 的内在力量!
當我們在widgets目錄中,我們可以看到很多小部件,如
Padding
,
Align
,
SizedBox
,等。我們通過組合它們來建立其他小部件,我發現這種方法可擴充、強大且易于了解。
但是當我閱讀 一些我在網際網路上找到的或由新采用者編寫的源代碼時,有一件讓我震驚的事情:擁有大量build**`**方法的趨勢,執行個體化很多小部件!我發現這很難閱讀、了解和維護。
作為軟體開發人員,我們必須記住,軟體的真實生活從第一次釋出給使用者開始。該軟體的源代碼将由其他人(包括您未來的您)閱讀和維護,這就是為什麼保持我們的代碼簡單、易于閱讀和了解非常重要。
“小部件中的一切”的示例可以在Flutter 文檔本身中找到。本教程的目标是展示如何建構此布局:
image-20210822082626144
最終代碼達到了它的目的:展示如何簡單地建立上述布局。正如我們所見,甚至還有一些變量和方法可以為布局的各個部分提供語義。這是一個很好的觀點,因為它使代碼更容易了解。
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
Widget titleSection = Container(
padding: const EdgeInsets.all(32),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
'Oeschinen Lake Campground',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
),
Text(
'Kandersteg, Switzerland',
style: TextStyle(
color: Colors.grey[500],
),
),
],
),
),
Icon(
Icons.star,
color: Colors.red[500],
),
Text('41'),
],
),
);
Color color = Theme.of(context).primaryColor;
Widget buttonSection = Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildButtonColumn(color, Icons.call, 'CALL'),
_buildButtonColumn(color, Icons.near_me, 'ROUTE'),
_buildButtonColumn(color, Icons.share, 'SHARE'),
],
),
);
Widget textSection = Container(
padding: const EdgeInsets.all(32),
child: Text(
'Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese '
'Alps. Situated 1,578 meters above sea level, it is one of the '
'larger Alpine Lakes. A gondola ride from Kandersteg, followed by a '
'half-hour walk through pastures and pine forest, leads you to the '
'lake, which warms to 20 degrees Celsius in the summer. Activities '
'enjoyed here include rowing, and riding the summer toboggan run.',
softWrap: true,
),
);
return MaterialApp(
title: 'Flutter layout demo',
home: Scaffold(
appBar: AppBar(
title: Text('Flutter layout demo'),
),
body: ListView(
children: [
Image.asset(
'images/lake.jpg',
width: 600,
height: 240,
fit: BoxFit.cover,
),
titleSection,
buttonSection,
textSection,
],
),
),
);
}
Column _buildButtonColumn(Color color, IconData icon, String label) {
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color),
Container(
margin: const EdgeInsets.only(top: 8),
child: Text(
label,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: color,
),
),
),
],
);
}
}
複制
事實上,情況可能更糟。這是我不喜歡的這段代碼的典型多合一小部件版本:。
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
Color color = Theme.of(context).primaryColor;
return MaterialApp(
title: 'Flutter layout demo',
home: Scaffold(
appBar: AppBar(
title: Text('Flutter layout demo'),
),
body: ListView(
children: [
Image.asset(
'images/lake.jpg',
width: 600,
height: 240,
fit: BoxFit.cover,
),
Container(
padding: const EdgeInsets.all(32),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
'Oeschinen Lake Campground',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
),
Text(
'Kandersteg, Switzerland',
style: TextStyle(
color: Colors.grey[500],
),
),
],
),
),
Icon(
Icons.star,
color: Colors.red[500],
),
Text('41'),
],
),
),
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.call, color: color),
Container(
margin: const EdgeInsets.only(top: 8),
child: Text(
'CALL',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: color,
),
),
),
],
),
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.near_me, color: color),
Container(
margin: const EdgeInsets.only(top: 8),
child: Text(
'ROUTE',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: color,
),
),
),
],
),
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.share, color: color),
Container(
margin: const EdgeInsets.only(top: 8),
child: Text(
'SHARE',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: color,
),
),
),
],
),
],
),
),
Container(
padding: const EdgeInsets.all(32),
child: Text(
'Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese '
'Alps. Situated 1,578 meters above sea level, it is one of the '
'larger Alpine Lakes. A gondola ride from Kandersteg, followed by a '
'half-hour walk through pastures and pine forest, leads you to the '
'lake, which warms to 20 degrees Celsius in the summer. Activities '
'enjoyed here include rowing, and riding the summer toboggan run.',
softWrap: true,
),
),
],
),
),
);
}
}
複制
在第二個版本中,我們有一個大
build
方法的小部件,它很難閱讀、了解和維護。
現在讓我們看看我将如何重寫它:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter layout demo',
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter layout demo'),
),
body: ListView(
children: [
const _Header(),
const _SubHeader(),
const _Buttons(),
const _Description(),
],
),
);
}
}
class _Header extends StatelessWidget {
const _Header({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Image.asset(
'images/lake.jpg',
width: 600,
height: 240,
fit: BoxFit.cover,
);
}
}
class _SubHeader extends StatelessWidget {
const _SubHeader({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(32),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const _Title(),
const _SubTitle(),
],
),
),
const _Likes(),
],
),
);
}
}
class _Title extends StatelessWidget {
const _Title({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
'Oeschinen Lake Campground',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
);
}
}
class _SubTitle extends StatelessWidget {
const _SubTitle({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Text(
'Kandersteg, Switzerland',
style: TextStyle(
color: Colors.grey[500],
),
);
}
}
class _Likes extends StatelessWidget {
const _Likes({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Icon(
Icons.star,
color: Colors.red[500],
),
Text('41'),
],
);
}
}
class _Buttons extends StatelessWidget {
const _Buttons({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const _Button(icon: Icons.call, text: 'CALL'),
const _Button(icon: Icons.share, text: 'ROUTE'),
const _Button(icon: Icons.share, text: 'SHARE'),
],
),
);
}
}
class _Button extends StatelessWidget {
const _Button({
Key key,
@required this.icon,
@required this.text,
}) : assert(icon != null),
assert(text != null),
super(key: key);
final IconData icon;
final String text;
@override
Widget build(BuildContext context) {
Color color = Theme.of(context).primaryColor;
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: color),
Container(
margin: const EdgeInsets.only(top: 8),
child: Text(
text,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: color,
),
),
),
],
);
}
}
class _Description extends StatelessWidget {
const _Description({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(32),
child: Text(
'Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese '
'Alps. Situated 1,578 meters above sea level, it is one of the '
'larger Alpine Lakes. A gondola ride from Kandersteg, followed by a '
'half-hour walk through pastures and pine forest, leads you to the '
'lake, which warms to 20 degrees Celsius in the summer. Activities '
'enjoyed here include rowing, and riding the summer toboggan run.',
softWrap: true,
),
);
}
}
複制
你不覺得這更易讀嗎?
有什麼好處?
我了解為什麼教程不經常這樣做:它需要更多行(在我的示例中為 100 行),人們可能想知道為什麼我們要建立這麼多其他小部件。由于教程旨在專注于一個概念,是以這樣編寫它們可能會适得其反。但結果是,新采用者可能傾向于在他們的
build
方法中放置一個大的小部件樹。讓我們看看為布局的每個部分都有一個獨特的小部件有什麼好處:
可讀性
我們為布局的每個語義部分建立一個小部件。是以,每個小部件都有一個較小的
build
方法。它更易于閱讀,因為您無需滾動即可到達小部件的末尾。
可了解性
每個小部件都有一個與其角色比對的名稱,這稱為語義命名。通過這樣做,當我們閱讀代碼時,更容易在我們的腦海中映射代碼的哪一部分與我們在應用程式上看到的内容相比對。我在這裡看到了可了解性方面的兩個改進:\1. 當我們閱讀其他地方引用的此類小部件時,我們幾乎知道它的作用,而無需檢視其實作。2.在閱讀帶有語義命名的小部件的建構方法之前,我們已經對其内容有一個大緻的了解。
可維護性
如果您必須更換一個元件或更改一個部件,它隻會在一個地方,與其他小部件的其餘部分分開。多虧了這種做法,它更不容易出錯,因為每個小部件的角色都得到了很好的定義。在您的應用程式甚至另一個應用程式中的另一個頁面中共享布局的一部分也将更加容易。
Performances
前面的所有原因應該足以讓您采用這種方式來建立 Flutter 應用程式,但是這樣做還有一個好處:我們提高了應用程式的性能,因為每個小部件都可以與其他小部件分開重建(事實并非如此如果我們使用方法來分隔我們的布局部分)。例如,假設我們必須在單擊它時增加紅星旁邊的數字。在這個版本中,我們可以制作
_Likes
一個
StatefulWidget
并處理這裡的增量。當使用者點選星星時,隻有
_Likes
小部件會被重建。在第一個版本中,
MyApp
如果我們将其設為
StatefulWidget
.
Flutter 文檔中也解釋了這種最佳實踐:
“當在狀态上調用時,所有後代小部件都将重建。是以,将
setState()
setState()
調用本地化到 UI 實際需要更改的子樹部分。如果更改包含在樹的一小部分,請避免在樹的高處調用 setState()。
”
另一個優點是能夠
const
更頻繁地使用關鍵字。然後可以緩存和重新使用小部件。正如Flutter 文檔所述:
“重用小部件比建立新的(但配置相同的)小部件要高效得多。
”
如何提高工作效率?
如您所見,通過為布局的每個語義部分建立一個小部件,我們編寫了更多代碼。我們可以在 Visual Studio Code 中使用Dart擴充提供的
stless
和
stful
片段,
為了我自己的需要,我建立了新的片段,稱為
sless
和
sful
,這樣我的工作效率比以往任何時候都高。如果您希望在 Visual Studio Code 中使用它們,則必須遵循此文檔并添加以下内容:
{
"Flutter stateless widget": {
"scope": "dart",
"prefix": "sless",
"description": "Insert a StatelessWidget",
"body": [
"class $1 extends StatelessWidget {",
" const $1({",
" Key key,",
" }) : super(key: key);",
"",
" @override",
" Widget build(BuildContext context) {",
" return Container(",
" $2",
" );",
" }",
"}"
]
},
"Flutter stateful widget": {
"scope": "dart",
"prefix": "sful",
"description": "Insert a StatefulWidget",
"body": [
"class $1 extends StatefulWidget {",
" const $1({",
" Key key,",
" }) : super(key: key);",
"",
" @override",
" _$1State createState() => _$1State();",
"}",
"",
"class _$1State extends State<$1> {",
" @override",
" Widget build(BuildContext context) {",
" return Container(",
" $2",
" );",
" }",
"}"
]
},
}
複制
結論
我相信這是編寫 Flutter 應用程式的好方法,我希望你也相信。如果不是這樣,我對你的意見很感興趣!
從現在開始,記住這句話:“Everything’s a widget but don’t put everything in one widget!”。