- (6)FirstPage
--首页
-
DisclaimerMsgState
--免责声明弹窗
首先我们看一下FirstPageState
的initState
方法中做了什么:
void initState() {
super.initState();
if (key == null) {
key = GlobalKey<DisclaimerMsgState>();
// key = const Key('__RIKEY1__');
//获取sharePre
_unKnow = _prefs.then((SharedPreferences prefs) {
//查询是否需要自动弹出
return (prefs.getBool('disclaimer::Boolean') ?? false);
});
/// 判断是否需要弹出免责声明,已经勾选过不在显示,就不会主动弹
_unKnow.then((bool value) {
new Future.delayed(const Duration(seconds: 1),(){
if (!value) {
key.currentState.showAlertDialog(context);
}
});
});
}
}
弹出免责声明showAlertDialog
:
void showAlertDialog(BuildContext context) {
showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
//title: Text('免责声明'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Container(
padding: EdgeInsets.fromLTRB(5.0, 5.0, 10.0, 10.0),
//width: 100,
height: 35,
child: Text('免责声明',
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.w700)),
decoration: BoxDecoration(
//color: Colors.blue,
image: DecorationImage(
fit: BoxFit.fitWidth,
image: AssetImage('assets/images/paimaiLogo.png')),
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
//alignment: Alignment.bottomRight,
)),
SizedBox(height: 20),
Text(disclaimerText1),
Text(disclaimerText2),
],
),
),
shape: RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(20.0)), // 圆角
actions: <Widget>[
new Container(
width: 250,
child: _create(),
)
],
);
},
);
}
这里涉及到了AlertDialog
组件大家可自行去官网学习,这里不过多阐述,我们来看_create
函数:
Row _create() {
//已读
if (_readed) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FlatButton(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: Text('已阅读知晓',
style: TextStyle(fontSize: 16, color: Colors.white)),
//可点击
color: Theme.of(context).primaryColor,
onPressed: () {
Navigator.of(context).pop();
},
),
SizedBox(
width: 10.0,
)
],
);
}
//第一次读取
return Row(mainAxisAlignment: MainAxisAlignment.spaceAround,
//crossAxisAlignment:CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Checkbox(
activeColor: Theme.of(context).primaryColor,
tristate: false,
value: _valBool,
onChanged: (bool bol) {
if(mounted) {
setState(() {
_valBool = bol;
});
}
Navigator.of(context).pop(); // here I pop to avoid multiple Dialogs
showAlertDialog(context); //here i call the same function
}),
Text('不再自动提示', style: TextStyle(fontSize: 14)),
],
),
FlatButton(
child: Text('知道了',
style: TextStyle(fontSize: 16, color: Colors.white)),
//可点击
color: _valBool
? Theme.of(context).primaryColor
: Theme.of(context).primaryColor.withAlpha(800),
onPressed: () {
// if (_valBool) {
refs(_valBool);//存储已读结果
Navigator.of(context).pop();
// }
},
),
]);
}
build
函数:没啥可值得说的,自己看代码吧。
-
ListRefresh
首页主要widget
build
函数:
Widget build(BuildContext context) {
return new RefreshIndicator(
child: ListView.builder(
itemCount: items.length + 1,
itemBuilder: (context, index) {
if (index == 0 && index != items.length) {
if(widget.headerView is Function){
return widget.headerView();//头部轮播图
}else {
return Container(height: 0);
}
}
if (index == items.length) {
//return _buildLoadText();
return _buildProgressIndicator();//底部加载更多提示
} else {
//print('itemsitemsitemsitems:${items[index].title}');
//return ListTile(title: Text("Index${index}:${items[index].title}"));
if (widget.renderItem is Function) {//列表项
return widget.renderItem(index, items[index]);
}
}
},
controller: _scrollController,
),
onRefresh: _handleRefresh,
);
}
大概分为三部分headerView
、_buildProgressIndicator
、renderItem
- 1)
headerView
(头部轮播+免责声明)
headerView(){
return
Column(
children: <Widget>[
Stack(
//alignment: const FractionalOffset(0.9, 0.1),//方法一
children: <Widget>[
Pagination(),//轮播图组件
Positioned(//方法二
top: 10.0,
left: 0.0,
child: DisclaimerMsg(key:key,pWidget:this)
),
]),
SizedBox(height: 1, child:Container(color: Theme.of(context).primaryColor)),
SizedBox(height: 10),
],
);
}
headerView
的build函数构建主要是_pageSelector
List<Widget> _pageSelector(BuildContext context) {
List<Widget> list = [];//widget列表
List<StoryModel> bannerStories = [];//数据列表
/// super.initState();
arr.forEach((item) {
//StoryModel将数据<StoryModel>装到数组里面
bannerStories.add(StoryModel.fromJson(item));
});
if (arr.length > 0) {
//将widget<HomeBanner>装入数组
list.add(HomeBanner(bannerStories, (story) {
_launchURL('${story.url}');
}));
}
return list;
}
StoryModel
就是一个数据模型不多说,主要来看HomeBanner
组件,构造方法:
final List<StoryModel> bannerStories;//数据源
final OnTapBannerItem onTap;//点击回调
HomeBanner(this.bannerStories, this.onTap, {Key key})
:super(key: key);
_BannerState
build方法:
Widget build(BuildContext context) {
return Container(
height: 226.0,//轮播图高度
child: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
PageView(
controller: controller,
onPageChanged: _onPageChanged,
children: _buildItems(),),//轮播item
_buildIndicator(), // 下面的小点
]),
);
}
-
_buildItems
:
返回一个装有wedget 的列表,列表项wedget通过_buildItem
创建:
Widget _buildItem(StoryModel story) {
return GestureDetector(//手势
onTap: () { // 按下
if (widget.onTap != null) {
widget.onTap(story);
}
},
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Image.network(story.image, fit: BoxFit.cover),//图片
_buildItemTitle(story.title), // 内容文字,大意
],),);
}
Image
是用来展示网络图片,_buildItemTitle
是构建渐变遮罩和标题文字的。没有什么难点,不阐述了。
-
_buildIndicator
构造方法(没有复杂点):
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);
}
最后再来看计时器的初始化以及响应方法:
void initState() {
super.initState();
controller = PageController(initialPage: realIndex);
timer = Timer.periodic(Duration(seconds: 5), (timer) { // 自动滚动
/// print(realIndex);
controller.animateToPage(realIndex + 1,
duration: Duration(milliseconds: 300),
curve: Curves.linear);
});
}
这代码的意思是每隔5秒进行一次动画变化,并响应_onPageChanged
方法模拟无限循环:
_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(() {});
}
- 1)
makeCard
首页列表项
构造方法:
Widget makeCard(index,FirstPageItem item){
var myTitle = '${item.title}';
var myUsername = '${'👲'}: ${item.username} ';
var codeUrl = '${item.detailUrl}';
return new ListViewItem(itemUrl:codeUrl,itemTitle: myTitle,data: myUsername,);
}
返回的是ListViewItem
实例对象没啥好说的。
- 1)
getIndexListData
首页数据获取
Future<Map> getIndexListData([Map<String, dynamic> params]) async {
const juejin_flutter = 'https://timeline-merger-ms.juejin.im/v1/get_tag_entry?src=web&tagId=5a96291f6fb9a0535b535438';
var pageIndex = (params is Map) ? params['pageIndex'] : 0;
final _param = {'page':pageIndex,'pageSize':20,'sort':'rankIndex'};
var responseList = [];
var pageTotal = 0;
try{
var response = await NetUtils.get(juejin_flutter, params: _param);
responseList = response['d']['entrylist'];
pageTotal = response['d']['total'];
if (!(pageTotal is int) || pageTotal <= 0) {
pageTotal = 0;
}
}catch(e){
}
pageIndex += 1;
List resultList = new List();
for (int i = 0; i < responseList.length; i++) {
try {
FirstPageItem cellData = new FirstPageItem.fromJson(responseList[i]);
resultList.add(cellData);
} catch (e) {
// No specified type, handles all
}
}
Map<String, dynamic> result = {"list":resultList, 'total':pageTotal, 'pageIndex':pageIndex};
return result;
}
NetUtils
为网络请求工具,内部通过Dio
三方进行网络请求并返回数据。
最后ListRefresh
通过 _getMoreData
方法进行加载数据和下拉加载更多逻辑,进行数据显示
// list探底,执行的具体事件
Future _getMoreData() async {
if (!isLoading && _hasMore) {
// 如果上一次异步请求数据完成 同时有数据可以加载
if (mounted) {
setState(() => isLoading = true);
}
//if(_hasMore){ // 还有数据可以拉新
List newEntries = await mokeHttpRequest();
//if (newEntries.isEmpty) {
_hasMore = (_pageIndex <= _pageTotal);
if (this.mounted) {
setState(() {
items.addAll(newEntries);
isLoading = false;
});
}
backElasticEffect();
} else if (!isLoading && !_hasMore) {
// 这样判断,减少以后的绘制
_pageIndex = 0;
backElasticEffect();
}
}
// 伪装吐出新数据
Future<List> mokeHttpRequest() async {
if (widget.requestApi is Function) {
final listObj = await widget.requestApi({'pageIndex': _pageIndex});
_pageIndex = listObj['pageIndex'];
_pageTotal = listObj['total'];
return listObj['list'];
} else {
return Future.delayed(Duration(seconds: 2), () {
return [];
});
}
}
首页布局已经全部完成。