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