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();
本篇完~