在上一篇 【flutter_web 初體驗】中,簡單的介紹了下 flutter_web 建立一個項目和一些踩坑的解決方案,本篇將進一步講解搭建 flutter_web 項目的基本過程。html
本文將講解如下要點:前端
/
├── 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 大部分:git
接下來,將要實現 2 個頁面, 效果分別以下:github
列表和詳情,頁面仍是比較簡單的。web
總體佈局就是頭部、內容、尾部。好比尾部在首頁和詳情頁的底部都是同樣的,把它領出來做爲一個公共組件。我的的開發習慣就是,相同的東西往上冒泡,讓文件目錄層次上浮。json
由於頁面不存在懸浮狀況,因此首頁佈局仍是比較簡單的:swift
// 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 包裝:markdown
return SingleChildScrollView(
child: Column(
children: <Widget>[mdView, FooterView()],
),
);
複製代碼
在有使用過站點的第一版的時候,當你改變瀏覽器的大小的時候,佈局會比較醜陋,且會發送一些佈局警告。形成的緣由是沒有適配各類屏幕的大小。若是作前端的,會有一些比較通用的解決辦法:
若是對此感興趣可深刻閱讀 《響應式佈局的經常使用解決方案對比(媒體查詢、百分比、rem和vw/vh)》
那麼若是 flutter_web 要作響應是佈局,該怎麼辦?
MediaQuery
獲取當前的窗口的大小Flexible
和 Expanded
去佈局界面,使用百分比而不是硬編碼。LayoutBuilder
獲取父 widget 的 ConstraintBox
MediaQuery
或 OrientationBuilder
獲取設備的方向。AspectRatio
和 FractionallySizedBox
是經常使用的百分比相關的 Widget。響應式佈局有兩篇文章推薦閱讀:
// main.dart
main() {
Static.storage = Storage();
runApp(SwiftClub());
}
class SwiftClub extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'swiftclub',
theme: ThemeData(fontFamily: "Montserrat"),
onGenerateRoute: (setting) => buildRouters(setting),
initialRoute: "/",
);
}
}
複製代碼
// router/router.dart
Route<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
爲此作了個 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();
}
}
複製代碼
在 github 上巡遊一番,應該找不到針對 flutter_web 的 markdown 的支持庫。筆者在參考
封裝了在 flutter_web 上可用的 markdown 組件,具體實現可參考 swiftclub/site
語法高亮如今只支持 dart 語言的。
更多閱讀,請關注 SwiftOldBird 官方微信公衆號