I have a bottom navigation bar like this:
When i click (1) in tab Home, it navigate to Detail page of item in tab My Learning (2) but not tab My Learning (using Navigator.of(context).push()). But not change tab to My Learning in bottom navigation bar, it still tab Home. So how i fix that. Thank you.
Some code:
Class main_view_model (contain bottom navigation bar):
BottomTabItemAfterSignIn _currentBottomTab = BottomTabItemAfterSignIn.home;
BottomTabItemAfterSignIn get currentBottomTab => _currentBottomTab;
void changeTab(BottomTabItemAfterSignIn tab, {bool isBackClick = false}) {
if (_currentBottomTab != tab) {
_currentBottomTab = tab;
}
}
Class main_view:
#override
Widget buildView(BuildContext context) {
return WillPopScope(
onWillPop: () async {
_onPressBackDevice();
return false;
},
child: Selector<MainUserViewModel, BottomTabItemAfterSignIn>(
selector: (_, viewModel) => viewModel.currentBottomTab,
builder: (_, currentBottomTab, __) {
return Scaffold(
backgroundColor: AppColor.neutrals.shade900,
body: _buildBody(),
bottomNavigationBar: BottomNavigationUserWidget(
currentTab: currentBottomTab,
onSelectTab: _selectTab,
),
);
},
),
);
}
Widget _buildBody() {
if (viewModel.currentBottomTab == BottomTabItemAfterSignIn.home) {
return _buildTabItem(
BottomTabItemAfterSignIn.home,
_cacheBottomTabWidgets[BottomTabItemAfterSignIn.home],
);
} else if (viewModel.currentBottomTab == BottomTabItemAfterSignIn.course) {
return _buildTabItem(
BottomTabItemAfterSignIn.course,
_cacheBottomTabWidgets[BottomTabItemAfterSignIn.course],
);
} else if (viewModel.currentBottomTab ==
BottomTabItemAfterSignIn.myLearning) {
return _buildTabItem(
BottomTabItemAfterSignIn.myLearning,
_cacheBottomTabWidgets[BottomTabItemAfterSignIn.myLearning],
);
} else {
return _buildTabItem(
BottomTabItemAfterSignIn.setting,
_cacheBottomTabWidgets[BottomTabItemBeforeSignIn.setting],
);
}
}
Widget _buildTabItem(BottomTabItemAfterSignIn tabItem, Widget? child) {
// Cache Widget
return Offstage(
offstage: viewModel.currentBottomTab != tabItem,
child: child ??
(viewModel.currentBottomTab == tabItem
? _buildCacheTab(tabItem)
: Container()),
);
}
Widget _buildCacheTab(BottomTabItemAfterSignIn tabItem) {
return _cacheBottomTabWidgets[tabItem] =
BottomBodyNavigationUserWidget(
bottomMenuBar: tabItem,
navigatorKey: _bottomTabKeys[tabItem]!,
);
}
void _selectTab(BottomTabItemAfterSignIn bottomMenuBar) {
viewModel.changeTab(bottomMenuBar);
}
I hope you are using stateful widget if so then you have to use setState((){}) to to effect any value change so your code for _currentBottomTab = tab; should like this:
setState((){
_currentBottomTab = tab;
});
For more info on stateful widget please check here.
use the same fun on 1-tab nd pass the index number where you want to go
changeTab(2),
Related
I am using PopNavigatorRouterDelegateMixin and RootBackButtonDispatcher and I would expect that the back button press would call the onPopPage method of the navigator built in the router delegate. Instead the back button pops the whole app.
This is the app code
GetMaterialApp(
navigatorKey: navigatorKey,
debugShowCheckedModeBanner: false,
home: Router(
routerDelegate: AppRouterDelegate(),
backButtonDispatcher: RootBackButtonDispatcher(),
),
and this is the RouterDelegate
class AppRouterDelegate extends RouterDelegate<AppRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<AppRoutePath> {
final GlobalKey<NavigatorState> navigatorKey;
AppRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return Navigator(
pages: [
if (!context.watch<AppState>().loadingDone)
MaterialPage(child: LaunchWidget())
else
MaterialPage(child: AvailableMatches()),
if (context.watch<AppState>().selectedMatch != null)
MaterialPage(
key: ValueKey("MatchDetails"),
name: "MatchDetails",
child: MatchDetails(matchId: context.read<AppState>().selectedMatch)
)
],
onPopPage: (route, result) {
print("popping");
print(route.settings.name);
if (!route.didPop(result)) {
return false;
}
if (route.settings.name == "MatchDetails") {
context.read<AppState>().setSelectedMatch(null);
}
notifyListeners();
return true;
},
);
}
#override
Future<void> setNewRoutePath(AppRoutePath configuration) {}
}
I want to know which Page was being removed by the navigation in Flutter (I'm using Navigation 2.0).
I have the following code:
#override
Widget build(BuildContext context) {
return Navigator(
key: this.navigatorKey,
onPopPage: (route, result) {
if (!route.didPop(result)) return false;
// TODO check for the page that was poped
viewModel.otherUserIDForChat = null;
return true;
},
pages: _generatePages(),
);
}
As seen in the previous code I'm setting to null the otherUserIDForChat, however, I would like to know if the page that was poped was the chat screen that I've implemented inside the _generatePages, here's the code for that:
/// Function that aggregates pages depending on [AppViewModel]'s values
List<Page> _generatePages() {
List<Page> pages = [];
pages.add(
MaterialPage(
child: HomeScreen(),
),
);
if (viewModel.otherUserIDForChat != null) {
pages.add(
MaterialPage(
key: ValueKey(viewModel.otherUserIDForChat),
child: SingleChatScreen(
parameters: SingleChatScreenParameters(
otherUserID: viewModel.otherUserIDForChat,
),
),
),
);
}
return pages;
}
How can I know which page was being poped?
You can give your page a name:
MaterialPage(
key: ValueKey(viewModel.otherUserIDForChat),
name: viewModel.otherUserIDForChat,
child: SingleChatScreen( ... ),
),
then in opPopPage you can check for the name:
onPopPage: (route, result) {
if (!route.didPop(result)) return false;
if (route.settings.name == viewModel.otherUserIDForChat) {
// do your thing
}
return true;
}
I use three TabBarView in my project with wantKeepAlive in every TabBarView, but the second tabBarView will dispose when i jump view from one to three。However when I switch to other widget which like fragment in Android , all the TabBarView will dispose,I cant't find anyone have the same problem just like me.Thanks First.
and this is my code
body: new TabBarView(controller: _tabController, children: <Widget>[
new EventsView(
presenter: _presenter,
key: Key("EventsView"),
),
new HistoryView(
presenter: _presenter,
key: Key("HistoryView"),
),
new NewsView(
key: Key("NewsView"),
presenter: _presenter,
),
]),
and if i select the drawer item
Widget build(BuildContext context) {
return Scaffold(
body: Builder(builder: (BuildContext context) {
this.scaffoldContext = context;
return _getFromIndex(position);
}));
}
_getFromIndex(int i) {
if (i == 0) {
return _dashboard();
} else if (i == 1) {
return new TeamView(key: Key("TeamView"),);
} else if (i == 2) {
return AllBookView();
} else if (i == 3) {
return AllDocView();
}
}
I'm using ListView widget to show items as a list. In a window three, items viewing must the middle item place in the middle.
So how can I detect position of ListView when scrolling stop?
How to detect ListView Scrolling stopped?
I used NotificationListener that is a widget that listens for notifications bubbling up the tree. Then use ScrollEndNotification, which indicates that scrolling has stopped.
For scroll position I used _scrollController that type is ScrollController.
NotificationListener(
child: ListView(
controller: _scrollController,
children: ...
),
onNotification: (t) {
if (t is ScrollEndNotification) {
print(_scrollController.position.pixels);
}
//How many pixels scrolled from pervious frame
print(t.scrollDelta);
//List scroll position
print(t.metrics.pixels);
},
),
majidfathi69's answer is good, but you don't need to add a controller to the list:
(Change ScrollUpdateNotification to ScrollEndNotification when you only want to be notified when scroll ends.)
NotificationListener<ScrollUpdateNotification>(
child: ListView(
children: ...
),
onNotification: (notification) {
//How many pixels scrolled from pervious frame
print(notification.scrollDelta);
//List scroll position
print(notification.metrics.pixels);
},
),
You can also achieve this functionality with the following steps
import 'package:flutter/material.dart';
class YourPage extends StatefulWidget {
YourPage({Key key}) : super(key: key);
#override
_YourPageState createState() => _YourPageState();
}
class _YourPageState extends State<YourPage> {
ScrollController _scrollController;
double _scrollPosition;
_scrollListener() {
setState(() {
_scrollPosition = _scrollController.position.pixels;
});
}
#override
void initState() {
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text('Position $_scrollPosition pixels'),
),
body: Container(
child: ListView.builder(
controller: _scrollController,
itemCount: 200,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.mood),
title: Text('Item: $index'),
);
},
),
),
);
}
}
The NotificationListener now accepts a type argument which makes the code shorter :)
NotificationListener<ScrollEndNotification>(
child: ListView(
controller: _scrollController,
children: ...
),
onNotification: (notification) {
print(_scrollController.position.pixels);
// Return true to cancel the notification bubbling. Return false (or null) to
// allow the notification to continue to be dispatched to further ancestors.
return true;
},
),
If you want to detect the scroll position of your ListView, you can simply use this;
Scrollable.of(context).position.pixels
In addition to #seddiq-sorush answer, you can compare the current position to _scrollController.position.maxScrollExtent and see if the list is at the bottom
https://coflutter.com/flutter-check-if-the-listview-reaches-the-top-or-the-bottom/ Source
If some want to Detect the bottom of a listview then use this way
NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
if (notification.metrics.atEdge) {
if (notification.metrics.pixels == 0) {
print('At top');
} else {
print('At bottom');
}
}
return true;
},
child: ListView.builder(itemBuilder: (BuildContext context) {
return YourItemWidget;
})
)
I would say You can easily detect Scroll Position by
#override
void initState() {
super.initState();
_scrollController = ScrollController();
_scrollController.addListener(() {
var _currectScrollPosition = _scrollController.position.pixels;//this is the line
});
}
but If you are going to call setState in addListener() ; It is okay, but this will cause to rebuild your entire build(context). This is not a good practice for Animations specially.
What I would recommand is to seprate your scrolling widget into a seprate StatefulWidget , Then get the sate of that widget by calling
_yourScrollableWidgetKey.currentState?.controller.addListener(() {
//code.......
setState(() {});
});
Note: Set a GlobalKey, and assign to your StatFulWidget.
final _yourScrollableWidgetKey = GlobalKey<_YourScrollAbleWidget>();
StackedPositionedAnimated(
key: _yourScrollableWidgetKey,
),
I want to change the icon of an active bottomnavigationbar item, ie if the item is selected the icon is filled and if it’s unselected it shows the outline.
Check the code below with explanations:
import 'package:flutter/material.dart';
class MainScreen extends StatefulWidget {
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen>
with SingleTickerProviderStateMixin {
// we need this to switch between tabs
TabController _tabController;
// here we remember the current tab, by default is the first one (index 0)
int _currentTabIndex = 0;
#override
void initState() {
super.initState();
// init the TabController
_tabController = TabController(vsync: this, length: _Tab.values.length);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
backgroundColor: Colors.white,
title: Text(_getTitleForCurrentTab(_Tab.values[_currentTabIndex])), // set the title in the AppBar
),
body: TabBarView(
controller: _tabController, // we set our instantiated TabController as the controller
children: <Widget>[
// here we put the screen widgets corresponding to each tab
// the order must correspond to the order given below in bottomNavigationBar
Tab1Widget(), // these are your custom widgets for each tab you have
Tab2Widget(),
Tab3Widget(),
],
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
onTap: (int index) {
// when a tab icon was tapped we just change the current index of the tab with the new one
// this set state call will re-render the screen and will set new the tab as active
setState(() {
_currentTabIndex = index;
});
// also we want to change the screen content not just the active tab icon
// so we use the TabController to go to another tab giving it the index of the tab which was just clicked
_tabController.animateTo(index);
},
// here we render all the icons we want in the BottomNavigationBar
// we get all the values of the _Tab enum and for each one we render a BottomNavigationBarItem
items: _Tab.values
.map((_Tab tab) => BottomNavigationBarItem(
title: Text(_getTitleForCurrentTab(tab)), // set the title of the tab icon
icon: Image.asset(
_getAssetForTab(tab),
width: 24.0,
height: 24.0,
))) // set the icon of the tab
.toList(),
),
);
}
/// Get the asset icon for the given tab
String _getAssetForTab(_Tab tab) {
// check if the given tab parameter is the current active tab
final active = tab == _Tab.values[_currentTabIndex];
// now given the tab param get its icon considering the fact that if it is active or not
if (tab == _Tab.TAB1) {
return active ? 'assets/tab1_active.png' : 'assets/tab1.png';
} else if (tab == _Tab.TAB2) {
return active ? 'assets/tab2_active.png' : 'assets/tab2.png';
}
return active ? 'assets/tab3_active.png' : 'assets/tab3.png';
}
/// Get the title for the current selected tab
String _getTitleForCurrentTab(_Tab tab) {
if (tab == _Tab.TAB1) {
return 'tab1_title';
} else if (tab == _Tab.TAB2) {
return 'tab2_title';
}
return 'tab3_title';
}
}
// Just an enum with all the tabs we want to have
enum _Tab {
TAB1,
TAB2,
TAB3,
}