阿里 Flutter-go 項目拆解筆記(八)

Flutter-go 項目地址是:https://github.com/alibaba/flutter-go

上文 我們分析了 第四個 Tab 頁面,主要分析了 翻頁動畫的實現

這篇文章主要拆解 詳情頁面和頁面跳轉

下圖是整理後的 詳情頁面和頁面跳轉 有關的內容:

ps: 這篇代碼量有點多,看不懂代碼可以先知道這個功能是如何去實現的,待後邊動手實現項目的時候回繼續補充講解。

頁面跳轉

頁面跳轉使用的是fluro庫,fluro庫的使用只需以下兩步即可。

  1. 先做實例化final router = Router();
  2. 定義handler,用於處理路由的路徑和參數的傳遞,如:
var usersHandler = Handler(handlerFunc: (BuildContext context, Map<String, dynamic> params) {
  // UsersScreen 頁面,接收參數爲 id 的值
  return UsersScreen(params["id"][0]);
});

void defineRoutes(Router router) {
  //  下面的路由會攔截如: /users/1234,的頁面路徑
  router.define("/users/:id", handler: usersHandler);

  // 需要頁面跳轉動畫可以使用 transitionType
  // router.define("users/:id", handler: usersHandler, transitionType: TransitionType.inFromLeft);
}

下面來看一下如何使用Router去進入Widget詳情頁面。

  1. main.dart中實例化Router,然後將對象賦值給application.dart中的Router對象,下次使用直接Application.router即可調用
  2. router_handler.dart文件中聲明瞭handler,然後在routers.dart文件中調用對應的handler。如:
// 定義Handler
var categoryHandler = new Handler(
  handlerFunc: (BuildContext context, Map<String, List<String>> params) {
    // 獲取參數
    String name = params["type"]?.first;

    return new CategoryHome(name);
  },
);

// 定義路徑
router.define('/category/:type', handler: categoryHandler);

// 頁面跳轉
 Application.router.navigateTo(
      context, "/category/${item.name}", 
      transition: TransitionType.inFromRight);

詳情頁面分析

分類頁面

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      // 當前頁面將退出,爲了多層分類頁面的複用
      body: WillPopScope(
        onWillPop: () {
          return back();
        },
        child: ListView(
          children: <Widget>[
            _buildContent(),
          ],
        ),
        // child: Container(color: Colors.blue,child: Text('123'),),
      ),
    );

網格佈局主要在 _buildContentWidgetItemContainer控件中通過循環去實現。WidgetItemContainer實現的思路如下:


僞代碼如下:

 List<Widget> _buildColumns(context) {
    List<Widget> _listWidget = [];
    List<Widget> _listRows = [];
    int addI;
    for (int i = 0, length = categories.length; i < length; i += columnCount) {
      _listRows = [];
      for (int innerI = 0; innerI < columnCount; innerI++) {
        addI = innerI + i;
        if (addI < length) {
          dynamic item = categories[addI];
          _listRows.add(
            Expanded(
              flex: 1,
              child: WidgetItem(
                title: item.name,
                onTap: () {
                  if (isWidgetPoint) {
                    String targetName = item.name;
                    String targetRouter = '/category/error/404';
                    widgetDemosList.forEach((item) {
                      if (item.name == targetName) {
                        targetRouter = item.routerName;
                      }
                    });
                    Application.router.navigateTo(context, "$targetRouter", transition: TransitionType.inFromRight);
                  } else {
                    Application.router
                        .navigateTo(context, "/category/${item.name}", transition: TransitionType.inFromRight);
                  }
                },
                index: addI,
                totalCount: length,
                rowLength: columnCount,
                textSize: isWidgetPoint ? 'middle' : 'small',
              ),
            ),
          );
        } else {
          _listRows.add(
            Expanded(
              flex: 1,
              child: Container(),
            ),
          );
        }
      }
      _listWidget.add(
        Row(
          children: _listRows,
        ),
      );
    }
    return _listWidget;
  }

Widget 詳情頁面

路徑:lib/componets/widget_demos.dart

return Scaffold(
        key: _scaffoldKey,
        appBar: AppBar(
          title: Text(widget.title),
          actions: <Widget>[
            new IconButton(
              tooltip: 'goBack home',
              onPressed: () {
                Navigator.popUntil(context, ModalRoute.withName('/'));
              },
              icon: Icon(Icons.home),
            ),
            new IconButton(
              tooltip: 'collection',
              onPressed: _getCollection,
              icon: Icon(_collectionIcons),
            ),
            PopupMenuButton<String>(
              onSelected: _selectValue,
              itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
                    const PopupMenuItem<String>(
                      value: 'doc',
                      child: ListTile(
                        leading: Icon(
                          Icons.library_books,
                          size: 22.0,
                        ),
                        title: Text('查看文檔'),
                      ),
                    ),
                    const PopupMenuDivider(),
                    const PopupMenuItem<String>(
                      value: 'code',
                      child: ListTile(
                        leading: Icon(
                          Icons.code,
                          size: 22.0,
                        ),
                        title: Text('查看Demo'),
                      ),
                    ),
                  ],
            ),
          ],
        ),
        body: Container(
          padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0),
          child: ListView(
            shrinkWrap: true,
            padding: const EdgeInsets.all(0.0),
            children: <Widget>[
              Column(
                children: _buildContent(),
              ),
            ],
          ),
        ),
        bottomNavigationBar:
            (widget.bottomNaviBar is Widget) ? widget.bottomNaviBar : null);
  }

從上面的源碼可以看出 點擊圖標回首頁 的操作是:

Navigator.popUntil(context, ModalRoute.withName('/'));

PopupMenu 彈窗的實現也是很簡單,直接使用PopupMenuButton即可生成。

中間的控件描述是通過獲取具體的Widget去展示的,在展示的時候用到了Markdownflutter_markdown

例如:展示Appbar組件的介紹,在widgets/components/Bar/AppBar路徑下有個index.dart,在index.dart裏面使用markdown格式返回了WidgetDemo控件要顯示的內容(也就是展示的詳情頁)

那麼那麼多個widget它是如何去區分點擊的是那個呢?

void onWidgetTap(WidgetPoint widgetPoint) {
    String targetName = widgetPoint.name;
    String targetRouter = '/category/error/404';
    widgetDemosList.forEach((item) {
      // print("targetRouter = item.routerName> ${[item.name,targetName]}");
      if (item.name == targetName) {
        targetRouter = item.routerName;
      }
    });
    Application.router.navigateTo(context, "$targetRouter");
  }

通過點擊事件可以看出,頁面跳轉的時候傳遞了widget的名稱,所以它是通過名稱來區別widget的,在widget_demo.dart中通過便利所有widget找到該名稱的widget然後用MarkDownBody去展示。僞代碼如下:

// 傳遞 title
class _DemoState extends State<Demo> {
  @override
  Widget build(BuildContext context) {
    return WidgetDemo(
      title: 'AppBar',
      codeUrl: 'components/Bar/AppBar/demo.dart',
      contentList: allDomes(context, this),
      docUrl: 'https://docs.flutter.io/flutter/material/AppBar-class.html',
    );
  }
}
// 得到 title,遍歷獲取該 widget 並展示
List<Widget> _buildContent() {
    List<Widget> _list = [
      SizedBox(
        height: 10.0,
      ),
    ];
    widget.contentList.forEach((item) {
      if (item.runtimeType == String) {
        _list.add(MarkdownBody(item));
        _list.add(
          SizedBox(
            height: 20.0,
          ),
        );
      } else {
        _list.add(item);
      }
    });
    return _list;
  }

爲了適配底部導航欄Widget還添加了bottomNavigationBar去控制顯示導航欄。

web 頁面

接收urltitle參數 ,使用了WebviewScaffold控件,這是一個第三方的庫(flutter_webview_plugin)提供的控件,該庫使用文檔

 return Scaffold(
       key: _scaffoldKey,
      appBar: AppBar(
        title: Text(widget.title),
        actions: <Widget>[
          new IconButton(
            tooltip: 'goBack home',
            onPressed: _getCollection,
            icon: Icon(
              _collectionIcons,
            ),
          ),
        ],
      ),
      body: WebviewScaffold(
        url: widget.url,
        withZoom: false,
        withLocalStorage: true,
        withJavascript: true,
      ),
    );

調用本地瀏覽器打開網頁

使用了url_launcher庫,該庫允許您打開移動平臺上的默認瀏覽器來顯示給定的URL,它在AndroidiOS上均受支持。

// 點擊首頁的 Banner 啓動手機瀏覽器
  void _launchURL(String url) async {
    if (await canLaunch(url)) {
      await launch(url);
    } else {
      throw 'Could not launch $url';
    }
  }
// 調用
    if (arr.length > 0) {
      list.add(HomeBanner(bannerStories, (story) {
        _launchURL('${story.url}');
      }));
    }

代碼閱讀頁面

使用了RichText控件,同時使用了代碼高亮工具類SyntaxHighlighterStyle去高亮代碼。

 Widget build(BuildContext context) {
    // 定義好樣式
    final SyntaxHighlighterStyle style =
        Theme.of(context).brightness == Brightness.dark
            ? SyntaxHighlighterStyle.darkThemeStyle()
            : SyntaxHighlighterStyle.lightThemeStyle();

    Widget body;
    if (_exampleCode == null) {
      body = const Center(child: CircularProgressIndicator());
    } else {
      Widget _codeWidget;
      try{
       // 使用代碼高亮
        DartSyntaxHighlighter(style).format(_exampleCode);
      // 富文本控件展示
        _codeWidget = RichText(
          text: TextSpan(
                style: const TextStyle(fontFamily: 'monospace', fontSize: 10.0),
                children: <TextSpan>[
                  DartSyntaxHighlighter(style).format(_exampleCode)
                ],),
        );
      }catch (err){
        _codeWidget = Text(_exampleCode);
      }
      body = SingleChildScrollView(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: _codeWidget,
        ),
      );
    }

點擊按鈕 Tab 頁面切換

 /// 第四個頁面點擊 回到首頁 按鈕
  _goHomePage(context) {
    Navigator.of(context)
        .pushNamedAndRemoveUntil('/home', (Route<dynamic> route) => false);
  }

本篇完~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章