flutter_web 實戰之文章列表與詳情

在上一篇 【flutter_web 初體驗】中,簡單的介紹了下 flutter_web 建立一個項目和一些踩坑的解決方案,本篇將進一步講解搭建 flutter_web 項目的基本過程。html

本文將講解如下要點:前端

  1. 項目結構介紹
  2. 佈局
  3. 響應式佈局
  4. 頁面路由
  5. 網絡請求
  6. markdown 渲染

項目目錄結構

/
├── 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

  • network: 網絡
  • models: 模型
  • router: 路由
  • pages: 頁面
  • components:組件
  • kit: 工具、常量、基類等

佈局

接下來,將要實現 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

若是對此感興趣可深刻閱讀 《響應式佈局的經常使用解決方案對比(媒體查詢、百分比、rem和vw/vh)》

那麼若是 flutter_web 要作響應是佈局,該怎麼辦?

  • 尺寸大小和位置不使用硬編碼
  • 使用 MediaQuery 獲取當前的窗口的大小
  • 使用 FlexibleExpanded 去佈局界面,使用百分比而不是硬編碼。
  • 使用 LayoutBuilder 獲取父 widget 的 ConstraintBox
  • 使用 MediaQueryOrientationBuilder 獲取設備的方向。
  • AspectRatioFractionallySizedBox 是經常使用的百分比相關的 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

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_web 上可用的 markdown 組件,具體實現可參考 swiftclub/site

語法高亮如今只支持 dart 語言的。

效果

更多閱讀,請關注 SwiftOldBird 官方微信公衆號

微信公衆號

原文:swiftoldbird.loveli.site/2019/08/22/…

相關文章
相關標籤/搜索