Created
June 9, 2022 15:54
-
-
Save daveshirman/95e0d2158d518965a8aebb817c79b50c to your computer and use it in GitHub Desktop.
Navigator changing colour of Scaffold beneath transparent widget
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'package:flutter/cupertino.dart'; | |
void main() => runApp(const MyApp()); | |
class MyApp extends StatelessWidget { | |
const MyApp({Key? key}) : super(key: key); | |
static const String _title = 'Flutter Code Sample'; | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: _title, | |
home: App(), | |
); | |
} | |
} | |
class App extends StatefulWidget { | |
@override | |
State<StatefulWidget> createState() => AppState(); | |
} | |
class AppState extends State<App> { | |
// this is static property so other widget throughout the app | |
// can access it simply by AppState.currentTab | |
static int currentTab = 0; | |
// list tabs here | |
final List<TabItem> tabs = [ | |
TabItem( | |
tabName: "Home 1".toUpperCase(), | |
page: PageHome(), | |
), | |
TabItem( | |
tabName: "Home 2".toUpperCase(), | |
page: PageHome(), | |
), | |
]; | |
AppState() { | |
// indexing is necessary for proper functionality | |
// of determining which tab is active | |
tabs.asMap().forEach((i, item) => item.setIndex(i)); | |
} | |
@override | |
void initState() { | |
super.initState(); | |
} | |
// sets current tab index | |
// and update state | |
void _selectTab(int index) { | |
if (index == currentTab) { | |
// pop to first route | |
// if the user taps on the active tab | |
tabs[index].key.currentState!.popUntil((route) => route.isFirst); | |
} else { | |
// update the state | |
// in order to repaint | |
if (mounted) { | |
currentTab = index; | |
_updateView(); | |
} | |
} | |
} | |
void _updateView() { | |
if (mounted) { | |
setState(() {}); | |
} | |
} | |
void resetToHomeTab() { | |
currentTab = 0; | |
} | |
@override | |
Widget build(BuildContext context) { | |
Widget body = Scaffold( | |
backgroundColor: Colors.transparent, | |
body: IndexedStack( | |
index: currentTab, | |
children: tabs.map((e) => e.page).toList(), | |
), | |
bottomNavigationBar: BottomNavigation( | |
onSelectTab: _selectTab, | |
tabs: tabs, | |
), | |
); | |
// Background of the tab view | |
Widget wrap = Container( | |
width: double.infinity, | |
height: double.infinity, | |
color: Colors.blue, | |
child: body, | |
); | |
// WillPopScope handle android back btn | |
return WillPopScope( | |
onWillPop: () async { | |
final isFirstRouteInCurrentTab = | |
!await tabs[currentTab].key.currentState!.maybePop(); | |
if (isFirstRouteInCurrentTab) { | |
// if not on the 'main' tab | |
if (currentTab != 0) { | |
// select 'main' tab | |
_selectTab(0); | |
// back button handled by app | |
return false; | |
} | |
} | |
// let system handle back button if we're on the first route | |
return isFirstRouteInCurrentTab; | |
}, | |
// this is the base scaffold | |
// don't put appbar in here otherwise you might end up | |
// with multiple appbars on one screen | |
// eventually breaking the app | |
child: wrap, | |
); | |
} | |
} | |
class TabItem { | |
final String tabName; | |
final GlobalKey<NavigatorState> key = GlobalKey<NavigatorState>(); | |
int _index = 0; | |
Widget? _page; | |
TabItem({ | |
required this.tabName, | |
required Widget page, | |
}) { | |
_page = page; | |
} | |
void setIndex(int i) { | |
_index = i; | |
} | |
int getIndex() => _index; | |
Widget get page { | |
// If you toggle between the two return statements below, you can see the transparency | |
// not working as expected when the page is wrapped in the Navigator | |
// return _page!; | |
return Visibility( | |
visible: (_index == AppState.currentTab), | |
maintainState: true, | |
// child: _page!, | |
child: Navigator( | |
key: key, | |
onGenerateRoute: (routeSettings) { | |
return CupertinoPageRoute( | |
builder: (_) { | |
return (_page != null) ? _page! : const SizedBox.shrink(); | |
}, | |
); | |
}, | |
), | |
); | |
} | |
} | |
class BottomNavigation extends StatelessWidget { | |
const BottomNavigation({ | |
required this.onSelectTab, | |
required this.tabs, | |
}); | |
final ValueChanged<int> onSelectTab; | |
final List<TabItem> tabs; | |
@override | |
Widget build(BuildContext context) { | |
return BottomNavigationBar( | |
elevation: 0, | |
showSelectedLabels: true, | |
showUnselectedLabels: true, | |
backgroundColor: Colors.transparent, | |
fixedColor: Colors.white, | |
type: BottomNavigationBarType.fixed, | |
items: tabs | |
.map((e) => _buildItem(e.getIndex(), e.tabName)) | |
.toList(), | |
onTap: (index) => onSelectTab( | |
index, | |
), | |
currentIndex: AppState.currentTab, | |
); | |
} | |
BottomNavigationBarItem _buildItem(int index, String tabName) { | |
return BottomNavigationBarItem( | |
icon: const Padding( | |
padding: EdgeInsets.only(bottom: 4), | |
child: Icon(Icons.home), | |
), | |
label: tabName, | |
); | |
} | |
} | |
class PageHome extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return const Scaffold( | |
backgroundColor: Colors.transparent, | |
body: Center( | |
child: Text("Home page"), | |
), | |
); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment