Flutter 1.17 Widget 之 NavigationRail 初探
Flutter 1.17 版本中加入了一個新的 Widget NavigationRail ,接下來就讓我們來簡單看一下這個 Widget 如何使用。
如果 Flutter SDK 不是最新版本請通過 flutter upgrade 命令來升級 SDK 。
flutter upgrade
複製程式碼
一、NavigationRail 介紹
NavigationRail 這個元件是一個材料設計風格的元件,通常展示在應用程式的左邊或者右邊,通過這個元件來進行頁面的導航,通常來說頁面數量都是3個或者5個以內。
NavigationRail 一般都是定義為 Row 的第一個或者最後一個元素,而 Row 都是做為 Scaffold 裡面的 body 來進行佈局。
NavigationRail 這個元件是為大屏裝置佈局設計的,如桌面程式、Web 或者平板裝置等大屏裝置上,而像手機這樣的豎屏小屏裝置,一般都是使用 BottomNavigationBar 來實現導航功能。
二、典型的使用方式和頁面結構
和 BottomNavigationBar 一樣具有導航功能,但是 NavigationRail 的使用方式和 BottomNavigationBar 完全不同,不像 BottomNavigationBar 一樣,可以做為Scaffold 中的一個引數來使用 。
一般來說,想要使用 NavigationRail,都是用 Row 做為 Scaffold 的 body 元件,而 NavigationRail 做為 Row 的第一個或者最後一個元素,這樣導航就會定義在最左邊或者最右邊,如果使用了 NavigationRail,那麼剩下的佈局通常都是使用 Expanded,這樣才會佔滿螢幕的剩餘空間。
典型的頁面結構是這樣的。
當然,完全可以進一步美化,比如,這樣的。
典型的程式碼結構如下。
Scaffold(
//row 做為 body 佈局
body: Row(
children: <Widget>[
NavigationRail(
selectedIndex: _selectedIndex,
onDestinationSelected: (int index) {
setState(() {
_selectedIndex = index;
});
},
labelType: NavigationRailLabelType.selected,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.favorite_border),
selectedIcon: Icon(Icons.favorite),
label: Text('First'),
),
NavigationRailDestination(
icon: Icon(Icons.bookmark_border),
selectedIcon: Icon(Icons.book),
label: Text('Second'),
),
NavigationRailDestination(
icon: Icon(Icons.star_border),
selectedIcon: Icon(Icons.star),
label: Text('Third'),
),
],
),
VerticalDivider(thickness: 1, width: 1),
// This is the main content.
//Expanded 佔滿剩下螢幕空間
Expanded(
child: Center(
child: Text('selectedIndex: $_selectedIndex'),
),
)
],
),
);
複製程式碼
三、屬性說明
const NavigationRail({
this.backgroundColor, //背景色
this.extended = false, //是否是擴充套件狀態
this.leading, //destinations 上的 widget 佈局
this.trailing, // destinations 下方的 widget
@required this.destinations, //導航條
@required this.selectedIndex, //當前選中的頁面索引
this.onDestinationSelected, //點選了導航的圖示的回撥
this.elevation, //z 軸方向高度
this.groupAlignment, //對齊方式
this.labelType, //選擇標籤時的效果
this.unselectedLabelTextStyle, //沒有選中的標籤的樣式
this.selectedLabelTextStyle, //選中的標籤的樣式
this.unselectedIconTheme, //未被選中的標籤文字樣式
this.selectedIconTheme, //選中的標籤文字樣式
this.minWidth,
this.minExtendedWidth,
}) : assert(destinations != null && destinations.length >= 2),
assert(selectedIndex != null),
assert(0 <= selectedIndex && selectedIndex < destinations.length),
assert(elevation == null || elevation > 0),
assert(minWidth == null || minWidth > 0),
assert(minExtendedWidth == null || minExtendedWidth > 0),
assert((minWidth == null || minExtendedWidth == null) || minExtendedWidth >= minWidth),
assert(extended != null),
assert(!extended || (labelType == null || labelType == NavigationRailLabelType.none));
複製程式碼
destinations 代表了導航條,型別是 NavigationRailDestination 陣列型別,NavigationRailDestination 就代表了每一個 item 。
類似 BottomNavigationBar , NavigationRail 導航的item 也有選中和未選中的狀態,可以設定不同的樣式。
destinations: [
NavigationRailDestination(
icon: Icon(Icons.favorite_border),
selectedIcon: Icon(Icons.favorite),
label: Text('First'),
),
NavigationRailDestination(
icon: Icon(Icons.bookmark_border),
selectedIcon: Icon(Icons.book),
label: Text('Second'),
),
NavigationRailDestination(
icon: Icon(Icons.star_border),
selectedIcon: Icon(Icons.star),
label: Text('Third'),
),
],
複製程式碼
通常來說,每一個 item 都有三個元素: icon、selectedIcon 和 label 。
-
icon 未被選中狀態下的圖示(通常都是輪廓)。
-
selectedIcon 可選的,選擇狀態下的圖示。
-
label 必須要有的,圖示下方的描述文字。
四、示例
class NanigationRailMain extends StatefulWidget {
@override
NanigationRailMainState createState() => NanigationRailMainState();
}
class NanigationRailMainState extends State<NanigationRailMain> {
int _selectedIndex = 0;
final padding = 8.0;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xff28292E),
body: SafeArea(
child: Row(
children: <Widget>[
NavigationRail(
minWidth: 56.0,
groupAlignment: 1.0,
backgroundColor: Color(0xff2D3035),
selectedIndex: _selectedIndex,
onDestinationSelected: (int index) {
setState(() {
_selectedIndex = index;
});
},
labelType: NavigationRailLabelType.all,
leading: Column(
children: <Widget>[
SizedBox(
height: 8,
),
Center(
child: CircleAvatar(
radius: 16,
backgroundImage: NetworkImage(
"https://t9.baidu.com/it/u=86853839,3576305254&fm=79&app=86&size=h300&n=0&g=4n&f=jpeg?sec=1591363041&t=58576d535d450f80e9248bd0ee84c8c0"),
),
),
SizedBox(
height: 108,
),
RotatedBox(
quarterTurns: -1,
child: IconButton(
icon: Icon(Icons.tune),
color: Color(0xffFCCFA8),
onPressed: () {},
),
)
],
),
selectedLabelTextStyle: TextStyle(
color: Color(0xffFCCFA8),
fontSize: 13,
letterSpacing: 0.8,
decoration: TextDecoration.underline,
decorationThickness: 2.0,
),
unselectedLabelTextStyle: TextStyle(
fontSize: 13,
letterSpacing: 0.8,
),
destinations: [
buildRotatedTextRailDestination("Popular", padding),
buildRotatedTextRailDestination("Favourites", padding),
buildRotatedTextRailDestination("Inspiration", padding),
buildRotatedTextRailDestination("All", padding),
],
),
// This is the main content.
ContentSpace(_selectedIndex)
],
),
),
);
}
}
NavigationRailDestination buildRotatedTextRailDestination(
String text, double padding) {
return NavigationRailDestination(
icon: SizedBox.shrink(),
label: Padding(
padding: EdgeInsets.symmetric(vertical: padding),
child: RotatedBox(
quarterTurns: -1,
child: Text(text),
),
),
);
}
class ContentSpace extends StatelessWidget {
final int _selectedIndex;
ContentSpace(this._selectedIndex);
final List<String> images = [
"https://images.unsplash.com/photo-1524758631624-e2822e304c36?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80",
"https://images.unsplash.com/photo-1493663284031-b7e3aefcae8e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80",
"https://images.unsplash.com/photo-1538688525198-9b88f6f53126?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1267&q=80",
"https://images.unsplash.com/photo-1513161455079-7dc1de15ef3e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=634&q=80",
"https://images.unsplash.com/photo-1544457070-4cd773b4d71e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=843&q=80",
"https://images.unsplash.com/photo-1532323544230-7191fd51bc1b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=634&q=80",
"https://images.unsplash.com/photo-1549488344-cbb6c34cf08b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=634&q=80",
];
final List<String> titles = [
"Popular\nIdeas",
"Favourites",
"Inspiration\nIdeas",
"All"
];
@override
Widget build(BuildContext context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(24, 8, 0, 0),
child: MediaQuery.removePadding(
removeTop: true,
context: context,
child: ListView(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.calendar_today),
onPressed: () {},
),
],
),
SizedBox(
height: 24,
),
Text(titles[_selectedIndex],
style: TextStyle(
color: Color(0xffFCCFA8),
fontSize: 16,
letterSpacing: 0.8,
decoration: TextDecoration.underline,
decorationThickness: 2.0,
),
),
SizedBox(
height: 24,
),
for (var i in images) ImageCard(i),
],
),
),
),
);
}
}
class ImageCard extends StatelessWidget {
final uri;
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.fromLTRB(0, 0, 24, 24),
child: Image.network(uri),
clipBehavior: Clip.antiAliasWithSaveLayer,
elevation: 0.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
);
}
const ImageCard(this.uri);
}
複製程式碼
效果
最後
歡迎關注「Flutter 程式設計開發」微信公眾號 。