近期項(xiàng)目收尾,回顧一下flutter的小說(shuō)翻頁(yè)效果以及邏輯的實(shí)現(xiàn).
一、主要邏輯
1、翻頁(yè)效果采用的是AnimationController動(dòng)畫(huà)的方式
2、由于小說(shuō)的章節(jié)比較多, 需要無(wú)限翻頁(yè),采用的是多頁(yè)面進(jìn)行內(nèi)容替換的邏輯
3、難點(diǎn): A頁(yè)面內(nèi)容的加載時(shí)機(jī);B章節(jié)內(nèi)容的加載時(shí)機(jī);C從目錄切換章節(jié);D字體行距的切換;E內(nèi)容分頁(yè)
二、頁(yè)面結(jié)構(gòu)
整個(gè)內(nèi)容采用的是一個(gè)帶有手勢(shì)交互的FutureBuilder視圖, 采用FutureBuilder的好處是可以自然的根據(jù)網(wǎng)絡(luò)請(qǐng)求返回的不同結(jié)果加載對(duì)應(yīng)的數(shù)據(jù),并且不需要手動(dòng)的去setstate
Widget _bodyView() {
return GestureDetector(
onTapUp: (TapUpDetails details) {
double xRate = details.globalPosition.dx / Get.width;
if (xRate > 0.33 && xRate < 0.66) {
NovelReadModel read = dataChapterList[currentIndex];
widget.clickMenu(read);
} else if (xRate >= 0.66) {
turnPagenext = true;
nextPage();
autoListenTurnPage = false;
} else {
// 如果是第一章 則不翻頁(yè)
if (currentIndex == 1) {
gbs.shower.toast("已經(jīng)是第一頁(yè)了");
return;
}
turnPagenext = false;
previousPage();
autoListenTurnPage = false;
}
},
onPanDown: (details) {
handsTapDetails = details;
},
onHorizontalDragUpdate: _onHorizontalDragUpdate,
onHorizontalDragEnd: (_) => _onHorizontalDragEnd(),
onHorizontalDragCancel: _resetAnimation,
child: FutureBuilder(),// 偽代碼
);
}
從代碼中可以看出,
翻頁(yè)不僅僅支持左右滑動(dòng)頁(yè)面onHorizontalDragUpdate方法處理
還可以支持點(diǎn)擊視圖的邊緣onTapUp: (TapUpDetails details) {的方式去切換

單頁(yè)面內(nèi)容包含了頭部的章節(jié)菜單和底部的文章內(nèi)容
其中內(nèi)容采用的是富文本加載方便聽(tīng)書(shū)時(shí)的進(jìn)度顯示
三、翻頁(yè)
書(shū)籍有很多內(nèi)容, 我們采用一個(gè)數(shù)組L,首次存儲(chǔ)三個(gè)章節(jié)的內(nèi)容(上一章、本章節(jié)、下一章)
翻頁(yè)的重點(diǎn)邏輯在_onHorizontalDragEnd的方法里面,這個(gè)方法是手勢(shì)結(jié)束的方法,.需要通過(guò)手勢(shì)的坐標(biāo)變化來(lái)確定是上一頁(yè)還是下一頁(yè)
1、我們拿下一頁(yè)來(lái)舉例
下一頁(yè)要做一下幾個(gè)事情
(1)、是不是本書(shū)的最后一張最后一頁(yè), 如果是則需要將后面的一頁(yè)替換為完本頁(yè)面
(2)、如果數(shù)組L中沒(méi)有下一章的數(shù)據(jù)(網(wǎng)絡(luò)不佳就會(huì)沒(méi)有下一章),需要進(jìn)行網(wǎng)絡(luò)請(qǐng)求填充下一章
(3)、更新當(dāng)前頁(yè)碼和數(shù)組下標(biāo)
(4)、講停留在頁(yè)面中間的動(dòng)畫(huà)執(zhí)行完畢
(5)、刷新下一頁(yè)內(nèi)容
(6)、統(tǒng)計(jì)埋點(diǎn)
(7)、記錄歷史記錄(存本地?cái)?shù)據(jù)庫(kù))
(8)、根據(jù)需要向服務(wù)器上傳歷史記錄
歷史記錄中涉及到一個(gè)章節(jié)閱讀字?jǐn)?shù)的計(jì)算問(wèn)題, 可以根據(jù)需要取本頁(yè)的第一個(gè)字和最后一個(gè)字中間的字

刷新上一頁(yè)下一頁(yè)內(nèi)容
pageCenter.autoNext();
// 更新center頁(yè)面(1、將第二頁(yè)的內(nèi)容替換為第三頁(yè) 2、讓第二頁(yè)返回視覺(jué)上代替第三頁(yè))
await Future.delayed(const Duration(milliseconds: 100));
pageCenter.refresh(_pageCenterChild(dataChapterList[currentIndex].content, dataChapterList[currentIndex], true));
await Future.delayed(const Duration(milliseconds: 200));
pageCenter.noanimaPrevious();
pageBottom.refresh(_pageDefaultChild(dataChapterList[currentIndex + 1].content, dataChapterList[currentIndex + 1], false));
四、切換章節(jié)
切換章節(jié)有多種方案, 主要思路還是替換數(shù)組中的內(nèi)容, 并刷新頁(yè)面
我這里采用了一個(gè)最簡(jiǎn)單粗暴的方案:
1、清空數(shù)組L中所有數(shù)據(jù)
2、重新獲取前后三章的數(shù)據(jù)
3、計(jì)算當(dāng)前章節(jié)第一頁(yè)的下標(biāo)刷新當(dāng)前頁(yè)面內(nèi)容
五、小結(jié)
小說(shuō)翻頁(yè)總體來(lái)講邏輯還是比較復(fù)雜的,
由于功能點(diǎn)比較多又比較碎, 很容易產(chǎn)生各種bug 比如: 切換字體字號(hào)后當(dāng)前頁(yè)面的內(nèi)容刷新問(wèn)題、又比如快速翻頁(yè)到章節(jié)變更的數(shù)據(jù)刷新問(wèn)題
主要整體思路沒(méi)問(wèn)題剩下的只要細(xì)心豆可以搞定