實(shí)戰(zhàn):首頁布局拆解與組件組合指南)
把Flutter鴻蒙開發(fā)的系列寫到第五篇前幾篇把環(huán)境搭建、工程骨架和狀態(tài)管理聊完總算可以動(dòng)手寫一個(gè)真實(shí)的頁面了。這篇文章我準(zhǔn)備聚焦在首頁基礎(chǔ)布局上從拿到設(shè)計(jì)稿怎么拆結(jié)構(gòu)開始逐個(gè)講透Flutter布局里最常用的Flex、Column、Row、ListView和GridView最后落到調(diào)試報(bào)錯(cuò)和組件拆分。首頁是每個(gè)App信息密度最高的一屏通常同時(shí)包含搜索欄、輪播、功能入口、內(nèi)容推薦流如果一開始沒把布局分層想清楚后面的維護(hù)成本會(huì)直線上升。這篇內(nèi)容適合剛把環(huán)境跑通、準(zhǔn)備寫第一個(gè)完整頁面的新手也適合已經(jīng)在寫業(yè)務(wù)但覺得布局代碼一團(tuán)亂的開發(fā)者對(duì)照參考。1. 首頁布局的拆解思路把設(shè)計(jì)稿先翻譯成組件樹1.1 方塊圖法先分層后編碼拿到一張首頁設(shè)計(jì)稿我的第一個(gè)動(dòng)作基本不是打開編輯器而是拿張草稿紙或者空白記事本把頁面畫成幾層方塊。這一步的目的不是追求畫得好看而是逼自己回答三個(gè)問題這一屏從上到下分幾個(gè)區(qū)域每個(gè)區(qū)域內(nèi)部是橫向排列還是縱向排列哪些區(qū)域是固定高度、哪些區(qū)域會(huì)隨內(nèi)容變長舉個(gè)例子一個(gè)很典型的電商首頁結(jié)構(gòu)大概是這樣的頂部是搜索欄和用戶信息中間是一張輪播圖下面排8個(gè)功能入口兩行四個(gè)再往下就是推薦商品流。把這個(gè)對(duì)應(yīng)關(guān)系落到紙上其實(shí)就已經(jīng)得出了首頁的骨架。后面寫代碼的時(shí)候你自然會(huì)按照這個(gè)骨架去組織最外層是一個(gè)縱向列表或ColumnColumn里按順序放著頂部區(qū)域、輪播區(qū)域、宮格區(qū)域每個(gè)區(qū)域內(nèi)部再根據(jù)方向選擇Row或者GridView。這個(gè)動(dòng)作看起來很簡單但能省掉大量返工。我見過不少同事一上來就照著設(shè)計(jì)稿密密麻麻的標(biāo)注寫代碼寫到一半發(fā)現(xiàn)中間那個(gè)區(qū)塊的寬度撐不開或者某個(gè)區(qū)域的高度寫死之后在更大屏幕上露白最后只能整體推翻重排。先在紙上確定區(qū)塊的父子關(guān)系寫代碼時(shí)就不會(huì)被細(xì)節(jié)牽著走。1.2 把首頁壓縮成“一個(gè)大Column”從Flutter的布局機(jī)制來看幾乎所有首頁都可以簡化成一個(gè)縱向排布的大結(jié)構(gòu)默認(rèn)從上到下排子組件子組件之間互不重疊。需要橫向排列時(shí)就換成Row需要多列鋪開就用GridView。這里有個(gè)比較實(shí)用的建議拿到結(jié)構(gòu)圖之后先用注釋或者空函數(shù)把區(qū)域定義出來。比如這樣override Widget build(BuildContext context) { return Scaffold( body: SafeArea( child: Column( children: [ _buildTopBar(), // 頂部搜索欄與用戶信息 _buildBanner(), // 輪播圖區(qū)域 _buildGridMenu(), // 功能宮格 _buildRecommend(), // 推薦內(nèi)容流 ], ), ), ); }先搭一個(gè)空殼再逐個(gè)往里面填組件。這樣做的好處非常明顯即使某個(gè)區(qū)域內(nèi)部報(bào)錯(cuò)或者溢出問題也被隔離在對(duì)應(yīng)方法里不會(huì)把首頁的整體結(jié)構(gòu)帶崩。調(diào)試的時(shí)候也能直接把某一個(gè)方法拿出去單獨(dú)run快速確認(rèn)視覺表現(xiàn)。我還想提醒一點(diǎn)Flutter的布局過程是“約束向下傳、尺寸向上返回”。父組件通過BoxConstraints告訴子組件“你最多能有多大、最少能有多小”子組件測量完之后再把實(shí)際尺寸匯報(bào)給父組件。很多寬高不對(duì)的問題根本不是代碼里寫的數(shù)值錯(cuò)了而是上層的約束把子組件給限制死了。理解這個(gè)機(jī)制之后排查方向會(huì)清晰很多不是盯著某個(gè)Widget參數(shù)發(fā)呆而是往它的父級(jí)去看約束來源。2. Flex、Column與Row的組合用法布局的主心骨2.1 Flex是地基Column和Row只是方便寫法新手很容易把Column和Row當(dāng)作兩個(gè)彼此獨(dú)立的組件來記其實(shí)它倆的底層都是Flex只是排列方向不同Row是水平方向的FlexColumn是垂直方向的Flex。那為什么不直接都用Flex因?yàn)镕lex需要多傳一個(gè)direction參數(shù)寫起來麻煩Flutter干脆做了兩個(gè)封裝讓你不用反復(fù)強(qiáng)調(diào)方向。理解這個(gè)繼承關(guān)系最大的好處是你看到Flexible這類名字時(shí)不會(huì)發(fā)懵——Flexible不是Flex的什么兄弟它是Flex布局里專門用來控制子組件在主軸方向上如何伸縮的工具。首頁布局里經(jīng)常要做的等分按鈕、比例欄目靠的就是Flexible配合flex參數(shù)。比如三個(gè)功能入口要按1:2:1的寬度鋪在同一行Row( children: [ Flexible(flex: 1, child: _buildEntry(今日推薦)), Flexible(flex: 2, child: _buildEntry(熱門活動(dòng))), Flexible(flex: 1, child: _buildEntry(個(gè)人中心)), ], )這樣三個(gè)區(qū)域就會(huì)按照權(quán)重占據(jù)整行寬度不管屏幕有多寬。如果不用Flexible而是直接寫死Container寬度換一臺(tái)大屏手機(jī)就會(huì)露餡。2.2 主軸與交叉軸排隊(duì)的人和對(duì)齊的人用生活化的方式理解主軸和交叉軸會(huì)輕松很多。主軸是隊(duì)伍排隊(duì)的那個(gè)方向交叉軸是隊(duì)伍之外垂直的方向。Column的主軸是豎直方向所以它是從上往下排隊(duì)交叉軸是水平方向決定每一列怎么對(duì)齊。Row剛好反過來??刂浦鬏S上的排列方式用mainAxisAlignment控制交叉軸上的對(duì)齊用crossAxisAlignment。首頁布局里最常用到的幾個(gè)取值我整理成了一張表參數(shù)表現(xiàn)適用場景mainAxisAlignment.start靠主軸起始端排列表內(nèi)容自然左對(duì)齊mainAxisAlignment.center主軸方向居中輪播圖等居中元素mainAxisAlignment.spaceBetween首尾貼邊、中間均分左右兩欄工具欄mainAxisAlignment.spaceEvenly所有間隔完全一致底部一排圖標(biāo)均勻分布crossAxisAlignment.center交叉軸居中圖標(biāo)和文字橫排時(shí)垂直居中crossAxisAlignment.stretch交叉軸方向拉滿讓卡片寬度撐滿容器這幾個(gè)里面最容易混淆的是spaceBetween和spaceAround。spaceBetween是第一個(gè)元素貼起始邊、最后一個(gè)貼結(jié)束邊中間的元素把剩余空間均勻插在間隙里spaceAround則是每個(gè)元素左右兩邊的空隙一致所以首尾元素外側(cè)只有中間間距的一半。想要“看起來邊距一致”的效果選spaceEvenly最穩(wěn)。這些都是排版基本功首頁那種結(jié)構(gòu)復(fù)雜的頁面尤其依賴它們來統(tǒng)一視覺節(jié)奏。2.3 flex比例、Spacer與間距的選擇如果希望一組子組件按比例鋪開寬度用Flexible配合flex參數(shù)如果只是想在某兩個(gè)組件之間頂開距離用Spacer就行。Spacer本質(zhì)上就是個(gè)透明的Flexible它內(nèi)部也有flex參數(shù)可以調(diào)權(quán)重比如頂部搜索欄左右各放一個(gè)按鈕時(shí)中間用Spacer把兩邊的組件頂?shù)竭吘?。間距處理我有一個(gè)自己的標(biāo)準(zhǔn)在首頁布局里用起來很順手區(qū)塊和區(qū)塊之間用Container的margin控制區(qū)塊內(nèi)部內(nèi)容到邊界的距離用padding控制同一個(gè)區(qū)塊內(nèi)組件與組件之間的空隙如果只出現(xiàn)一次直接用SizedBox(height: 12)如果多處重復(fù)用建議抽成統(tǒng)一常量比如放在一個(gè)AppSize類里class AppSize { static const double pageGap 16; static const double cardRadius 12; static const double innerGap 8; }為什么不直接到處寫16、12因?yàn)樵O(shè)計(jì)稿后續(xù)一定會(huì)調(diào)整間距到時(shí)候全局搜一遍替換比改一處忘一處要省心得多。首頁這種一屏幾十個(gè)元素的頁面間距統(tǒng)一是視覺是否“干凈”的關(guān)鍵。3. 列表和網(wǎng)格首頁內(nèi)容區(qū)的兩個(gè)高頻載體3.1 ListView靜態(tài)與動(dòng)態(tài)兩種模式的取舍首頁往下翻的內(nèi)容推薦、消息列表基本都離不開ListView。ListView有兩種典型用法第一種是children模式ListView( children: [ _buildItem(內(nèi)容一), _buildItem(內(nèi)容二), _buildItem(內(nèi)容三), ], )這種寫法比較簡單直觀適合內(nèi)容量不大、幾乎不會(huì)變化的靜態(tài)列表。但它有一個(gè)隱藏問題children模式會(huì)一次性把列表里的所有子組件全部構(gòu)建出來哪怕頁面只顯示三屏后面幾十屏的組件也會(huì)先創(chuàng)建好。數(shù)據(jù)量小的時(shí)候無所謂但一旦接上接口、列表破百性能浪費(fèi)就非常明顯。更推薦的做法是用ListView.builder它只會(huì)構(gòu)建當(dāng)前視口里可見的那幾個(gè)item滾動(dòng)時(shí)按需創(chuàng)建、離屏后回收這就是常說的懶加載ListView.builder( itemCount: _items.length, itemBuilder: (context, index) { return _buildItem(_items[index]); }, )我的建議是首頁里的列表哪怕現(xiàn)在還是寫死的數(shù)據(jù)也直接用builder模式。它的寫法并不比children模式復(fù)雜多少但將來接接口時(shí)不需要改結(jié)構(gòu)只要替換數(shù)據(jù)源就行。3.2 GridView構(gòu)建功能宮格從快捷寫法到可配置網(wǎng)格宮格入口是首頁另一類高頻組件常見的8個(gè)功能圖標(biāo)、兩行四列或者三行三列的品類導(dǎo)航都是GridView的典型場景。Flutter提供了兩種常用的使用方式。第一種是GridView.count把列數(shù)直接寫在構(gòu)造器里簡單直接GridView.count( crossAxisCount: 4, mainAxisSpacing: 10, crossAxisSpacing: 10, children: [...], )第二種是GridView.builder配合SliverGridDelegateWithFixedCrossAxisCount寫起來更長但可配置性高GridView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 4, childAspectRatio: 1.1, mainAxisSpacing: 10, crossAxisSpacing: 10, ), itemCount: _menuItems.length, itemBuilder: (context, index) { return _buildMenuCell(_menuItems[index]); }, )這里有個(gè)參數(shù)需要特別留意childAspectRatio。它決定單元格的寬高比默認(rèn)是1也就是正方形。宮格里如果只有一個(gè)圖標(biāo)正方形夠用但圖標(biāo)下還要放文字時(shí)1:1會(huì)讓文字區(qū)顯得局促通常需要把比例調(diào)到1.1或1.2讓格子稍微高一點(diǎn)。這個(gè)值沒有絕對(duì)標(biāo)準(zhǔn)得結(jié)合圖標(biāo)大小和文字長度實(shí)測調(diào)整。另外shrinkWrap和physics這兩個(gè)參數(shù)在這里是配套使用的。因?yàn)槭醉摰膶m格通常是嵌在一個(gè)可滾動(dòng)的整體頁面里如果不給GridView關(guān)掉自身滾動(dòng)、再讓它高度隨內(nèi)容撐開就會(huì)出現(xiàn)滾動(dòng)手勢沖突和高度無限的報(bào)錯(cuò)。set到這兩個(gè)參數(shù)宮格就變成了一個(gè)“只負(fù)責(zé)排列、不負(fù)責(zé)滾動(dòng)”的普通模塊滾動(dòng)統(tǒng)一交給外層。3.3 嵌套滾動(dòng)與滾動(dòng)沖突的處理首頁同時(shí)存在“整體可滾動(dòng)”和“局部可滾動(dòng)”的情況很常見比如外層是一個(gè)縱向滾動(dòng)列表內(nèi)部第一屏里疊著一個(gè)橫向滑動(dòng)的banner輪播。橫向和縱向滾動(dòng)在Flutter里一般不會(huì)互相干擾系統(tǒng)會(huì)根據(jù)手勢方向自動(dòng)做路由判斷。真正容易出問題的是兩個(gè)縱向滾動(dòng)組件嵌套。比如在ListView里塞一個(gè)不設(shè)任何參數(shù)的GridView這時(shí)手指上下滑動(dòng)往往會(huì)“打架”不知道應(yīng)該滾外層還是滾內(nèi)層甚至內(nèi)層GridView會(huì)直接報(bào)高度無限的錯(cuò)誤。解決辦法有兩種一種就是我上面寫的給內(nèi)層GridView設(shè)置shrinkWrap: true和NeverScrollableScrollPhysics讓它失去獨(dú)立滾動(dòng)能力、高度由內(nèi)容決定另一種更進(jìn)階把整個(gè)首頁改成CustomScrollView把banner、宮格、列表統(tǒng)統(tǒng)變成Sliver模塊由統(tǒng)一的滾動(dòng)容器接管。CustomScrollView的思路其實(shí)更接近首頁這種長頁面的最佳實(shí)踐但它需要理解Sliver體系上手門檻比普通ListView高一些。初次做首頁布局時(shí)用“外層ListView 內(nèi)部shrinkWrap網(wǎng)格”的方式能更快跑通等對(duì)Sliver熟了再回來重構(gòu)也不遲。4. 讓布局從“能看”到“好看”的細(xì)節(jié)工程4.1 間距、內(nèi)邊距與視覺對(duì)齊功能跑得通的布局和設(shè)計(jì)稿上看著舒服的布局差別往往在細(xì)節(jié)間距上。Flutter控制間距的方法一共有三套SizedBox負(fù)責(zé)撐寬高、Padding控制內(nèi)容到組件邊界的距離、Margin控制組件與外部元素的距離。三者的使用場景不一樣不要混著亂用。在首頁這種結(jié)構(gòu)密集的頁面里我強(qiáng)烈建議把間距值限定在一個(gè)小范圍內(nèi)比如8、12、16、24、32。這么做之后頁面會(huì)自然形成一種呼吸感不會(huì)出現(xiàn)“這里空一大塊、那里擠成一團(tuán)”的失調(diào)現(xiàn)象。設(shè)計(jì)稿的間距如果跟這些值稍微有點(diǎn)出入優(yōu)先取最接近的檔位視覺上幾乎看不出區(qū)別但代碼維護(hù)起來省事很多。視覺對(duì)齊還有一個(gè)容易踩的點(diǎn)交叉軸默認(rèn)是center也就是說Column里每個(gè)子組件的寬度由各自內(nèi)容決定居中對(duì)齊。如果你希望讓幾個(gè)橫排卡片拉通到一樣寬就得用Expanded或者把交叉軸設(shè)置成stretch。Expanded的作用是把主軸方向上的剩余空間全部填滿這一層邏輯很多新手會(huì)漏結(jié)果就是明明同一行組件寬度卻參差不齊。4.2 圓角、陰影與卡片質(zhì)感首頁習(xí)慣用卡片承載信息圓角和陰影是營造卡片質(zhì)感的關(guān)鍵。用Container做卡片時(shí)圓角不是直接寫在Container上而是寫在裝飾器里Container( padding: EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withAlpha(30), blurRadius: 8, offset: Offset(0, 4), ), ], ), child: _buildCardContent(), )這個(gè)位置問題幾乎每周都能在答疑帖里看到有人對(duì)著Container的屬性翻半天以為支持圓角的參數(shù)被刪了。其實(shí)它就是設(shè)計(jì)在decoration里的沒有寫在Container最外層。陰影方面要特別提醒陰影參數(shù)應(yīng)該全頁面統(tǒng)一比如所有卡片的陰影色、模糊半徑、偏移量都用同一組值。如果每張卡片自由發(fā)揮有的陰影重、有的陰影輕、有的不設(shè)陰影頁面會(huì)顯得很臟。比較好的做法是把卡片封裝成一個(gè)通用組件內(nèi)部統(tǒng)一處理圓角、陰影和內(nèi)邊距業(yè)務(wù)側(cè)只傳入內(nèi)容即可。這樣改一次就能讓整套卡片風(fēng)格全部對(duì)齊不需要各處復(fù)制微調(diào)。4.3 Stack與Positioned懸浮元素的正確打開方式首頁頂部常常有懸浮搜索框、右下角可能有懸浮按鈕這種“一個(gè)元素疊在另一個(gè)元素之上”的效果用Stack最順手。Stack會(huì)把子組件按順序疊放配合Positioned可以指定子組件相對(duì)Stack四邊的距離Stack( children: [ _buildBackground(), Positioned( bottom: 16, right: 16, child: _buildFloatingButton(), ), ], )不過在排查“布局重疊”問題的時(shí)候有一半情況根本不是Stack造成的。兩個(gè)縱向排列的模塊在視覺上出現(xiàn)了重疊更常見的原因是外層用了負(fù)margin、內(nèi)層某個(gè)文本溢出把高度撐變形了或者是一個(gè)組件在Overlay層沒被及時(shí)移除。我自己的經(jīng)驗(yàn)是先把報(bào)錯(cuò)信息里提示的Widget找到確認(rèn)它是想表達(dá)有意的疊加還是無意中被擠成了重疊效果再?zèng)Q定要不要引入Stack和Positioned。否則等于把錯(cuò)誤的方法用在了錯(cuò)誤的問題上越修越亂。5. 布局報(bào)錯(cuò)、溢出與適配調(diào)試階段的三個(gè)老大難5.1 RenderFlex overflowed的三種解法Flutter布局調(diào)試?yán)锍霈F(xiàn)頻率最高的報(bào)錯(cuò)就是RenderFlex overflowed。它的本質(zhì)是主軸方向上空間不夠用了組件內(nèi)容超出容器可用的范圍于是屏幕邊緣會(huì)出現(xiàn)一條黃黑條紋提示。遇到這個(gè)報(bào)錯(cuò)先不要著急加Expanded而是按順序排查三種可能。第一種是約束問題這個(gè)Row或Column是不是被父級(jí)固定了寬度而里面的內(nèi)容加在一起已經(jīng)超過了這個(gè)寬度如果是就得從更上層打開空間比如把固定的寬度改成Flexible。第二種是比例問題多個(gè)子組件同時(shí)存在誰占多少空間沒有說清楚導(dǎo)致其中一個(gè)把空間擠沒了。這時(shí)可以用Expanded或者Flexible給關(guān)鍵組件分配權(quán)重。第三種是展示問題某個(gè)Text文字寫得太長又禁用了換行直接把Row撐爆。這種場合給Text加overflow: TextOverflow.ellipsis讓多出的部分變成省略號(hào)比強(qiáng)行撐寬更優(yōu)雅。判斷順序?qū)α舜蟛糠忠绯鰡栴}都能快速定位省得在代碼里撞運(yùn)氣。5.2 文本方向布局與雙端適配文本方向這塊做純中文頁面時(shí)不容易被注意但只要開始涉及數(shù)字、金額、中英文混排或者將來要做多語言版本就要小心了。Flutter在很多場景會(huì)讀取環(huán)境里的區(qū)域設(shè)置自動(dòng)決定文本方向但如果你在某個(gè)子組件里手動(dòng)指定了textDirection而它和父級(jí)配置不一致排版就很容易亂。我的習(xí)慣是在組件樹靠近根的位置統(tǒng)一設(shè)置好locale和textDirection子組件不輕易覆蓋。另外中英文混排時(shí)換行策略也會(huì)影響布局默認(rèn)情況下Flutter會(huì)依據(jù)文本內(nèi)容自動(dòng)斷行但如果文案被設(shè)置了maxLines: 1文本就沒辦法自動(dòng)換行寬度不夠時(shí)溢出問題會(huì)立刻暴露出來。這時(shí)直接用ellipsis配合overflow處理截?cái)嗖灰獓L試讓文字去適應(yīng)容器。鴻蒙設(shè)備上的適配還有一個(gè)比較現(xiàn)實(shí)的問題屏幕尺寸跨度很大從手表到平板都有可能跑Flutter應(yīng)用。建議首頁布局里盡量少依賴寫死的寬高數(shù)值改用比例布局和Expanded。必須固定尺寸的地方至少把數(shù)值集中管理不要在build方法里散落一堆魔法數(shù)字。5.3 用Flutter Inspector快速定位問題與其盯著代碼猜測問題根源不如直接打開Flutter Inspector。這個(gè)工具會(huì)以可視化方式展示組件樹和渲染邊界你點(diǎn)中頁面上的某個(gè)區(qū)域它就能告訴你這個(gè)區(qū)域?qū)?yīng)哪個(gè)Widget、它的父級(jí)和子級(jí)分別是誰、實(shí)際尺寸和約束條件是多少。排查布局重疊或者間距錯(cuò)誤時(shí)我的操作路徑是先在Inspector里選中異常區(qū)域看當(dāng)前Widget的約束條件再往上一層看父級(jí)給它分配了多少寬度和高度必要時(shí)連續(xù)往上追兩級(jí)基本上就能定位到是誰把空間擠掉了。很多布局問題的根源不在當(dāng)前可見的那個(gè)元素而在它的父容器用Inspector一查就非常直觀。對(duì)新手來說這個(gè)工具能極大縮短“試錯(cuò)—刷新—再看”的循環(huán)時(shí)間比滿世界搜報(bào)錯(cuò)帖有效得多。6. 首頁布局走向組件化的一點(diǎn)私人體會(huì)6.1 拆件時(shí)機(jī)讓布局代碼不再堆在一起首頁的代碼如果一直堆在一個(gè)build方法里短期倒也沒問題但每加一個(gè)需求方法就膨脹一圈幾個(gè)月后就會(huì)變成幾百行的“大泥球”。我判斷一個(gè)區(qū)域要不要抽成獨(dú)立Widget通常會(huì)問三個(gè)問題這段UI是否會(huì)被其他頁面復(fù)用它內(nèi)部是否有自己的數(shù)據(jù)邏輯它包含的子組件是否超過三個(gè)只要命中兩三條就值得拆出去。拆的時(shí)候也不追求一步到位。先按首頁的大區(qū)塊拆比如頂欄、輪播、宮格、推薦流各成一個(gè)方法或類某個(gè)區(qū)塊內(nèi)部如果還有復(fù)雜的小結(jié)構(gòu)再往下拆一層。先拆大粒度的等代碼繼續(xù)膨脹再細(xì)化比一上來就按每個(gè)小組件拆分要?jiǎng)?wù)實(shí)得多也不容易出現(xiàn)“拆得太碎、調(diào)用鏈太長”的新問題。6.2 拆完之后的通信回調(diào)和狀態(tài)管理組件拆完之后緊接著要面對(duì)的就是通信問題。我的原則很簡單數(shù)據(jù)向下傳事件向上拋。父組件通過構(gòu)造參數(shù)把數(shù)據(jù)傳給子組件子組件需要修改數(shù)據(jù)時(shí)不直接改共享變量而是把事件通過回調(diào)拋給父組件由父組件統(tǒng)一處理。_buildGridMenu( menuItems: _menuItems, onItemTap: (item) { _handleMenuTap(item); }, )這樣做的好處是數(shù)據(jù)流動(dòng)方向單一出問題時(shí)可以沿著調(diào)用鏈一路追下去。等業(yè)務(wù)復(fù)雜到某個(gè)共享狀態(tài)需要被多個(gè)兄弟組件跨層讀取時(shí)再考慮引入Provider或Riverpod這類狀態(tài)管理方案。很多人問Flutter的Provider到底該怎么用其實(shí)它解決的就是組件拆分后共享數(shù)據(jù)歸屬不清的問題——基礎(chǔ)布局這一篇先不用急著引入把回調(diào)的方式用熟再上狀態(tài)管理會(huì)順很多。6.3 布局類字段的組織建議最后分享一個(gè)寫代碼時(shí)的組織技巧一個(gè)Widget的構(gòu)造參數(shù)盡量按“固定的放前面、變化的放后面”來排。必傳的數(shù)據(jù)字段放在構(gòu)造函數(shù)參數(shù)的前面可選樣式字段放后面并給上默認(rèn)值。比如一個(gè)通用卡片組件class AppCard extends StatelessWidget { const AppCard({ super.key, required this.child, this.padding const EdgeInsets.all(12), this.margin EdgeInsets.zero, this.borderRadius const BorderRadius.all(Radius.circular(12)), }); }這樣讀代碼的人一眼就能看出哪些是核心數(shù)據(jù)、哪些只是樣式修飾調(diào)用時(shí)的可讀性會(huì)高出很多。首頁布局里的重復(fù)性組件尤其需要這種約定因?yàn)橐粋€(gè)頁面里可能有十幾個(gè)樣式相近但細(xì)節(jié)不同的卡片參數(shù)順序一旦混亂調(diào)用起來非常費(fèi)神。布局這關(guān)過了首頁基本就立住了。下一回可以接著聊輪播、下拉刷新這類交互部件把首頁從靜態(tài)骨架推進(jìn)到能用的狀態(tài)。要是你在做首頁布局時(shí)遇到過特別刁鉆的溢出或重疊問題也建議順著這篇的排查思路走一遍大多數(shù)情況都比你想的要簡單。