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

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

上文 我們分析了home.dart文件,這個文件主要承載了底部四個Tab頁面,以及頂部的搜索功能。今天時間來不拆解搜索功能,下篇文章再進行拆解。

這篇文章主要拆解first_page.dart的文件,也就是第一個Tab頁的實現。首頁文件的路徑如下:
'package:flutter_go/views/first_page/first_page.dart';

從項目的演示效果上可以看出第一個Tab頁主要包含

免責聲明實現

顯示時機

源碼initState方法可以看出這裏使用了 SharedPreferences庫,用於記錄是否查看過免責聲明。

如果查看過免責聲明則下次啓動不在彈出,否則彈出。

/// _unKnow 是 SP 存儲的 Boolean 值,判斷是否需要彈出免責聲明,已經勾選過不在顯示,就不會主動彈
_unKnow.then((bool value) {
 new Future.delayed(const Duration(seconds: 1),(){
   if (!value) {
      key.currentState.showAlertDialog(context);
   }
 });
});

彈窗實現

實現彈窗的文件路徑在:package:flutter_go/components/disclaimer_msg.dart

彈窗的實現是通過AlertDialog去實現的。

這裏分爲第一次 查看 以及之後在 Banner 左上角點擊後查看。

第一次查看:底部按鈕顯示 不再自動提示 文字和 知道了 文字
之後查看:底部按鈕顯示 已閱讀知曉 文字

關閉彈窗的方法:Navigator.of(context).pop();

信息流實現

實現信息流的文件路徑在:package:flutter_go/components/list_refresh.dart

從上圖可以看出ListRefresh是實現列表的關鍵,它通過構造函數傳遞了 數據集合、卡片佈局、banner參數過去。而 數據集合、卡片佈局、banner 是在first_page.dart文件中獲取

數據集合的實現
getIndexListData方法中通過接口參數的拼接,然後通過NetUtils工具去獲取數據,NetUtils工具的實現是使用了網絡請求庫Dio。獲取到返回的json數據之後,通過FirstPageItem去解析數據(FirstPageItem 中定義了數據Bean ,FirstPageItem的路徑是:package:flutter_go/views/first_page/first_page_item.dart),然後將結果添加到集合中,最後返回該結果。

卡片 Item 實現

路徑是:package:flutter_go/components/list_view_item.dart,構造方法中接收了文章地址,文章標題,作者名字

通過查看源碼可以看到卡片 Item使用的是Card Widget包裹ListTile Widget實現,利用了Card Widget提供的圓角、陰影功能,以及ListTile提供的title、subtitle、trailing來實現子佈局。
具體效果可如下:

下拉刷新

使用的是 RefreshIndicator組件

// 下拉加載的事件,清空之前list內容,取前X個
// 其實就是列表重置
  Future<Null> _handleRefresh() async {
// mokeHttpRequest 發起網絡接口請求
    List newEntries = await mokeHttpRequest();
// this.mounted 確保能夠刷新
    if (this.mounted) {
      setState(() {
        items.clear();
        items.addAll(newEntries);
        isLoading = false;
        _hasMore = true;
        return null;
      });
    }
  }

上拉加載

通過ScrollController監聽 RefreshIndicator組件是否滑動到最後一條觸發加載操作。在觸發加載操作時還進一步判斷是否有更多數據返回,沒有更多數據則顯示數據沒有更多了!!!,如果有更多數據則顯示稍等片刻更精彩..後刷新數據。

 _scrollController.addListener(() {
      // 如果下拉的當前位置到scroll的最下面
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        _getMoreData();
      }
    });

Banner 實現

headerView方法中的children: <Widget>中的Pagination實現了輪播圖效果,Pagination文件的路徑在:package:flutter_go/components/pagination.dart

Pagination中的_pageSelector中構建了HomeBanner組件,該組件構造方法中接收了Banner要顯示的數據,以及點擊的操作。

HomeBanner組件的路徑在:package:flutter_go/components/home_banner.dart
它利用PageView + PageController組件實現翻頁效果,利用Timer.periodic實現了自動滾動,在實現 無限滾動 的時候使用的技巧是在 第一個item 的前面添加上最後一條 item 的數據,在最後一條 item 的前面添加上第一條一條 item 的數據 ,代碼如下:

List<Widget> _buildItems() { // 排列輪播數組
    List<Widget> items = [];
    if (widget.bannerStories.length > 0) {
      // 頭部添加一個尾部Item,模擬循環
      items.add(
          _buildItem(widget.bannerStories[widget.bannerStories.length - 1]));
      // 正常添加Item
      items.addAll(
          widget.bannerStories.map((story) => _buildItem(story)).toList(
              growable: false));
      // 尾部
      items.add(
          _buildItem(widget.bannerStories[0]));
    }
    return items;
  }

Banner實現源碼中,爲了適配圖片是白色和標題問題白色的問題,源碼中還實現背景漸變的功能;

  Widget _buildItemTitle(String title) {
    return Container(
      decoration: BoxDecoration( /// 背景的漸變色
        gradient: LinearGradient(
          begin: Alignment.bottomCenter,
          end: const Alignment(0.0, -0.8),
          colors: [const Color(0xa0000000), Colors.transparent],
        ),
      ),
      alignment: Alignment.bottomCenter,
      child: Container(
        margin: EdgeInsets.symmetric(vertical: 22.0, horizontal: 16.0),
        child: Text(
          title, style: TextStyle(color: Colors.white, fontSize: 18.0),),),
    );
  }

輪播圖小圓點 的實現,以及頁面滑動的時候,對item的處理
由於之前做了無限輪播,所以這裏的座標需要加一層判斷

小圓點的實現
  Widget _buildIndicator() {
    List<Widget> indicators = [];
    for (int i = 0; i < widget.bannerStories.length; i++) {
      indicators.add(Container(
          width: 6.0,
          height: 6.0,
          margin: EdgeInsets.symmetric(horizontal: 1.5, vertical: 10.0),
          decoration: BoxDecoration(
              shape: BoxShape.circle,
              color: i == virtualIndex ? Colors.white : Colors.grey)));
    }
    return Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: indicators);
  }

// 頁面滑動監聽
 _onPageChanged(int index) {
    realIndex = index;
    int count = widget.bannerStories.length;
    if (index == 0) {
      virtualIndex = count - 1;
      controller.jumpToPage(count);
    } else if (index == count + 1) {
      virtualIndex = 0;
      controller.jumpToPage(1);
    } else {
      virtualIndex = index - 1;
    }
// 刷新 小圓點的 狀態
    setState(() {});
  }

這裏點擊之後是調用系統的瀏覽器打開該條Item對應的鏈接,實現如下;

  void _launchURL(String url) async {
    if (await canLaunch(url)) {
// launch(url) 啓動默認瀏覽器打開該 URL
      await launch(url);
    } else {
      throw 'Could not launch $url';
    }
  }

一些小發現

widget 指代什麼?

// 僞裝吐出新數據
  Future<List> mokeHttpRequest() async {
    if (widget.requestApi is Function) {
     ...
    } else {
    ...
    }
  }

比如widget.requestApi中的widget是從哪來的呢?
通過查看源碼,發現widget的介紹是這樣的

abstract class State<T extends StatefulWidget> extends Diagnosticable {
  /// The current configuration.
  ///
  /// A [State] object's configuration is the corresponding [StatefulWidget]
  /// instance. This property is initialized by the framework before calling
  /// [initState]. If the parent updates this location in the tree to a new
  /// widget with the same [runtimeType] and [Widget.key] as the current
  /// configuration, the framework will update this property to refer to the new
  /// widget and then call [didUpdateWidget], passing the old configuration as
  /// an argument.
  T get widget => _widget;
  T _widget;

我的理解應該是繼承了StatefulWidget的組件後,就可以在組件中通過widget去調用組件中的定義的參數。相當於Android 中 this 指代的上下文吧

養成好習慣

在頁面銷燬的時候處理掉不用的資源

  @override
  void dispose() {
    super.dispose();
    _scrollController.dispose();
  

本篇完~

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