- 这篇主要介绍下应用界面的结构,以及导航相关的小部件,参考王皓的教学视频,这个教程挺好的,一步一步很详细适合我这种小白。
先来看看最终效果:
一、MaterialApp和Scaffold
MaterialApp和Scaffold是Flutter提供的两个Widget
- MaterialApp是一个方便的Widget,它封装了应用程序实现Material Design所需要的一些Widget。
- Scaffold组件是Material Design布局结构的基本实现。此类提供了用于显示drawer、snackbar和底部sheet的API。
MaterialApp
组件中提供了如下属性:
const MaterialApp({
Key key,
this.navigatorKey,
this.home,
this.routes = const <String, WidgetBuilder>{},
this.initialRoute,
this.onGenerateRoute,
this.onUnknownRoute,
this.navigatorObservers = const <NavigatorObserver>[],
this.builder,
this.title = '',
this.onGenerateTitle,
this.color,
this.theme,
this.locale,
this.localizationsDelegates,
this.localeListResolutionCallback,
this.localeResolutionCallback,
this.supportedLocales = const <Locale>[Locale('en', 'US')],
this.debugShowMaterialGrid = false,
this.showPerformanceOverlay = false,
this.checkerboardRasterCacheImages = false,
this.checkerboardOffscreenLayers = false,
this.showSemanticsDebugger = false,
this.debugShowCheckedModeBanner = true,
})
首先,我们在main.dart
中引用import 'package:flutter/material.dart'
并返回一个MaterialApp
类型app,设置title
、主题样式theme
,根视图home
等:
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
debugShowCheckedModeBanner: false, // 不展示右上角"DEBUG"横幅
title: 'Olive',
theme: ThemeData(
primaryColor: Colors.yellow[300], // 主题颜色
highlightColor: Color.fromRGBO(255, 255, 255, 0.5),
splashColor: Colors.white70,
),
home: MyTabController(), // 根视图导航控制器
);
}
}
其中MyTabController
是自定义的一个dart
组件,作为App的根视图控制器⤵️
二、DefaultTabController
新建一个MyTabController.dart
文件,返回一个有状态控件DefaultTabController
:
import 'package:flutter/material.dart';
import '../pages/HomePage.dart';
import '../pages/LeftDrawerPage.dart';
import 'BottomBarPage.dart';
class MyTabController extends StatefulWidget {
@override
createState() => new MyTabState();
}
class MyTabState extends State<MyTabController> {
int _navIndex = 0;
// 储存切换bottomNavigationBar时的四个界面
var _body = [
TabBarView(
children: <Widget>[
HomePage(),
Icon(Icons.local_florist, size: 128.0, color: Colors.black12,),
]
),
Icon(Icons.local_airport, size: 128.0, color: Colors.black12,),
Icon(Icons.local_activity, size: 128.0, color: Colors.black12,),
Icon(Icons.local_cafe, size: 128.0, color: Colors.black12,)
];
void navChange(int index) {
setState(() {
_navIndex = index;
});
}
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
backgroundColor: Colors.grey[100],
appBar: _myBar(),
body: _body[_navIndex],
drawer: LeftDrawer(),
bottomNavigationBar: BottomBar(navChange),
),
);
}
}
其中DefaultTabController
的child是Scaffold
组件。
在该组件中,通过backgroundColor
设置界面背景色;_myBar()
为自定义的AppBar
类型组件,用于配置导航栏相关;drawer
为左侧滑抽屉组件,若想从右侧划出,则使用endDrawer
组件。
LeftDrawer()
为自定义的Drawer
组件,BottomBar()
为底部导航栏,在下面都会讲到。
- AppBar
上面的_myBar()
是一个AppBar
类型组件,该组件有五大部分:
提供了如下属性:
AppBar({
Key key,
this.leading, // 左上角控件,一般放置一个icon
this.automaticallyImplyLeading = true,
this.title, // 标题
this.actions, // 右上角一系列组件
this.flexibleSpace, // 导航栏与bottom间的间隙,见上图
this.bottom, // 底部控件,位置见上图
this.elevation = 4.0, // 阴影Z轴
this.backgroundColor, // 背景色
this.brightness, // 状态栏亮度
this.iconTheme, // 图标样式
this.textTheme, // 文字样式
this.primary = true,
this.centerTitle, // 标题是否居中显示,默认值根据不同的操作系统,显示方式不一样
this.titleSpacing = NavigationToolbar.kMiddleSpacing,
this.toolbarOpacity = 1.0,
this.bottomOpacity = 1.0,
})
在本示例中,代码如下:
Widget _myBar () {
return AppBar(
title: Text('Olive'),
elevation: 5.0, // 阴影
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
tooltip: 'Search',
onPressed: () => debugPrint('Search button is pressed')
),
], // 导航栏右侧按钮
bottom: TabBar(
tabs: <Widget>[
Tab(icon: Icon(Icons.home)),
Tab(icon: Icon(Icons.local_florist)),
],
indicatorColor: Colors.black54,
indicatorSize: TabBarIndicatorSize.label,
unselectedLabelColor: Colors.black38,
), // 导航栏下方的选项卡
);
}
若没有自定leading
左上角控件,且有drawer
左侧抽屉,则默认会创建左上角按钮,且点击事件为展开左侧抽屉。
若要使用自定义左侧按钮来打开抽屉,可使用Scaffold.of(context).openDrawer()
方法,具体如下:
leading: new Builder(builder: (BuildContext context){
return IconButton(
icon: ClipRRect(
child: Image.asset('images/nav_user.png', fit: BoxFit.contain, width: 28, height: 28),
borderRadius: BorderRadius.circular(28/2),
),
onPressed: (){
Scaffold.of(context).openDrawer();
},
);
}),
- TabBar和TabBarView
TabBar
组件为横向标签页,一般结合TabBarView
来使用。
TabBar
有如下属性:
const TabBar({
Key key,
@required this.tabs, // 一系列Tab对象,当然也可以是其他的Widget
this.controller, // TabController对象
this.isScrollable = false, // 是否可滚动
this.indicatorColor, // 指示器颜色
this.indicatorWeight = 2.0, // 指示器厚度
this.indicatorPadding = EdgeInsets.zero, // 底部指示器的内间距
this.indicator, // 指示器decoration,例如边框等
this.indicatorSize, // 指示器大小计算方式
this.labelColor, // 选中Tab文字颜色
this.labelStyle, // 选中Tab文字Style
this.labelPadding, // 文字内间距
this.unselectedLabelColor, // 未选中Tab中文字颜色
this.unselectedLabelStyle, // 未选中Tab中文字style
})
本示例中,在上面_myBar()
代码中,给TabBar添加了两个按钮:
bottom: TabBar(
tabs: <Widget>[
Tab(icon: Icon(Icons.home)),
Tab(icon: Icon(Icons.local_florist)),
],
indicatorColor: Colors.black54,
indicatorSize: TabBarIndicatorSize.label,
unselectedLabelColor: Colors.black38,
),
在DefaultTabController
中设置了TabBarView
,控制每个选项卡具体展示的页面,点击第一个选项展示HomePage
界面,第二个选项卡展示一个Icon,其中HomePage
为一个List,这里就不具体说了:
TabBarView(
children: <Widget>[
HomePage(),
Icon(Icons.local_florist, size: 128.0, color: Colors.black12,)
]
),
三、LeftDrawerPage
新建LeftDrawerPage.dart
文件,用于侧滑Drawer
布局。废话不多说,直接上代码:
import 'package:flutter/material.dart';
class LeftDrawer extends StatelessWidget {
final String avatarUrl = 'https://upload.jianshu.io/users/upload_avatars/2650319/becf3e53-9113-43e5-8241-de68bcf8b15f.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240';
final String headerBgImgUrl = 'https://images.unsplash.com/photo-1548693316-8ec65a5f2192?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1225&q=80';
List<Icon> _iconItems = <Icon>[
Icon(Icons.message, color: Colors.black12, size: 22,),
Icon(Icons.favorite, color: Colors.black12, size: 22,),
Icon(Icons.settings, color: Colors.black12, size: 22,),
];
List<String> _titleItems = <String>[
'Message', 'Favorite', 'Settings'
];
Widget _listItemBuilder(BuildContext context, int index) {
return new ListTile(
title: Text(
_titleItems[index],
textAlign: TextAlign.right,
),
trailing: _iconItems[index],
onTap: () => Navigator.pop(context),
);
}
Widget _listHeaderBuilder() {
return new UserAccountsDrawerHeader(
accountName: Text(
'Olive',
style: TextStyle(fontWeight: FontWeight.bold),
),
accountEmail: Text('461485900@qq.com'),
currentAccountPicture: CircleAvatar(
backgroundImage: NetworkImage(avatarUrl),
),
decoration: BoxDecoration(
color: Colors.yellow[400],
image: DecorationImage(
image: NetworkImage(headerBgImgUrl),
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(
Colors.yellow[400].withOpacity(0.6),
BlendMode.hardLight
),
)
),
);
}
@override
Widget build(BuildContext context) {
List<Widget> _list = new List();
_list.add(_listHeaderBuilder());
for (int i = 0; i < _titleItems.length; i++) {
_list.add(_listItemBuilder(context, i));
}
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: _list,
),
);
}
}
示例中Drawer
的child中其实是个ListView
,具体用法在上一篇文章中有记录。这里要提的是,竟然有UserAccountsDrawerHeader
这种组件!真的很方便,不知道是不是我孤落寡闻哈哈~
UserAccountsDrawerHeader
能快速设置用户头像、用户名、Email 等信息,显示一个符合纸墨设计规范的 drawer header。
四、BottomNavigationBar底部导航栏
创建BottomBarPage.dart
文件,代码如下:
import 'package:flutter/material.dart';
class BottomBar extends StatefulWidget {
ValueChanged<int> _didClickNav;
BottomBar(this._didClickNav);
@override
createState() => new BottomBarState(_didClickNav);
}
class BottomBarState extends State<BottomBar> {
int _currentIndex = 0;
ValueChanged<int> _didClickNav;
BottomBarState(this._didClickNav);
void _onTapHandler (int index) {
setState(() {
_currentIndex = index;
_didClickNav(index);
});
}
@override
Widget build(BuildContext context) {
return BottomNavigationBar(
type: BottomNavigationBarType.fixed,
fixedColor: Colors.black,
currentIndex: _currentIndex,
onTap: _onTapHandler,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.explore),
title: Text('Explore')
),
BottomNavigationBarItem(
icon: Icon(Icons.history),
title: Text('History')
),
BottomNavigationBarItem(
icon: Icon(Icons.list),
title: Text('List')
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
title: Text('My')
),
],
);
}
}