GIF:
homepage.gif
UI如图:
homepage.png
需求分析:
- 主页显示动态名字且居中,跳转到抽屉的图标
- 图片展示,手动切换,指示器,自动轮播,点击可以跳转
- 整体滑动 ,根据滑动的位置改变主页名字(参考最后演示的GIF)
UI拆解并实现:
appBar: new AppBar(
title: new Text('$_title'),//动态改变title
centerTitle: true, // 居中
), //头部的标题AppBar
拆解1:图片展示
Widget _buildItem(BuildContext context, int index) {
HotNewsTopStoriesModel item = _fakeList[index];
return new GestureDetector(
onTap: () {
RouteUtil.route2Detail(context, '${item.id}'); // 通过路由跳转到详情
},
child: new FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
image: item.image,
height: widget._homeBannerHeight,
fit: BoxFit.fitWidth),
);
}
拆解2:手动滑动
new PageView.builder(
controller: _pageController,
itemBuilder: (BuildContext context, int index) {
return _buildItem(context, index);
},
itemCount: _fakeList.length,
onPageChanged: (index) {
_changePage(index);
},
)
拆解3:轮播(向左向右都可以“无限”滚动)
//用于做banner循环
_initFakeList() {
for (int i = 0; i < fakeLength; i++) {
_fakeList.addAll(widget.topList);
}
}
拆解4:自动轮播,以及手动与自动的判定
//通过时间timer做轮询,达到自动播放的效果
initTimer() {
_timer = new Timer.periodic(_bannerDuration, (timer) {
if(_isEndScroll){
_pageController.animateToPage(_curPageIndex + 1,
duration: _bannerAnimationDuration, curve: Curves.linear);
}
});
}
//检查手指和自动播放的是否冲突,如果滚动停止开启自动播放,反之停止自动播放
return new NotificationListener(
onNotification: (ScrollNotification scrollNotification) {
if (scrollNotification is ScrollEndNotification || scrollNotification is UserScrollNotification) {
_isEndScroll = true;
} else {
_isEndScroll = false;
}
return false;
},
........
........
拆解5:创建指示器
//创建指示器
Widget _buildIndicators() {
_initIndicators();
return new Align(
alignment: Alignment.bottomCenter,
child: new Container(
color: Colors.black45,
height: 20.0,
child: new Center(
child: new SizedBox(
width: widget.topList.length * 16.0,
height: 5.0,
child: new Row(
children: _indicators,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
),
),
)),
);
}
_initIndicators() {
_indicators.clear();
for (int i = 0; i < widget.topList.length; i++) {
_indicators.add(new SizedBox(
width: 5.0,
height: 5.0,
child: new Container(
color: i == _curIndicatorsIndex ? Colors.white : Colors.grey,
),
));
}
}
拆解6:完成组件整合
Widget _buildBanner() {
return new Container(
height: widget._homeBannerHeight,
//指示器覆盖在pagerview上,所以用Stack
child: new Stack(
children: <Widget>[
_buildPagerView(),
_buildIndicators(),
],
),
);
}
Widget _buildNormalItem(HotNewsStoriesModel item) {
final String imgUrl = item.images[0];
final String title = item.title;
final int id = item.id;
return new InkWell(
onTap: () {
RouteUtil.route2Detail(context, '$id');
},
child: new Padding(
padding: const EdgeInsets.only(left: 12.0, right: 12.0),
child: new SizedBox(
height: Constant.normalItemHeight,
child: new Column(
children: <Widget>[
new Row(
children: <Widget>[
new Expanded(
child: new Text(
title,
style: new TextStyle(
fontSize: 16.0, fontWeight: FontWeight.w300),
),
),
new Padding(
padding: const EdgeInsets.all(8.0),
child: new SizedBox(
height: 80.0,
width: 80.0,
child: new Image.network(imgUrl),
),
)
],
),
new Expanded(
child: new Align(
alignment: Alignment.bottomCenter,
child: CommonDivider.buildDivider(),
),
),
],
),
)));
}
拆解2:日期Item
Widget _buildDateTimeItem(HotNewsStoriesModel item) {
final String dateTime = item.curDate;
return new Container(
color: Colors.blue,
height: Constant.dateTimeItemHeight,
child: new Center(
child: new Text(
dateTime,
style: new TextStyle(
fontSize: 16.0, fontWeight: FontWeight.w300, color: Colors.white),
),
),
);
}
拆解3:ListView组装items
//根据type组装数据
Widget _buildItem(BuildContext context, int index) {
final HotNewsStoriesModel item = _normalDatas[index];
Widget widget;
switch (item.itemType) {
case HotNewsStoriesModel.itemTypeBanner:
widget = new HomeBanner(_topDatas, Constant.bannerHeight);
break;
case HotNewsStoriesModel.itemTypeNormal:
widget = _buildNormalItem(item);
break;
case HotNewsStoriesModel.itemTypeDate:
widget = _buildDateTimeItem(item);
break;
}
return widget;
}
content = new ListView.builder(
//设置physics属性总是可滚动
physics: AlwaysScrollableScrollPhysics(),
controller: _scrollController,
itemCount: _normalDatas.length,
itemBuilder: _buildItem,
);
拆解4:刷新
var _refreshIndicator = new NotificationListener(
onNotification: _onNotification,
child: new RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: _refreshData,
child: content,
),
);
拆解5:加载更多
void _scrollListener() {
//滑到最底部刷新
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_loadData();
}
}
拆解5: 动态改变title
//由于暂时没找到监听滑动到某个具体Item的方法,所以用了个很原始方法计算 ,根据位移和Items的高度进行判断
_computeShowtTitle(double offset) {
//滑动监听改变title
if (_dateTimeOffsetList.isNotEmpty) {
if (offset.round() < _dateTimeOffsetList[0]) {
_title = Constant.todayHot;
setState(() {});
return;
}
for (int i = 0; i < _dateTimeOffsetList.length; i++) {
if (i != _dateTimeOffsetList.length - 1) {
if (offset.round() >= _dateTimeOffsetList[i].round() &&
_dateTimeOffsetList[i + 1].round() >= offset.round()) {
String dateTime = DateUtil.formatDateWithWeek(
_dateTimeList[i].subtract(new Duration(days: 1)));
if (dateTime != _title) {
setState(() {
_title = dateTime;
});
}
break;
}
} else {
if (offset.round() >= _dateTimeOffsetList[i].round() &&
_dateTimeOffsetList[i].round() >= offset.round()) {
String dateTime = DateUtil.formatDateWithWeek(
_dateTimeList[i].subtract(new Duration(days: 1)));
if (dateTime != _title) {
setState(() {
_title = dateTime;
});
}
break;
}
}
if (i != _dateTimeOffsetList.length - 1) {
if (offset.round() < _dateTimeOffsetList[i + 1].round() &&
offset.round() > _dateTimeOffsetList[i].round()) {
String dateTime = DateUtil.formatDateWithWeek(_dateTimeList[i]);
if (dateTime != _title) {
setState(() {
_title = dateTime;
});
}
break;
}
} else {
if (offset.round() < _dateTimeOffsetList[i].round() &&
offset.round() > _dateTimeOffsetList[i].round()) {
String dateTime = DateUtil.formatDateWithWeek(_dateTimeList[i]);
if (dateTime != _title) {
setState(() {
_title = dateTime;
});
}
break;
}
}
}
}
}
每天学一点,学到Flutter发布正式版!
网友评论