想入坑flutter的同學(xué),都很擔(dān)心嵌套地獄,但我實際使用flutter差不多1個月,發(fā)現(xiàn)如果用的好,是可以避免嵌套地獄的,我總結(jié)為5種方法。
方法一:適當(dāng)使用“高階組件”
flutter官方文檔沒有“高階組件”的概念,我這里說的“高階組件”指的是由基礎(chǔ)組件搭建的內(nèi)置組件,如果純粹用基礎(chǔ)組件搭建UI,那嵌套層次必然很多,我們應(yīng)該了解內(nèi)置的各類“高階組件”,而且不要害怕“高階組件”不夠靈活,要知道,組件的child屬性、title屬性、leading屬性等都可以賦值任何其他組件,“高階組件”一樣可以擴(kuò)展。
舉例1: 使用ListTile代替Row+Container
ListTile其實是一個三欄布局組件,如果你有三欄或者兩欄的布局需求,而且是兩頭寬度固定,中間一欄寬度自適應(yīng),都可以直接用它實現(xiàn),而不必要自己用Row+Flex+Container實現(xiàn)。
return ListTile(
leading: CircleAvatar(
//頭像半徑
radius: 25,
//頭像圖片 -> NetworkImage網(wǎng)絡(luò)圖片,AssetImage項目資源包圖片, FileImage本地存儲圖片
backgroundImage: NetworkImage('${mModel.headurl}'),
),
title: Text(
'${mModel.nick}',
style: TextStyle(letterSpacing: 0, color: Colors.black, fontSize: 14),
),
subtitle: Text(
'${mModel.decs}',
style: TextStyle(
letterSpacing: 0, color: Color(0xff666666), fontSize: 10),
),
trailing: mFollowBtnWidget(mModel, i - 1),
);
舉例2: 使用RichText代替Text
import '../constant.dart';
RichText(
text: TextSpan(
text: '這是標(biāo)題',
style: Constant.SUB_TITLE
children: <TextSpan>[
TextSpan(
text: '這是鏈接',
style: Constant.LINK,
TextSpan(
text: '這里淡化',
style: Constant.MUTE_TEXT,
],
),
)
舉例3: 使用SliverAppBar代替AppBar
SliverAppBar是一個頂部帶banner圖并且banner圖可以滑動收起的AppBar,如果自己實現(xiàn)類似功能,就要寫很多嵌套和滾動事件。它的使用方法可以去官方文檔查看。
總之,對應(yīng)官方提供的組件,我們要了解透徹,不要寫了半天代碼,原來寫的是官方都實現(xiàn)了的組件,浪費了時間不說,還增加了嵌套層次。
方法二:封裝自定義組件
封裝的方法在大多數(shù)語言和框架里都有,相信大家都知道,在這里提幾個注意的點:
適合封裝的組件:
- 頂部的AppBar
- 點贊按鈕
- 分享按鈕和分享按鈕組(比如微信分享、qq分享)
- 個人頭像
- 常用的主按鈕、默認(rèn)按鈕(類似vue的vant組件庫的按鈕)
- Loading加載動畫
- Dialog對話窗
- 常用的帶顏色和大小屬性的文本
恰當(dāng)使用全局常量:
// file: constant.dart
class Constant {
static const COLOR_TITLE = Color(0xFF202020);
static const COLOR_SUBTITLE = Color(0xFF88888A);
static const COLOR_MUTE = Color(0xccccccff);
static const COLOR_WARN = Color(0xFFF59A23);
static const COLOR_ERROR = Color(0xFFFA3651);
static const COLOR_PRIMARY = Color(0xFFA33028);
static const COLOR_LINK = Color(0xCC61ADF3);
static sonst MAIN_TITLE = TextStyle(fontSize: 16, color: COLOR_TITLE);
static sonst SUB_TITLE = TextStyle(fontSize: 12, color: COLOR_SUBTITLE);
static sonst LINK = TextStyle(fontSize: 13, color: COLOR_LINK);
}
使用:
import '../constant.dart';
.....
Container(
margin: EdgeInsets.only(top: 3),
child: Text(
mCneterItem[0].hotdiscuss,
style: Constant.LINK,
)
方法三:使用SizedBox代替Container的margin,Spacer代替Flex
如果我們使用Container僅僅是為了使用的它的margin屬性,可以適當(dāng)?shù)氖褂肧izedBox和Spacer代替。
例如:
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 5),
Input(
...
),
SizedBox(height: 5),
Input(
...
),
),
],
)
Spacer其實就是包裝了一個 Expanded 的 SizedBox. 我們可以通過它靈活控制 Row/Column
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
color: Colors.blue,
margin: EdgeInsets.symmetric(horizontal: 5),
height: 50,
width: 50,
),
Spacer(flex: 2), // 彈性系數(shù)為2
Container(
color: Colors.blue,
height: 50,
margin: EdgeInsets.symmetric(horizontal: 5),
width: 50,
),
Spacer(), // 彈性系數(shù)默認(rèn)為1
Container(
color: Colors.blue,
margin: EdgeInsets.symmetric(horizontal: 5),
height: 50,
width: 50,
),
],
),
)
方法四:使用Map代替if else
有時我們有通過if else來返回不同的組件嵌套,而里面僅有一部分不同,可以用Map或者三元語法返回一個變量,在組件嵌套里面直接使用變量就可以了。
實例:
// 優(yōu)化前的代碼:
if(level == 'level1') {
return Container(
width: (MediaQuery.of(context).size.width - 75) / 4,
height: (MediaQuery.of(context).size.width - 75) / 4,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
image: DecorationImage(
image: NetworkImage('https://hrlweibo-1259131655.cos.ap-beijing.myqcloud.com/pic1.jpg'),
fit: BoxFit.cover,
)),
)
} else {
return Container(
width: (MediaQuery.of(context).size.width - 75) / 4,
height: (MediaQuery.of(context).size.width - 75) / 4,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
image: DecorationImage(
image: NetworkImage('https://hrlweibo-1259131655.cos.ap-beijing.myqcloud.com/pic2.jpg'),
fit: BoxFit.cover,
)),
)
}
// 優(yōu)化后的代碼
urls = {
'level1': 'https://hrlweibo-1259131655.cos.ap-beijing.myqcloud.com/pic1.jpg',
'level2': 'https://hrlweibo-1259131655.cos.ap-beijing.myqcloud.com/pic1.jpg'
}
return Container(
width: (MediaQuery.of(context).size.width - 75) / 4,
height: (MediaQuery.of(context).size.width - 75) / 4,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
image: DecorationImage(
image: NetworkImage(urls[level]),
fit: BoxFit.cover,
)),
),
大家可能覺得自己不會寫優(yōu)化前這么垃圾的代碼,但我在github上就經(jīng)??吹竭@樣的代碼,可能是有點懶,也可能是沒注意。說到這里,提醒大家要多review一下代碼,看是否可以優(yōu)化。
方法五:使用擴(kuò)展函數(shù)
關(guān)于擴(kuò)展函數(shù), 這篇文章寫的很好:《Flutter嵌套地獄!看完此文你就掌握了解決方案》,這個方法是解決嵌套地獄的終極方法,不過要適當(dāng)?shù)挠?,不要濫用,否則也會影響代碼閱讀,建議主要使用樣式組件的擴(kuò)展函數(shù),比如顏色、大小、paddig、是否可見,居中、居右,ClipOval,borderRadius等。文章的作者還寫了一個擴(kuò)展函數(shù)庫給我們使用,里面涵蓋了通常使用的擴(kuò)展函數(shù),我們可以在他的基礎(chǔ)上加自己喜歡的擴(kuò)展函數(shù)。
總結(jié):
有了以上5種方法,我感覺flutter好香!媽媽再也不用擔(dān)心我的嵌套地獄問題了!當(dāng)然,以上每個方法都有它自己的適用場景,要結(jié)合使用。還有,我們在開發(fā)過程中要學(xué)會重構(gòu)和去除重復(fù)代碼,方法二和方法四實際上是去除重復(fù)代碼,從而避免了嵌套。