底部四個切換導航
它分爲首頁,問答,視頻和我的四大模塊
創建lib/home/home.dart首頁文件,使用的是bottomNavigationBar組件,官網也有介紹
它有一個onTap函數,這個函數會有一個index下標參數,同時準備四個模塊頁面news.dart,question.dart,video.dart和user.dart;裏面的切換過程:定義上面四個頁面數組,在body屬性中將動態下標傳遞過去,在事件裏更改這個下標即可,同時加上currentIndex動態屬性點擊效果,注意的是要用StatefulWidget組件.
新聞列表頁
頂部搜索
在news/news.dart文件中
頂部的搜索是寫入的樣式,實際點擊是進入另一個頁面的,具體實現步驟:在AppBar組件的title中給一個自定義SearchBox組件,在SearchBox.dart組件中寫我們的搜索內容:
頭部tabBar
使用的是DefaultTabController組件進行重新包裝,裏面放我們的Scaffold組件,具體如下:
import 'package:flutter/material.dart';
class TabBarBtn extends StatelessWidget {
final List channel;
TabBarBtn(this.channel);
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: TabBar(
labelColor: Colors.black,
unselectedLabelColor: Colors.black45,
labelStyle: TextStyle(
fontSize: 14.0
),
indicatorColor: Colors.blueAccent,
indicatorWeight: 3.0,
indicatorSize: TabBarIndicatorSize.label,
labelPadding: EdgeInsets.symmetric(horizontal: 20.0),
isScrollable: true,
tabs: channel.map((value){
return Tab(text: value['name'],);
}).toList()
),
);
}
}
主頁新聞列表news,tabContent
news.dart
class News extends StatefulWidget {
@override
_NewsState createState() => _NewsState();
}
class _NewsState extends State<News> {
List channels = [];
_getChannels () async{
print('關閉之後刷新復機');
var data = await PubMoudle.httpRequest('get', '/getchannels');
// print(data.data['data']['channels']);
setState(() {
channels = data.data['data']['channels'];
});
}
@override
void initState() {
super.initState();
_getChannels();
}
@override
Widget build(BuildContext context) {
return channels.length == 0 ?SizedBox():DefaultTabController(
length: channels.length,
child: Scaffold(
appBar: AppBar(
title: SearchBox(),
elevation: 0.0,
bottom: PreferredSize(
preferredSize: Size.fromHeight(50.0),
child: TabBarBtn(channels)
)
),
body: TabBarView(
children: channels.map((value){
return TabBarContent(value['id']);
}).toList()
),
drawer: DrawerList(_getChannels),
),
);
}
}
tabContent.dart
使用listView組件,橫線使用SizeBox,圖片是使用Row結合網絡圖片,AspectRadio組件可以讓子組件等比縮放,第三個Row左文字右圖片方式佈局,左文字使用靈活佈局Expanded佈局,圖片使用SizeBox限制固定大小.
class TabBarContent extends StatefulWidget {
final int id;
TabBarContent(this.id);
@override
_TabBarContentState createState() => _TabBarContentState();
}
class _TabBarContentState extends State<TabBarContent> {
List<Article> _list = [];
int page = 1;
ScrollController _controller = ScrollController();
_getData([type]) async{
var data = await PubMoudle.httpRequest('post', '/getarticles', {'id': widget.id, 'page': page});
print(data.data['data']['results']);
List jsonlist = data.data['data']['results'];
List<Article> listData = jsonlist.map((value) => Article.fromJson(value)).toList();
if(type == 1){
setState(() {
_list.addAll(listData);
});
}else{
setState(() {
_list = listData;
});
}
}
Future _refresh() async{
//走接口
_getData();
// setState(() {
// });
}
@override
void initState() {
super.initState();
_getData();
_controller.addListener((){
var maxScroll = _controller.position.maxScrollExtent;
var pixels = _controller.position.pixels;
if(maxScroll == pixels){
//s刷新了
_getData(1);
}
});
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: _refresh,
child: Padding(
padding: EdgeInsets.all(15.0),
child: ListView.builder(
itemCount: _list.length,
itemBuilder: (context, index){
return GestureDetector(
onTap: (){
Navigator.push(context, MaterialPageRoute(
builder: (context) => DetailPage(_list[index].artId)
));
},
child: NewsItem(_list[index]),
);
},
controller: _controller,
),
),
);
}
}
class NewsItem extends StatelessWidget {
final Article article;
NewsItem(this.article);
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
article.imgType == 1?Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: Text(
article.title,
style: TextStyle(
color: Colors.black,
fontSize: 18.0
),
),
),
//圖片
SizedBox(
width: 100.0,
height: 100.0,
child: Image.network(
article.images[0],
fit: BoxFit.cover,
),
)
],
): Text(
article.title,
style: TextStyle(
color: Colors.black,
fontSize: 18.0
),
),
SizedBox(height: 6.0,),
article.imgType == 3 ? Row(
children: article.images.map((value){
return Expanded(
child: AspectRatio(
aspectRatio: 4/3,
child: Image.network(
value,
fit: BoxFit.cover,
),
),
);
}).toList()
): SizedBox(height: 6.0,),
RichText(
text: TextSpan(
text: '${article.isTop==1?"置頂 ":""}',
style: TextStyle(
color: Colors.red,
),
children: [
TextSpan(
text: '${article.autName} ',
style: TextStyle(
color: Colors.grey,
),
),
TextSpan(
text: '${article.commCount}評論 ',
style: TextStyle(
color: Colors.grey,
),
),
TextSpan(
text: timeago.format(DateTime.parse(article.pubdate)),
style: TextStyle(
color: Colors.grey,
),
)
]
),
),
Divider(height: 30.0,),
],
);
}
}
渲染tab數據
在news.dart裏寫一個獲取數據的方_getChannels法,在初始化聲明週期裏調用這個方法,將數據放在TabBarBtn和TabBarContent裏,因爲第一次調用是空數據,所以做了三元判斷加載DefaultTabController,在map數組循環裏用toList()轉換
渲染新聞內容
根據上面tab個數渲染TabBarContent,將id傳遞過來,因爲涉及到數據所以需要改用StatefulWidget,在生命週期裏調用新聞數據_getData方法,同一頁面獲取別的組件id參數獲取方法用widget.id,在響應體裏判斷是否字符串和數字的方法,使用序列化,創建article.dart文件,裏面用類似構造函數的方法,在dart中跟類寫法很像,裏面用formJson方法用json進行處理,在響應體裏調用,用List listData保留起來,最後將數據放到頁面中,在數據定義的時候使用list
下拉刷新
使用RefreshIndicator組件,把要刷新的組件寫在裏面,同時給一個方法,這個方法要用Future聲明,同時要用異步寫法,其實很簡單.
上拉加載更多
定義一個controller方法,用ScrollController定義,在初始化生命週期裏添加監聽,監聽最大上拉值和pixels值,相等代表刷新,根據每次page++在_getData裏使用addAll()方法添加數據.
時間格式化(裏面的多少時間前)
使用的是timego2.0插件,可以將正常時間轉爲多少時間前.
最終效果: