flutter_web 实战之文章列表与详情
在上一篇 【flutter_web 初體驗】中,簡單的介紹了下 flutter_web 創建一個項目和一些踩坑的解決方案,本篇將進一步講解搭建 flutter_web 項目的基本過程。
本文將講解以下要點:
項目目錄結構
/ ├── README.md ├── analysis_options.yaml ├── build # webdev build 編譯生成的目錄,用于部署 ├── lib # 工作區 │?? ├── components # 組件(Widgets) │?? ├── kit # 工具、父類 │?? ├── main.dart # 入口 │?? ├── models # 數據模型 │?? ├── network # 網絡 │?? ├── pages # 頁面 │?? │?? ├── detail # 詳情頁 │?? │?? │?? ├── bloc │?? │?? │?? ├── detail.dart │?? │?? │?? ├── model │?? │?? │?? └── page │?? │?? ├── index # 首頁 │?? │?? ├── pages.dart │?? │?? └── user # 用戶 │?? └── router # 頁面路由 ├── pubspec.lock ├── pubspec.yaml # 依賴 └── web├── assets # 資源區│?? ├── FontManifest.json # 字體│?? └── images # 圖片│?? └── swift_logo.png├── index.html└── main.dart 復制代碼主要劃分了 6 大部分:
- network: 網絡
- models: 模型
- router: 路由
- pages: 頁面
- components:組件
- kit: 工具、常量、基類等
布局
接下來,將要實現 2 個頁面, 效果分別如下:
列表和詳情,頁面還是比較簡單的。
整體布局就是頭部、內容、尾部。比如尾部在首頁和詳情頁的底部都是一樣的,把它領出來作為一個公共組件。個人的開發習慣就是,相同的東西往上冒泡,讓文件目錄層次上浮。
首頁布局
因為頁面不存在懸浮情況,所以首頁布局還是比較簡單的:
// pages/index/index_pages.dart// 添加頭部 List<Widget> lists = [_buildHeader(context)];// 添加列表內容 lists.addAll(rows.map((item) {return _buildCell(item); }).toList());// 添加尾部 lists.add(Container(margin: EdgeInsets.only(top: 100),child: FooterView(), ));/// 整體內容用 SingleChildScrollView 進行包裝 SingleChildScrollView(child: Container(color: Colors.white,child: Column(children: lists,),)) 復制代碼詳情頁布局
詳情頁頭部是懸浮的,且文章采用 markdown,也是一個 ListView。 然后底部是通用的底部欄。
那么懸浮的話就采用了 AppBar :
Scaffold(backgroundColor: Colors.white,appBar: PreferredSize(child: HeaderView(), preferredSize: Size.fromHeight(50)),body: body) 復制代碼上面的 body 是通過 SingleChildScrollView 包裝:
return SingleChildScrollView(child: Column(children: <Widget>[mdView, FooterView()],), ); 復制代碼響應式布局
在有使用過站點的初版的時候,當你改變瀏覽器的大小的時候,布局會比較丑陋,且會發送一些布局警告。造成的原因是沒有適配各種屏幕的大小。如果做前端的,會有一些比較通用的解決辦法:
- 媒體查詢
- 百分比
- rem
- vw/vh
如果對此感興趣可深入閱讀 《響應式布局的常用解決方案對比(媒體查詢、百分比、rem和vw/vh)》
那么如果 flutter_web 要做響應是布局,該怎么辦?
- 尺寸大小和位置不使用硬編碼
- 使用 MediaQuery 獲取當前的窗口的大小
- 使用 Flexible 和 Expanded 去布局界面,使用百分比而不是硬編碼。
- 使用 LayoutBuilder 獲取父 widget 的 ConstraintBox
- 使用 MediaQuery 或 OrientationBuilder 獲取設備的方向。
- AspectRatio 和 FractionallySizedBox 是常用的百分比相關的 Widget。
響應式布局有兩篇文章推薦閱讀:
- Build Responsive UIs in Flutter
- Making Cross-platform Flutter Landing Page Responsive (Part 3)
頁面路由
// main.dart main() {Static.storage = Storage();runApp(SwiftClub()); }class SwiftClub extends StatelessWidget {Widget build(BuildContext context) {return MaterialApp(debugShowCheckedModeBanner: false,title: 'swiftclub',theme: ThemeData(fontFamily: "Montserrat"),onGenerateRoute: (setting) => buildRouters(setting),initialRoute: "/",);} } 復制代碼// router/router.dartRoute<dynamic> buildRouters(RouteSettings settings) {dynamic args = settings.arguments;switch (settings.name) {case "/login":return SimpleRoute(name: "/login", title: "login", builder: (context) => LoginPage());case "/detail":if (args == null) {// 直接刷新當前,參數可能不存在,返回首頁return defaultRoute();}// 解析頁面傳遞的參數final topicId = SafeValue.toInt(args['topicId']);return SimpleRoute(name: "detail",title: "detaila",builder: (context) => DetailPage(topicId: topicId,));case "/":return defaultRoute();default:return defaultRoute();} }SimpleRoute defaultRoute() {return SimpleRoute(name: '/', title: 'swiftclub', builder: (context) => IndexPage()); } 復制代碼flutter_web 的路由,跟 flutter 的路由管理是一樣的,主要是注意兩點:
-
如果刷新當前頁面,之前其他頁面傳遞過來的參數就沒有了,刷新后,頁面獲取不到傳遞過來的參數,進行網絡請求,報錯。所以這里做了判斷,如果參數不存在了,則返回到默認的首頁。
-
路由參數取值,嘗試多次,發現
ModalRoute.of(context).settings.arguments; 復制代碼
獲取不到 arguments,但在判斷路由的時候可以獲取。
網絡請求
由于 dart:io 在 flutter_web 中還不支持,所以 dio 是不能使用的,官方建議使用 package:http
Flutter for web: Frequently Asked Questions
為此做了個 http 請求的封裝,可參考使用:
import 'dart:convert'; import 'package:flutter_web/widgets.dart'; import 'package:http/http.dart' as http; import 'package:swiftclub/kit/macro/macro.dart';class Network {static getReq(String url, {Map params, Map headers}) async {var fullUrl = Macro.URL_base + url;return await _getReq(fullUrl, params: params, headers: headers);}static _getReq(String url, {Map params, Map headers}) async {var reqUri = _uriWith(url, queryParameters: params);http.Response response = await http.get(reqUri, headers: headers);var responseBody = json.decode(response.body);return responseBody;}static _postReq(String url, {Map headers, Map params}) async {var fullUrl = Macro.URL_base + url;if (headers != null && headers.isNotEmpty) {http.Response response =await http.post(Uri.parse(fullUrl), headers: headers, body: params);var responseBody = json.decode(response.body);return responseBody;} else {http.Response response =await http.post(Uri.parse(fullUrl), body: params);var responseBody = json.decode(response.body);return responseBody;}}static Uri _uriWith(String url, {Map queryParameters}) {String _url = url;String query = _urlEncodeMap(queryParameters);if (query.isNotEmpty) {_url += (_url.contains("?") ? "&" : "?") + query;}// Normalize the url.return Uri.parse(_url).normalizePath();}static String _urlEncodeMap(data) {StringBuffer urlData = StringBuffer("");bool first = true;void urlEncode(dynamic sub, String path) {if (sub is List) {for (int i = 0; i < sub.length; i++) {urlEncode(sub[i],"$path%5B${(sub[i] is Map || sub[i] is List) ? i : ''}%5D");}} else if (sub is Map) {sub.forEach((k, v) {if (path == "") {urlEncode(v, "${Uri.encodeQueryComponent(k)}");} else {urlEncode(v, "$path%5B${Uri.encodeQueryComponent(k)}%5D");}});} else {if (!first) {urlData.write("&");}first = false;urlData.write("$path=${Uri.encodeQueryComponent(sub.toString())}");}}urlEncode(data, "");return urlData.toString();} } 復制代碼markdown 渲染
在 github 上巡游一番,應該找不到針對 flutter_web 的 markdown 的支持庫。筆者在參考
- flutter/flutter_markdown
- JakeCai/flutter_web_markdown
- alibaba/flutter-go
封裝了在 flutter_web 上可用的 markdown 組件,具體實現可參考 swiftclub/site
語法高亮現在只支持 dart 語言的。
效果
更多閱讀,請關注 SwiftOldBird 官方微信公眾號
原文:swiftoldbird.loveli.site/2019/08/22/…
轉載于:https://juejin.im/post/5d5e666ff265da03a715da36
總結
以上是生活随笔為你收集整理的flutter_web 实战之文章列表与详情的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Swagger 入门使用
- 下一篇: 被辞退员工的一天