小册 UI & 功能 编写
Nealyang opened this issue · 0 comments
前言
如上,我们完成了首页、沸点的UI及功能的编写。小册这部分我们没有涉及到的知识点或许就是头部加上横切bar的效果。本篇完成,我们将实现如下效果:
TabBar & TabBarView
横切bar的效果,我们使用widget:TabBar + TabBarView。由于我们的顶部导航栏是不确定的(从接口获取数据),所以这里我们需要新建有状态类StatefulWidget
,并且需要实例化 TabController 来设置顶部导航栏的个数。
- lib/pages/book_page.dart
@override
Widget build(BuildContext context) {
if (_navData.length == 0) {
return Center(
child: CircularProgressIndicator(),
);
}
return new Scaffold(
appBar: new AppBar(
backgroundColor: Theme.of(context).primaryColor,
title: new TabBar(
controller: _tabController,
tabs: _myTabs,
indicatorColor: Colors.white,
isScrollable: true,
),
),
body: new TabBarView(
controller: _tabController,
children: _myTabView,
),
);
}
- build 方法比较简单,一如既往的
Scaffold
作为类似前端的HTML标签,撑开整个页面架构。 - 但是这里title我们实例化了TabBar,controller是需要制定长度的一个TabController实例,tab为我们TabBar的子组件。indicatorColor 为tabBar下面当前tab的指示器。
- TabBarView也跟Tab类似,注意,TabBar里面的tabs和TabBarView里面的children是一一对应关系。
数据准备
老规矩,我们继续去封装我们当前页面所需要的数据model以及数据请求的方法,但是这里,我们需要封装两套,因为tab是一套,tabView里面数据中的cell也需要一套。也就是上面我们说的TabBar和TabBarView需要一一对应的关系。
定义数据model
- lib/model/book_nav.dart
class BookNav {
String name;
String id;
String alias;
BookNav({this.alias, this.id, this.name});
factory BookNav.fromJson(Map<String, dynamic> json) {
return BookNav(alias: json['alias'], name: json['name'], id: json['id']);
}
}
- lib/model/book_cell.dart
class BookCell {
String id;
int sectionCount;
int buyCount;
String img;
String title;
String userName;
double price;
BookCell(
{this.id,
this.buyCount,
this.img,
this.price,
this.title,
this.sectionCount,
this.userName});
factory BookCell.fromJson(Map<String, dynamic> json) {
return BookCell(
id: json['_id'],
buyCount: json['buyCount'],
img: json['img'],
title:json['title'],
price: json['price'],
sectionCount: json['section'].length,
userName: json['userData']['username']);
}
}
这里就是定义我们界面每一个豆腐块需要的数据
请求数据
在lib/api/api.dart
中,定义我们准备好的请求Url
// 小册导航
static const String BOOK_NAV = 'https://xiaoce-timeline-api-ms.juejin.im/v1/getNavList';
static const String BOOK_LIST = 'https://xiaoce-timeline-api-ms.juejin.im/v1/getListByLastTime';
- lib/util/net_util.dart
// 获取小册导航栏
static Future<List<BookNav>> getBookNavData() async {
List<BookNav> resultList = [];
var response = await NetUtils.get(Api.BOOK_NAV);
var responseList = response['d'];
for (int i = 0; i < responseList.length; i++) {
BookNav bookNav;
try {
bookNav = BookNav.fromJson(responseList[i]);
} catch (e) {
print("error $e at $i");
continue;
}
resultList.add(bookNav);
}
return resultList;
}
// 获取小册
static Future<List<BookCell>> getBookListData(
Map<String, dynamic> params) async {
List<BookCell> resultList = new List();
var response = await NetUtils.get(Api.BOOK_LIST, params: params);
var responseList = response['d'];
for (int i = 0; i < responseList.length; i++) {
BookCell bookCell;
try {
bookCell = BookCell.fromJson(responseList[i]);
} catch (e) {
print("error $e at $i");
continue;
}
resultList.add(bookCell);
}
return resultList;
}
写到这里,细心地同学是否发现,我们data_utils.dart
里面的代码有很多是重复的?是否可以抽象出来一个模子出来。当然,这里希望大家写完后仔细思考,有好的想法,欢迎在群里交流。
渲染页面
当我们拿到数据后,我们需要准备TabBar还需要准备TabBarView。
List<Tab> _myTabs = <Tab>[
Tab(
text: '全部',
)
];
List<BookPageTabView> _myTabView = <BookPageTabView>[
BookPageTabView(
alias: 'all',
)
];
TabController _tabController;
在initState
的时候,请求数据,然后设置以上三个变量的值,去重新渲染页面。
getNavList() {
DataUtils.getBookNavData().then((resultData) {
resultData.forEach((BookNav bn) {
_myTabs.add(Tab(
text: bn.name,
));
_myTabView.add(BookPageTabView(
alias: bn.alias,
));
});
if (this.mounted) {
setState(() {
_navData = resultData;
});
_tabController =
new TabController(vsync: this, length: _navData.length + 1);
}
});
}
- 注意这里我们同样在setState之前判断了下当前页面是否monted,原因和之前说的一样。
TabBarView页面
如上,我们完成book_page的外壳,下面来编写TabBarView的页面内容
在pages目录下新建 book_page_tab_view.dart
注意上面代码,我们在实例化tab_view page的时候是传递alias参数的,这也是我们请求当前tab下全部小册的重要参数。
Widget _itemBuilder(context,index){
return BookListCell(cellData: _bookList[index],);
}
@override
Widget build(BuildContext context) {
if (_bookList.length == 0) {
return Center(
child: CircularProgressIndicator(),
);
}
return ListView.builder(
itemBuilder: _itemBuilder,
itemCount: _bookList.length,
);
}
页面请求逻辑大致和上面相同,我们依然将每一本小册的cell分离出来作为一个组件
- lib/widget/book_list_cell.dart
@override
Widget build(BuildContext context) {
final Color accentColor = Theme.of(context).accentColor;
final Color primaryColor = Colors.blueAccent;
return InkWell(
onTap: () {
String url = "https://juejin.im/book/${cellData.id}";
Application.router.navigateTo(context,
"/web?url=${Uri.encodeComponent(url)}&title=${Uri.encodeComponent(cellData.title)}");
},
child: Container(
padding: EdgeInsets.symmetric(
horizontal: Util.setPercentage(0.03, context), vertical: 15.0),
decoration: BoxDecoration(
color: Colors.white,
border: Border(
bottom: BorderSide(color: accentColor, width: 0.5),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
padding: EdgeInsets.only(
right: Util.setPercentage(0.03, context),
),
child: Image.network(
cellData.img,
width: Util.setPercentage(0.2, context),
height: 100,
fit: BoxFit.contain,
),
),
Container(
width: Util.setPercentage(0.5, context),
margin: EdgeInsets.only(
right: Util.setPercentage(0.01, context)), //0.8
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
cellData.title,
style: TextStyle(
color: Color(0xFF34383B),
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
SizedBox(
height: 5.0,
),
Text(
cellData.userName,
style: TextStyle(color: Color(0xFF34383B), fontSize: 16.0),
),
SizedBox(
height: 5.0,
),
Row(
children: <Widget>[
Text(
'${cellData.sectionCount}小节',
style: TextStyle(color: accentColor),
),
InTextDot(),
Text(
'${cellData.buyCount}人已购买',
style: TextStyle(color: accentColor),
)
],
)
],
),
),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 15.0, vertical: 5.0),
decoration: BoxDecoration(
color: Color(0xFFF0F7FF),
borderRadius: BorderRadius.all(Radius.circular(15.0))),
child: Text(
'¥${cellData.price}',
style: TextStyle(color: primaryColor, fontSize: 16.0),
))
],
),
),
);
}
代码地址:flutter_juejin
总结
如上,我们完成了带有TabBar页面的UI以及功能的编写,后面活动、开源库的UI编写其实也是如此。使用的Widget基本都是我们常用的Widget,代码的编写风格也区域统一。