我正在使用BottomNavigationBar和TabController。 通过单击BottomNavigationBar的不同选项卡,TabView正在更改内容。 但是,如果我在TabView上滑动以切换到另一个视图/选项卡,则BottomNavigationBar不会更新到我刷到的选项卡。 我已经在TabController中添加了一个监听器来检测更改。 但是,如何以编程方式更新BottomNavigationBar以反映更改?
答案 0 :(得分:3)
我认为在您的情况下使用PageView
代替TabBarView
会更优雅。
class BottomBarExample extends StatefulWidget {
@override
_BottomBarExampleState createState() => new _BottomBarExampleState();
}
class _BottomBarExampleState extends State<BottomBarExample> {
int _page = 0;
PageController _c;
@override
void initState(){
_c = new PageController(
initialPage: _page,
);
super.initState();
}
@override
Widget build(BuildContext context) {
return new Scaffold(
bottomNavigationBar: new BottomNavigationBar(
currentIndex: _page,
onTap: (index){
this._c.animateToPage(index,duration: const Duration(milliseconds: 500),curve: Curves.easeInOut);
},
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(icon: new Icon(Icons.supervised_user_circle), title: new Text("Users")),
new BottomNavigationBarItem(icon: new Icon(Icons.notifications), title: new Text("Alerts")),
new BottomNavigationBarItem(icon: new Icon(Icons.email), title: new Text("Inbox")),
],
),
body: new PageView(
controller: _c,
onPageChanged: (newPage){
setState((){
this._page=newPage;
});
},
children: <Widget>[
new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Icon(Icons.supervised_user_circle),
new Text("Users")
],
),
),
new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Icon(Icons.notifications),
new Text("Alerts")
],
),
),
new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Icon(Icons.mail),
new Text("Inbox")
],
),
),
],
),
);
}
}
答案 1 :(得分:1)
完整答案在这里:https://stackoverflow.com/a/63258130/10563627
首先创建一个课程MyBottomBarDemo
class MyBottomBarDemo extends StatefulWidget {
@override
_MyBottomBarDemoState createState() => new _MyBottomBarDemoState();
}
class _MyBottomBarDemoState extends State<MyBottomBarDemo> {
int _pageIndex = 0;
PageController _pageController;
List<Widget> tabPages = [
Screen1(),
Screen2(),
Screen3(),
];
@override
void initState(){
super.initState();
_pageController = PageController(initialPage: _pageIndex);
}
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("BottomNavigationBar", style: TextStyle(color: Colors.white)),
backgroundColor: Colors.deepPurple,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _pageIndex,
onTap: onTabTapped,
backgroundColor: Colors.white,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem( icon: Icon(Icons.home), title: Text("Home")),
BottomNavigationBarItem(icon: Icon(Icons.mail), title: Text("Messages")),
BottomNavigationBarItem(icon: Icon(Icons.person), title: Text("Profile")),
],
),
body: PageView(
children: tabPages,
onPageChanged: onPageChanged,
controller: _pageController,
),
);
}
void onPageChanged(int page) {
setState(() {
this._pageIndex = page;
});
}
void onTabTapped(int index) {
this._pageController.animateToPage(index,duration: const Duration(milliseconds: 500),curve: Curves.easeInOut);
}
}
然后创建一个屏幕
class Screen1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.green,
child: Center(child: Text("Screen 1")),
);
}
}
screen 2... Screen 3...
答案 2 :(得分:0)
在TabView上滑动时,您应该更新bottomNavigationBar的当前索引以匹配tabview的新索引
data a;
array a(5) a5 - a1;
a1=1 ;a3=2;
want = whichn(coalesce(of a[*]), of a[*]);
run;
答案 3 :(得分:0)
关于BottomNavigationBar的另一种观点
许多人甚至官方文档面临额外导航的方式与 Web Navigation 不同。它们不会呈现具有可在单击后执行的可用链接的通用和/或部分通用模板。相反,他们正在实现的是在附加的“tabs_screen”中预加载不同的屏幕(基本上只是一个额外的 Scaffold 包装和渲染一个屏幕),然后,根据 onTap 上的给定索引,甚至是 BottomNavigationBar,您将使用该索引来确定将在该脚手架内实际呈现哪个屏幕。仅此而已。
相反,我建议以这种方式处理额外导航:
我像往常一样添加了底部导航栏,但我没有在列表页面上预加载/实例化屏幕,我所做的只是为收藏夹屏幕添加另一个命名路由,
我设计了我的底部导航栏:
bottomNavigationBar: BottomNavigationBar(
onTap: (index) => onTapSelectNavigation(index, context),
items: [
BottomNavigationBarItem(
icon: Icon(
Icons.category,
),
label: '',
activeIcon: Icon(
Icons.category,
color: Colors.red,
),
tooltip: 'Categories',
),
BottomNavigationBarItem(
icon: Icon(
Icons.star,
),
label: '',
activeIcon: Icon(
Icons.star,
color: Colors.red,
),
tooltip: 'Favorites',
),
],
currentIndex: _activeIndex,
),
然后在 onTap 上甚至我调用了 onTapSelectNavigation 方法。
在该方法中,根据给定的点击标签索引,我将决定我将调用哪个路由。此BottomNavigationBar 可用于整个应用程序。为什么?因为我在我的 FeeddyScaffold 上实现了这个,这对于所有的屏幕都是通用的,因为 FeeddyScaffold 将所有这些屏幕上的所有内部小部件都包装起来。在每个屏幕上,我都会实例化 FeeddyScaffold,并传递一个小部件列表作为命名参数。这样我保证脚手架对于每个屏幕都是通用的,如果我实现了那个公共脚手架的额外导航,那么它就可以用于屏幕。这是我的 FeeddyScaffold 组件:
// Packages:
import 'package:feeddy_flutter/_inner_packages.dart';
import 'package:feeddy_flutter/_external_packages.dart';
// Screens:
import 'package:feeddy_flutter/screens/_screens.dart';
// Models:
import 'package:feeddy_flutter/models/_models.dart';
// Components:
import 'package:feeddy_flutter/components/_components.dart';
// Helpers:
import 'package:feeddy_flutter/helpers/_helpers.dart';
// Utilities:
import 'package:feeddy_flutter/utilities/_utilities.dart';
class FeeddyScaffold extends StatefulWidget {
// Properties:
final bool _showPortraitOnly = false;
final String appTitle;
final Function onPressedAdd;
final String objectName;
final int objectsLength;
final List<Widget> innerWidgets;
final int activeIndex;
// Constructor:
const FeeddyScaffold({
Key key,
this.appTitle,
this.onPressedAdd,
this.objectName,
this.objectsLength,
this.innerWidgets,
this.activeIndex,
}) : super(key: key);
@override
_FeeddyScaffoldState createState() => _FeeddyScaffoldState();
}
class _FeeddyScaffoldState extends State<FeeddyScaffold> {
final bool _showPortraitOnly = false;
int _activeIndex;
@override
void initState() {
// TODO: implement initState
super.initState();
_activeIndex = widget.activeIndex;
}
void onTapSelectNavigation(int index, BuildContext context) {
switch (index) {
case 0:
Navigator.pushNamed(context, FoodCategoryIndexScreen.screenId);
break;
case 1:
Navigator.pushNamed(context, FavoritesScreen.screenId);
break;
}
}
@override
Widget build(BuildContext context) {
AppData appData = Provider.of<AppData>(context, listen: true);
Function closeAllThePanels = appData.closeAllThePanels; // Drawer related:
bool deviceIsIOS = DeviceHelper.deviceIsIOS(context);
// WidgetsFlutterBinding.ensureInitialized(); // Without this it might not work in some devices:
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
// DeviceOrientation.portraitDown,
if (!_showPortraitOnly) ...[
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
],
]);
FeeddyAppBar appBar = FeeddyAppBar(
appTitle: widget.appTitle,
onPressedAdd: widget.onPressedAdd,
objectName: widget.objectName,
);
return Scaffold(
appBar: appBar,
onDrawerChanged: (isOpened) {
if (!isOpened) {
closeAllThePanels();
}
},
drawer: FeeddyDrawer(),
body: NativeDeviceOrientationReader(
builder: (context) {
final orientation = NativeDeviceOrientationReader.orientation(context);
bool safeAreaLeft = DeviceHelper.isLandscapeLeft(orientation);
bool safeAreaRight = DeviceHelper.isLandscapeRight(orientation);
bool isLandscape = DeviceHelper.isLandscape(orientation);
return SafeArea(
left: safeAreaLeft,
right: safeAreaRight,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: widget.innerWidgets,
),
);
},
),
bottomNavigationBar: BottomNavigationBar(
onTap: (index) => onTapSelectNavigation(index, context),
items: [
BottomNavigationBarItem(
icon: Icon(
Icons.category,
),
label: '',
activeIcon: Icon(
Icons.category,
color: Colors.red,
),
tooltip: 'Categories',
),
BottomNavigationBarItem(
icon: Icon(
Icons.star,
),
label: '',
activeIcon: Icon(
Icons.star,
color: Colors.red,
),
tooltip: 'Favorites',
),
],
currentIndex: _activeIndex,
),
// FAB
floatingActionButton: deviceIsIOS
? null
: FloatingActionButton(
tooltip: 'Add ${widget.objectName.inCaps}',
child: Icon(Icons.add),
onPressed: () => widget.onPressedAdd,
),
// floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
floatingActionButtonLocation: deviceIsIOS ? null : FloatingActionButtonLocation.endDocked,
);
}
}
因此,在 onTapSelectNavigation 方法上,根据给定的选项卡索引,并使用简单的 Switch case 语句,我决定调用哪个命名路由。就这么简单。
void onTapSelectNavigation(int index, BuildContext context) {
switch (index) {
case 0:
Navigator.pushNamed(context, FoodCategoryIndexScreen.screenId);
break;
case 1:
Navigator.pushNamed(context, FavoritesScreen.screenId);
break;
}
}
现在,这还不够。为什么?因为当我为整个应用程序提供额外导航时,就会发生一些奇怪的事情。
如果我们只使用一个简单的 setState 在本地状态中设置和存储这个公共脚手架上的 activeTab,那么当我们在两个选项卡之间移动时,它会起到很好的作用。但是,当我们从 FoodCategoryIndexScreen 单击特定的 FoodCategory 时,访问保存 FoodRecipesList(餐食)和/或特定餐食的 FoodCategoryShowScreen,然后我们在 IOS 上向后滑动或在两个平台上都点击后,活动选项卡功能将获得疯狂的。它将不再正确显示当前活动标签。
为什么?
因为您是通过弹出路线到达那里的,而不是通过单击然后执行 onTab 事件,因此不会执行 setState 函数,因此不会更新 activeTab。
解决办法:
使用 RouteObserver 功能。这可以手动完成,但最简单的方法是安装 route_observer_mixin 包。这就是我们接下来要做的:
void main() {
runApp(MultiProvider(
providers: [
RouteObserverProvider(),
// Config about the app:
ChangeNotifierProvider<AppData>(
create: (context) => AppData(),
),
// Data related to the FoodCategoriesData objects: (sqlite)
ChangeNotifierProvider<FoodCategoriesData>(
create: (context) => FoodCategoriesData(),
),
// Data related to the FoodCategoriesFoodRecipesData objects: (sqlite)
ChangeNotifierProvider<FoodCategoriesFoodRecipesData>(
create: (context) => FoodCategoriesFoodRecipesData(),
),
// Data related to the FoodRecipesData objects: (sqlite)
ChangeNotifierProvider<FoodRecipesData>(
create: (context) => FoodRecipesData(),
),
// Data related to the FoodIngredientsData objects: (sqlite)
ChangeNotifierProvider<FoodIngredientsData>(
create: (context) => FoodIngredientsData(),
),
// Data related to the RecipeStepsData objects: (sqlite)
ChangeNotifierProvider<RecipeStepsData>(
create: (context) => RecipeStepsData(),
),
],
// child: MyApp(),
child: InitialSplashScreen(),
));
}
示例:
.
.
.
class _FoodCategoryIndexScreenState extends State<FoodCategoryIndexScreen> with RouteAware, RouteObserverMixin {
.
.
.
在每个有状态屏幕上(FavoritesScreen 除外),我们都会将此属性添加到本地状态:
int _activeTab = 0;
注意:在收藏夹屏幕上,这将默认为 1,而不是 0,当然,因为收藏夹是第二个选项卡(索引 = 1)。所以在 FavoritesScreen 中是这样的:
int _activeTab = 1;
/// Called when the top route has been popped off, and the current route
/// shows up.
@override
void didPopNext() {
print('didPopNext => Emerges: $_screenId');
setState(() {
_activeTab = 0;
});
}
/// Called when the current route has been pushed.
@override
void didPush() {
print('didPush => Arriving to: $_screenId');
setState(() {
_activeTab = 0;
});
}
/// Called when the current route has been popped off.
@override
void didPop() {
print('didPop => Popping of: $_screenId');
}
/// Called when a new route has been pushed, and the current route is no
/// longer visible.
@override
void didPushNext() {
print('didPushNext => Covering: $_screenId');
}
当然,在FavoritesScreen里是这样的:
/// Called when the top route has been popped off, and the current route
/// shows up.
@override
void didPopNext() {
print('didPopNext => Emerges: $_screenId');
setState(() {
_activeTab = 1;
});
}
/// Called when the current route has been pushed.
@override
void didPush() {
print('didPush => Arriving to: $_screenId');
setState(() {
_activeTab = 1;
});
}
/// Called when the current route has been popped off.
@override
void didPop() {
print('didPop => Popping of: $_screenId');
}
/// Called when a new route has been pushed, and the current route is no
/// longer visible.
@override
void didPushNext() {
print('didPushNext => Covering: $_screenId');
}
RouterObserver 包将跟踪所有推送和弹出,并根据用户点击的每次后退或后退链接执行相应的正确方法,因此相应地更新每个有状态屏幕上的 _activeTab 属性。
lib/screens/food_category_index_screen.dart
.
.
.
return FeeddyScaffold(
activeIndex: _activeTab,
appTitle: widget.appTitle,
innerWidgets: [
// Food Categories Grid:
Expanded(
flex: 5,
child: FoodCategoriesGrid(),
),
],
objectsLength: amountTotalFoodCategories,
objectName: 'category',
onPressedAdd: () => _showModalNewFoodCategory(context),
);
}
.
.
.
基于 setState 执行的 _activeTab 属性的每次更新,将始终引发 UI 的重新渲染,这将允许始终在 BottomNavigationBar 上相应地显示正确的选项卡索引,基于何时,始终匹配带有活动选项卡的当前路线。在这种情况下,我们希望始终显示第一个选项卡处于活动状态,但查看收藏夹屏幕时除外。
所以,无论我们是推送还是弹出路由,它都会以一种非常一致的方式看起来总是这样:
有关更多详细信息,您可以在 GitHub 上从 here 克隆我的应用程序的源代码。
结束。
答案 4 :(得分:-1)
对于正在寻找简短解决方案的人来说,这是我的,也许对某人有用:
#include <iostream>
namespace Eigen {
template<typename T, int W, int H>
class Matrix {
public:
static Matrix<T,W,H> Zero() {
return Matrix<T, W, H>{};
}
std::ostream &print_on(std::ostream &strm) const {
return strm;
}
};
}
template <typename T, int W, int H>
std::ostream &operator<<(std::ostream &strm, Eigen::Matrix<T,W,H> const &matrix) {
return matrix.print_on(strm);
}
template<typename T, int S>
class Base {
public:
Eigen::Matrix<int, S, S> Mat = Eigen::Matrix<int, S, S>::Zero();
virtual void print() { std::cout << Mat << std::endl; };
};
class Derived1 : public Base<Derived1,3>{
public:
};
class Derived2 : public Base<Derived2,4>{
public:
};
template <int Size>
class AdvertisingDerived : public Base<AdvertisingDerived<Size>,Size> {
public:
constexpr static int matrixSize = Size;
};
int main(){
Derived1 d1;
d1.print(); // print a 3x3 zero matrix
Derived2 d2;
d2.print(); // print a 4x4 zero matrix
AdvertisingDerived<3> ad1;
AdvertisingDerived<4> ad2;
std::cin.get();
return 0;
}
我们不会忘记主要内容,应该像这样:
class App extends StatefulWidget {
@override
State<StatefulWidget> createState() => AppState();
}
class AppState extends State<App> {
int currentTab = 0;
void _selectTab(int index) {
debugPrint (" index = $index ");
setState(() {
currentTab = index;
});
switch (currentTab) {
case 0:
debugPrint (" my index 0 ");
break;
case 1:
debugPrint (" my index 1 ");
break;
case 2:
debugPrint (" my index 2 ");
break;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: _buildBody(),
bottomNavigationBar: BottomNavigationBar(
currentIndex: currentTab,
onTap: _selectTab,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home), title: Text("Home"),
),
BottomNavigationBarItem(
icon: Icon(Icons.message), title: Text("Message"),
),
BottomNavigationBarItem(
icon: Icon(Icons.settings), title: Text("Settings"),
),
],
),
);
}
Widget _buildBody() {
// return a widget representing a page
}
}