Flutter - 2 : TabBar顶部导航
接着第一部分来,这次是TabBar,这玩意简直是新闻类应用的标配。
这是方法2的图。。。当然,颜值。。要啥自行车。。
这个方法比较官方,然鹅,也很坑爹,因为你每滑动一次,它才会创建相应的Widget,也就是说没有预加载界面,导致在滑动的时候这个界面卡的简直不忍直视,然而好像也没有属性能设置成预加载模式。或许是我打开的方法不对?
class EditPage extends StatelessWidget {
final int len = 8;
final List<String> titles = ["科技", "汽车", "金融", "体育", "影视", "军事", "娱乐", "头条"];
final TextStyle selected_style = new TextStyle(
fontSize: 14.0, fontWeight: FontWeight.bold);
final TextStyle unselected_style = new TextStyle(
fontSize: 12.0, fontWeight: FontWeight.bold);
final List<Tab> tabs = new List<Tab>();
final List<NewsPage> news_pages = new List<NewsPage>();
@override
Widget build(BuildContext context) {
createTabs();
createTabVews();
return new DefaultTabController(
length: len,
initialIndex: 0,
child: new Column(
children: <Widget>[
new Container(
color: Colors.lightBlue,
child: new AspectRatio(
aspectRatio: 8.0,
child: new TabBar(
isScrollable: true,
indicatorColor: Colors.white,
indicatorWeight: 2.0,
labelStyle: selected_style,
unselectedLabelStyle: unselected_style,
tabs: tabs)),
),
new Expanded(child: new TabBarView(children: news_pages))
],
)
);
}
void createTabs(){
for(int i=0;i<len;i++){
tabs.add(new Tab(text: titles[i],));
}
}
void createTabVews(){
for(int i=0;i<len;i++){
news_pages.add(new NewsPage(i,null));
}
}
}
使用StatefulWidget
的时候,需要自己创建一个TabController
,StatelessWidget
可以用DefaultTabController
。
这个方法比较土,跟上一次一样,用Stack
和Offset
去实现显示与隐藏的效果。
2.1 :先弄个基础页面
先放一个列控件Column
,然后用AspectRantio
把导航栏高度设置为宽度的1/8,最后用Expanded
把剩下的控件占满作为body
的空间。顺便加个手势检测控件,可以加个横向滑动页面啥的。
class BodyPageA extends StatelessWidget {
final int len = 8;
@override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new AspectRatio(
aspectRatio: 8.0,
child: new Container(
color: Colors.lightBlue,
child: new NewsTabView(len)
),
),
new Expanded(
child: new GestureDetector(
child: new BodyPageStack(len),
))
],
);
}
}
2.2 :导航栏
连底部装饰条都没有的"返祖式"导航栏。它负责创建自己的tab子集,并在某一个tab被点击之后,通知上一个被选中的tab刷新为未被选中状态。
// 新闻tabView
class NewsTabView extends StatelessWidget {
final int len;
final List<Expanded> expanded_tabs = new List<Expanded>();
int _selected_position = 0;
NewsTabView(this.len);
@override
Widget build(BuildContext context) {
createExpandedTab();
onSelectedChanged();
return new Row(children: expanded_tabs,);
}
void createExpandedTab(){
List<String> titles = ["科技", "汽车", "金融", "体育", "影视", "军事", "娱乐", "头条"];
for(int i=0;i<len;i++){
bool selected = false;
// 设置0号位为默认选择位
if(i == 0){
selected = true;
}
expanded_tabs.add(new Expanded(child: new NewsTab(i, titles[i],selected)));
}
}
// 导航栏点击位置的监听
void onSelectedChanged(){
Event.event_bus.on<PageATabChanged>().listen((PageATabChanged event){
int position = event.message;
print(position);
if(_selected_position != position){
NewsTab tab = expanded_tabs[_selected_position].child;
tab.setUnselected();
_selected_position = position;
}
});
}
}
下面是TabView里面的Tab们,它们负责被点击之后改变自己的状态,并通知导航栏和body部分做出相应的变化。
// 导航tab
class NewsTab extends StatefulWidget{
final int position;
final String title;
final bool selected;
final TextStyle select_style = new TextStyle(color: Colors.redAccent,fontSize: 15.0,fontWeight: FontWeight.bold);
final TextStyle unselect_style = new TextStyle(color: Colors.white,fontSize: 12.0,fontWeight: FontWeight.bold);
_NewsTabState _state;
NewsTab(this.position, this.title,this.selected);
@override
State<StatefulWidget> createState() {
_state = new _NewsTabState(position,title,select_style,unselect_style,selected);
return _state;
}
void setUnselected(){
_state.setUnselected();
}
}
class _NewsTabState extends State<NewsTab>{
final int position;
final String title;
final TextStyle select_style;
final TextStyle unselect_style;
bool _selected = false;
_NewsTabState(this.position,this.title,this.select_style,this.unselect_style,this._selected);
@override
Widget build(BuildContext context) {
return new GestureDetector(
child: new Tab(child: new Text(title,style: (_selected ? select_style : unselect_style),),),
onTap: setSelected,
);
}
void setSelected() {
if(!_selected){
setState(() {
_selected = true;
Event.event_bus.fire(new PageATabChanged(position));
});
}
}
void setUnselected(){
setState(() {
_selected = false;
});
}
bool get selected => _selected;
set selected(bool value) {
_selected = value;
}
}
2.3 Body控件
继承StatelessWidget
,然后用它实现对tab点击位置的监听,然后控制新闻列表页面的显示与隐藏
// body页面Stack
class BodyPageStack extends StatelessWidget {
final int len;
final List<OffstagePage> page_list = new List<OffstagePage>();
int _current_position = 0;
BodyPageStack(this.len);
@override
Widget build(BuildContext context) {
createPageList();
onSelectChanged();
Stack stack = new Stack(children: page_list,);
return stack;
}
// 创建statk控件列表
void createPageList() {
for (int i = 0; i < len; i++) {
bool hide = true;
if (i == _current_position) {
hide = false;
}
page_list.add(new OffstagePage(hide,new NewsPage(i,null)));
}
}
// 修改隐藏项
void resetPageList(int position) {
page_list[_current_position].onOffsetChanged(true);
page_list[position].onOffsetChanged(false);
}
// 选项更改监听
void onSelectChanged() {
Event.event_bus.on<PageATabChanged>().listen((PageATabChanged event) {
int position = event.message;
print(position);
if(_current_position != position){
resetPageList(position);
_current_position = position;
}
});
}
}
然后是在Offstage
里面放进真正的列表页面,并为其提供隐藏与显示的方法
// 可隐藏的pageye页面
class OffstagePage extends StatefulWidget{
final bool hide;
final NewsPage page;
OffstagePage(this.hide,this.page);
_OffstagePageState _state;
@override
State<StatefulWidget> createState() {
_state = new _OffstagePageState(hide,page);
return _state;
}
void onOffsetChanged(bool hide){
_state.setState((){
_state.hide = hide;
});
}
}
class _OffstagePageState extends State<OffstagePage>{
bool _hide;
final NewsPage page;
_OffstagePageState(this._hide,this.page);
@override
Widget build(BuildContext context) {
return new Offstage(
offstage: _hide,
child: page,
);
}
bool get hide => _hide;
set hide(bool value) {
_hide = value;
}
}
2.4 最后是消息类
class PageATabChanged{
final int message;
PageATabChanged(this.message);
}