热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Flutter快学快用11多样式导航栏:掌握所有Flutter导航栏的设计

本课时将介绍一些比较通用的导航栏功能,并应用上一课时的知识来实现导航栏跳转对应页面的功能。导航栏样式效果目前较为常见三种导航栏功能:底部导航栏、顶部

本课时将介绍一些比较通用的导航栏功能,并应用上一课时的知识来实现导航栏跳转对应页面的功能。


导航栏样式效果

目前较为常见三种导航栏功能:底部导航栏、顶部导航栏和侧边导航栏。为了更好的界面效果,我在导航栏基础上增加了搜索功能模块的实现,完善了整个界面交互效果。我们先来看看这三种导航栏+搜索功能的运行效果。

11.gif
图 1 底部导航栏+搜索栏

22.gif
图 2 顶部导航栏+搜索栏

33.gif
图 3 侧边栏+搜索栏+底部导航栏

44.gif
图 4 侧边栏+搜索栏+顶部导航栏

上面就是本课时所需要实现的全部导航栏功能,接下来我们就逐个介绍下如何实现该功能。


导航栏实现

导航栏功能会涉及 Flutter 中几个核心点,我们使用如下表格的方式来说明,后续内容遇到相应的知识点后,可以直接对照表格 1 。

image (5).png


图 1 底部导航栏+搜索栏


可以看到上述导航栏都是 Scaffold 的一个属性,这就类似于一个架子,架子提供了很多模块。如果我们需要某些模块,只需要按照模块的格式插入数据,就可以实现相应功能。这个控件的一些参数应用具体如下。

const Scaffold({Key key,this.appBar, // 应用栏,显示在顶部,包括其中的搜索框this.body, // 页面的主题显示内容this.floatingActionButton, // 设置显示在上层区域的按钮,默认位置位于右下角this.floatingActionButtonLocation, // 设置floatingActionButton的位置this.floatingActionButtonAnimator, // floatingActionButton动画this.persistentFooterButtons, // 在底部导航栏之上的一组操作按钮this.drawer, // 左侧导航栏this.endDrawer, // 右侧导航栏this.bottomNavigationBar, // 底部导航栏this.bottomSheet, // 底部可隐藏导航栏this.backgroundColor, // 内容区域颜色this.resizeToAvoidBottomPadding, // 是否重新布局来避免底部被覆盖了,比如当键盘显示的时候,重新布局避免被键盘盖住内容。默认值为 true。this.resizeToAvoidBottomInset, //键盘弹出时是否重新绘制,以避免输入框被遮挡this.primary = true, // 是否计算手机顶部状态栏的高度this.drawerDragStartBehavior = DragStartBehavior.start, // 拖动的处理this.extendBody = false, // 是否延伸body至底部this.extendBodyBehindAppBar = false, // 是否延伸body至顶部this.drawerScrimColor, // 抽屉遮罩层背景色this.drawerEdgeDragWidth, // 滑动拉出抽屉的生效距离this.drawerEnableOpenDragGesture = true, // 确定是否可以通过拖动手势打开Scaffold.drawer, 默认情况下,拖动手势处于启用状态this.endDrawerEnableOpenDragGesture = true, // 确定是否可以使用拖动手势打开Scaffold.endDrawer,默认情况下,拖动手势处于启用状态。
})

1. 底部导航栏

根据控件 Scaffold 的说明,其中涉及 bottomNavigationBar 这个属性名,在表格 1 中有说明到该属性对应的是一个 BottomNavigationBar 组件,该组件的属性也比较多,如下所示。

BottomNavigationBar({Key key,@required this.items, // 数组,对应于BottomNavigationBarItem这个组件为菜单栏的每一项,其中包含四个属性icon、title、activeIcon和backgroundColorthis.onTap, // 点击触发逻辑,一般用来触发页面的跳转更新this.currentIndex = 0, // 当前所在的 items 数组中的位置this.elevation = 8.0, // 设置阴影效果值BottomNavigationBarType type, // fixed(固定位置)和shifting(浮动效果)Color fixedColor, // 代表选中时候的颜色,不能和selectedItemColor一起使用this.backgroundColor, // 背景颜色this.iconSize = 24.0, // icon 大小Color selectedItemColor, // 代表选中的颜色,不能和selectedItemColor一起使用this.unselectedItemColor, // 未选中时颜色this.selectedIconTheme = const IconThemeData(), // 当前选中的BottomNavigationBarItem.icon中图标的大小,不透明度和颜色this.unselectedIconTheme = const IconThemeData(), // 当前未选中的BottomNavigationBarItem.icon中图标的大小,不透明度和颜色this.selectedFontSize = 14.0, // 选中的字体大小this.unselectedFontSize = 12.0, // 未选中字体大小this.selectedLabelStyle, // 选中字体样式this.unselectedLabelStyle, // 未选中字体样式this.showSelectedLabels = true, // 是否开启选中的样式bool showUnselectedLabels, // 是否开启未选中的样式
})

介绍完一些基础属性以后,我们来尝试实现顶部导航栏功能。基于上一课时我们实现的两个页面功能,现在我们需要使用导航栏的方式来支持页面跳转。底部导航栏需要一个状态属性 indexValue 来控制导航栏显示位置,我们看下具体在 Scaffold 中的代码逻辑。

return Scaffold(appBar: AppBar(title: Text('Two You'), // 页面名字),body: Stack(children: [_getPagesWidget(0),_getPagesWidget(1),_getPagesWidget(2)],),bottomNavigationBar: BottomNavigationBar(items: [BottomNavigationBarItem(icon: Icon(Icons.people),title: Text('推荐'),activeIcon: Icon(Icons.people_outline),),BottomNavigationBarItem(icon: Icon(Icons.favorite),title: Text('关注'),activeIcon: Icon(Icons.favorite_border),),BottomNavigationBarItem(icon: Icon(Icons.person),title: Text('我'),activeIcon: Icon(Icons.person_outline),),],iconSize: 24,currentIndex: _indexNum,/// 选中后,底部BottomNavigationBar内容的颜色(选中时,默认为主题色)/// (仅当type: BottomNavigationBarType.fixed,时生效)fixedColor: Colors.lightBlueAccent,type: BottomNavigationBarType.fixed,onTap: (int index) {///这里根据点击的index来显示,非index的page均隐藏if(_indexNum != index){setState(() {_indexNum = index;});}},),
);

上面代码中,第 5 - 10 行是获取具体的页面信息,并且在 _getPagesWidget 里会判断当前 index 的值,判断当前索引 _indexNum 与 index 是否相同,相同则显示页面,不相同则页面隐藏,具体 _getPagesWidget 代码实现逻辑如下:

/// 获取页面组件
Widget _getPagesWidget(int index) {List widgetList = [router.getPageByRouter('homepage'),Icon(Icons.directions_transit),router.getPageByRouter('userpage')];return Offstage(offstage: _indexNum != index,child: TickerMode(enabled: _indexNum == index,child: widgetList[index],),);
}

上面代码中又使用到了 router 的一个新方法,该方法组件是获取对应 router 名称的组件页面信息,具体代码在 router 中实现,可以参考 github 源码,没有特殊性。

Scaffold 中代码的第 12 行开始实现底部导航栏逻辑,其中使用到了 BottomNavigationBar 控件,配置控件中的 items 属性,该属性注意是导航栏具体每一项数据,iconSize、currentIndex、fixedColor、type 和 onTap,onTap 主要是来切换页面,触发 setState ,然后重新 build 页面结构。

以上就完成了导航栏的设计,运行完以后,就可以正常进行页面切换操作。但是这里存在一些问题,比如在我们上一课时提到的外部拉起 APP 功能,如果拉起的是首页,我们不应该再去 push 一个新的页面,而是打开首页并且根据具体的页面跳转到具体的 tab 下,因此这里需要将 router 中的 push 进行修改。

我们将原来的 push 改为 open,并且对代码做了修改,具体代码如下:

/// 执行页面跳转
///
/// 需要特别注意以下逻辑

/// -1 不在首页,则执行跳转
/// 大于 -1 则为首页,需要在首页进行 tab 切换,而不是进行跳转
int open(BuildContext context, String url) {// 非entrance入口标识int notEntrancePageIndex &#61; -1;if (url.startsWith(&#39;https://&#39;) || url.startsWith(&#39;http://&#39;)) {// 打开网页Navigator.push(context, MaterialPageRoute(builder: (context) {return CommonWebViewPage(url: url);}));return notEntrancePageIndex;}Map<String, dynamic> urlParseRet &#61; _parseUrl(url);int entranceIndex &#61; routerMapping[urlParseRet[&#39;action&#39;]].entranceIndex;if (entranceIndex > notEntrancePageIndex) {// 判断为首页&#xff0c;返回切换的tab信息return entranceIndex;}Navigator.pushNamedAndRemoveUntil(context, urlParseRet[&#39;action&#39;].toString(),(route) {if (route.settings.name &#61;&#61; urlParseRet[&#39;action&#39;].toString()) {return false;}return true;}, arguments: urlParseRet[&#39;params&#39;]);// 执行跳转&#xff0c;非首页return notEntrancePageIndex;
}

上面代码与我们之前唯一的不同在于&#xff0c;判断是否在 entrance 页面&#xff0c;如果是则返回相应 tab 的 index&#xff0c;而不是直接进行跳转。如果不是则进行跳转&#xff0c;并返回一个 -1 notEntrancePageIndex。因为返回不一样&#xff0c;因此在 entrance.dart 中也需要对返回的信息做一定的处理&#xff0c;处理部分代码如下。

/// 跳转页面
void redirect(String link) {if (link &#61;&#61; null) {return;}int indexNum &#61; router.open(context, link);if (indexNum > -1 && _indexNum !&#61; indexNum) {setState(() {_indexNum &#61; indexNum;});}
}

代码主要是判断是否返回非 -1 以及两个 index 不相等&#xff0c;这时候就使用 setState 来切换导航栏 tab。


2. 顶部导航栏

在表格 1 中我们看到顶部导航栏&#xff0c;需要控件 Scaffold 属性 appBar &#xff0c;在 appBar 中设置 bottom 就可以实现顶部导航栏功能。接下来看下 bottom 的设置方法&#xff0c;代码如下&#xff1a;

return Scaffold(appBar: AppBar(title: Text(&#39;Two You&#39;), // 页面名字bottom: TabBar(controller: _controller,tabs: [Tab(icon: Icon(Icons.view_list),text: &#39;推荐&#39;,),Tab(icon: Icon(Icons.favorite),text: &#39;关注&#39;,),Tab(icon: Icon(Icons.person),text: &#39;我&#39;,),],),),body: TabBarView(controller: _controller,children: [router.getPageByRouter(&#39;homepage&#39;),Icon(Icons.directions_transit),router.getPageByRouter(&#39;userpage&#39;)],),
);

在上面代码中的第 4 到第 21 行是在设置 bottom 的 TabBar 组件。在 TabBar 中&#xff0c;包含了一个控制导航栏的 controller 和具体导航栏的配置信息的 Tabs。在代码第 22 行到第 29 行也是在配置各个 tab 对应的页面内容组件&#xff0c;这里也是通过 controller 来控制显示&#xff0c;具体 controller 控制部分代码如下。

/// 跳转页面
void redirect(String link) {if (link &#61;&#61; null) {return;}int indexNum &#61; router.open(context, link);if (indexNum > -1 && _controller.index !&#61; indexNum) {_controller.animateTo(indexNum);}
}

顶部导航栏的跳转逻辑部分和底部导航栏相似&#xff0c;这里是使用状态变量 _controller 的 animateTo 方法来处理 tab 的切换。其他部分代码改动和底部导航栏都基本一致&#xff0c;具体代码参考 github 源码。


3. 侧边导航栏

侧边栏在表格 1 中&#xff0c;可以看到使用的是 Scaffold 的 drawer 属性。该属性需要一个 Drawer 对象&#xff0c;因此我们在 Widgets 目录中创建一个 menu 目录&#xff0c;并新增 draw.dart 文件&#xff0c;具体代码如下。

import &#39;package:flutter/material.dart&#39;;
import &#39;package:two_you_friend/router.dart&#39;;
/// 左侧菜单
class MenuDraw extends StatelessWidget {/// 外部跳转final Function redirect;/// 默认构造函数const MenuDraw(this.redirect);&#64;overrideWidget build(BuildContext context) {return Drawer(child: MediaQuery.removePadding(context: context,child: ListView(children: [ListTile(leading: Icon(Icons.view_list),title: Text(&#39;推荐&#39;),onTap: () {Navigator.pop(context);redirect(&#39;tyfapp://homepage&#39;);},),ListTile(leading: Icon(Icons.favorite),title: Text(&#39;关注&#39;),onTap: () {Navigator.pop(context);Router().open(context, &#39;http://www.qq.com&#39;);},),ListTile(leading: Icon(Icons.person),title: Text(&#39;我&#39;),onTap: () {Navigator.pop(context);redirect(&#39;tyfapp://userpage&#39;);},),],),),);}
}

前 4 行是导入相应的库&#xff0c;创建 MenuDraw 类&#xff0c;类包含 redirect 方法&#xff0c;该方法就是 entrance 中声明的 tab 导航栏切换的方法&#xff0c;如果非 entrance 的切换则需要使用到 router 跳转&#xff0c;类似上面代码中的第 33 行 。

代码的第 19 行到第 44 行则为相应的左侧导航栏的配置&#xff0c;onTap 为导航栏的跳转逻辑&#xff0c;在点击相应的 Tap 以后&#xff0c;需要使用 Navigator.pop(context) 来关闭左侧导航栏。

实现完成该 MenuDraw 类后&#xff0c;我们需要在控件 Scaffold 中增加 drawer 属性&#xff0c;代码如下。

return Scaffold(appBar: AppBar(title: Text(&#39;Two You&#39;), // 页面名字),drawer: MenuDraw(redirect),...
);

上面代码的第 5 行就是新增 drawer 左侧导航栏。


4. 搜索功能

为了让功能更完善&#xff0c;我们需要增加一个右侧搜索功能&#xff0c;这里就涉及表格 1 中 AppBar 的 actions 属性&#xff0c;我们可以在 AppBar 中增加如下代码&#xff1a;

AppBar(title: Text(&#39;Two You&#39;), // 页面名字actions: [IconButton(icon: Icon(Icons.search),onPressed: () {showSearch(context: context,delegate: SearchPageCustomDelegate());},),],
)

在 actions 中可以添加一组功能按钮&#xff0c;由于这里我们只需要搜索功能按钮&#xff0c;因此在 actions 属性中添加一个 IconButton 即可。IconButton 中需要展示一个搜索 icon &#xff0c;并且点击以后前往搜索页面。

接下来我们就需要实现 SearchPageCustomDelegate 的页面逻辑&#xff0c;新增 search_page 页面&#xff0c;并在 search_page 下新建 custom_delegate.dart 文件&#xff0c;接下来实现该文件代码。

这个类需要继承 SearchDelegate &#xff0c;然后必须包含四个方法的实现逻辑&#xff0c;代码如下。

import &#39;package:flutter/material.dart&#39;;
/// 搜索框
class SearchPageCustomDelegate extends SearchDelegate {&#64;overrideList buildActions(BuildContext context) {}&#64;overrideWidget buildLeading(BuildContext context) {}&#64;overrideWidget buildResults(BuildContext context) {}&#64;overrideWidget buildSuggestions(BuildContext context) {}
}

buildActions 为右侧的图标按钮&#xff0c;一般我们可以显示一个清除搜索框内容的功能&#xff0c;我们可以使用如下代码来实现。

return [IconButton(tooltip: &#39;Clear&#39;,icon: const Icon(Icons.clear),onPressed: () {query &#61; &#39;&#39;;showSuggestions(context);},)
];

buildLeading 为左侧的按钮一般来触发返回操作&#xff0c;代码实现如下&#xff1a;

&#64;override
Widget buildLeading(BuildContext context) {return IconButton(tooltip: &#39;Back&#39;,icon: AnimatedIcon(icon: AnimatedIcons.menu_arrow,progress: transitionAnimation,),onPressed: () {close(context, null);},);
}

关闭当前页面使用 close(context, null) 即可实现。

buildResults 为搜索结果显示列表&#xff0c;buildSuggestions 为搜索提示列表&#xff0c;在这里我们返回一个空 ListView() 就行。

在上面基础上&#xff0c;我们需要修改默认的搜索框的提示&#xff0c;并且需要匹配当前主题的颜色字体等&#xff0c;需要做以下两部分逻辑。

/// 修改提示框内容
String get searchFieldLabel &#61;> &#39;用户、帖子&#39;;
&#64;override
ThemeData appBarTheme(BuildContext context) {final ThemeData theme &#61; Theme.of(context);return theme.copyWith(inputDecorationTheme: InputDecorationTheme(),primaryColor: theme.primaryColor,primaryIconTheme: theme.primaryIconTheme,primaryColorBrightness: theme.primaryColorBrightness,primaryTextTheme: theme.primaryTextTheme);
}

上面代码中第 2 行是修改默认搜索框提示&#xff0c;第 5 至第 17 行则是匹配当前应用主题。完整代码可参考 github 源码。


总结

本课时介绍了控件 Scaffold 的一些基础用法&#xff0c;着重介绍了其中三个比较常用的属性 bottomNavigationBar、appBar 和 drawer&#xff0c;同时使用这些属性完成了我们顶部导航栏、底部导航栏、侧边导航栏和搜索功能的实现。学完本课时你需要掌握这些基础的导航栏设计的使用方法&#xff0c;其次了解控件 Scaffold 的其他属性的用法。

本课时实现了 App 的基础结构&#xff0c;下一课时我将从内容展示的多样式来实现具体的 App 页面内容。

点击此链接查看本课时源码




精选评论


**华&#xff1a;

老师&#xff0c;我运行本节课的源码报错&#xff0c;请问是什么原因** BUILD FAILED **Xcode’s output:↳ lib/pages/entrance_top_bar.dart:10:1: Error: ‘Router’ is imported from both ‘package:flutter/src/widgets/router.dart’ and ‘package:two_you_friend/router.dart’.



    讲师回复&#xff1a;

    原因是 flutter 在更新版本以后&#xff0c;把 router 进行调整&#xff0c;增加进原生库了&#xff0c;因此产生了冲突&#xff0c;近几天内&#xff0c;我会重新更新一版本代码&#xff0c;适应最新版本的flutter。



**辉&#xff1a;

中间 吸顶效果怎么做



    讲师回复&#xff1a;

    参考这里的代码&#xff0c;第一部分就是吸顶效果的实现。https://api.flutter.dev/flutter/widgets/NestedScrollView-class.html



**龙&#xff1a;

可以同时实现顶部导航&#43;底部导航&#43;侧边栏么&#xff1f;



    讲师回复&#xff1a;

    可以的&#xff0c;你可以直接把源代码拿出来&#xff0c;把三部分逻辑都打开。没有任何问题的哈。



推荐阅读
  • 我正在尝试创建一个在单击按钮时出现的表单。这是输出: ... [详细]
  • Webpack5内置处理图片资源的配置方法
    本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • 本文记录了在vue cli 3.x中移除console的一些采坑经验,通过使用uglifyjs-webpack-plugin插件,在vue.config.js中进行相关配置,包括设置minimizer、UglifyJsPlugin和compress等参数,最终成功移除了console。同时,还包括了一些可能出现的报错情况和解决方法。 ... [详细]
  • 本文介绍了pack布局管理器在Perl/Tk中的使用方法及注意事项。通过调用pack()方法,可以控制部件在显示窗口中的位置和大小。同时,本文还提到了在使用pack布局管理器时,应注意将部件分组以便在水平和垂直方向上进行堆放。此外,还介绍了使用Frame部件或Toplevel部件来组织部件在窗口内的方法。最后,本文强调了在使用pack布局管理器时,应避免在中间切换到grid布局管理器,以免造成混乱。 ... [详细]
  • Python的参数解析argparse模块的学习
    本文介绍了Python中参数解析的重要模块argparse的学习内容。包括位置参数和可选参数的定义和使用方式,以及add_argument()函数的详细参数关键字解释。同时还介绍了命令行参数的操作和可接受数量的设置,其中包括整数类型的参数。通过学习本文内容,可以更好地理解和使用argparse模块进行参数解析。 ... [详细]
  • 今日份分享:Flutter自定义之旋转木马
    今日份分享:Flutter自定义之旋转木马-先上图,带你回到童年时光:效果分析子布局按照圆形顺序放置且平分角度子布局旋转、支持手势滑动旋转、快速滑动抬手继续旋转、自动旋转支持X轴旋 ... [详细]
  • 第一步:PyQt4Designer设计程序界面该部分设计类同VisvalStudio内的设计,改下各部件的objectName!设计 ... [详细]
  • 使用Flutternewintegration_test进行示例集成测试?回答首先在dev下的p ... [详细]
  • 当我在doWork方法中运行代码时,通过单击button1,进度条按预期工作.但是,当我从其他方法(即btn2,btn3)将列表传递给doWork方法时,进度条在启动后会跳转到10 ... [详细]
  • java unhandled,Eclipse编辑java文件报Unhandled event loop exception错误的解
    本人Eclipse版本是”eclipse-jee-kepler-SR2-win32-x86_64“昨天因为换电脑,所以重装了一下软件,装好eclipse ... [详细]
  • 【GO】k8s 管理系统项目的前端部分16–前端布局详解
    【GO】k8s管理系统项目[前端部分–前端布局]1.前端布局2.Layout2.1layoutsrclayoutLayout.vue ... [详细]
author-avatar
手机用户2702938842_284
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有