Flutter bottomNavigationBar extend behind systemNavigationBar - android

I'm currently playing around with flutter by recreating the google files app. I followed some tutorials and stuff but are now stuck at a problem I can't find a solution for. I want the bottomNavigationBar to extend behind the systemNavigationBar which I made transparent.
The left image is my flutter app, the right one is the real deal.
Here's the code for that so far:
void main() {
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
systemNavigationBarColor: Colors.transparent));
runApp(const Main());
}
class Main extends StatelessWidget {
const Main({Key? key}) : super(key: key);
static const _brandColor = Colors.blue;
#override
Widget build(BuildContext context) {
// Wrap MaterialApp with a DynamicColorBuilder.
return DynamicColorBuilder(
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
ColorScheme lightColorScheme;
ColorScheme darkColorScheme;
if (lightDynamic != null && darkDynamic != null) {
lightColorScheme = lightDynamic.harmonized();
darkColorScheme = darkDynamic.harmonized();
} else {
lightColorScheme = ColorScheme.fromSeed(
seedColor: _brandColor,
);
darkColorScheme = ColorScheme.fromSeed(
seedColor: _brandColor,
brightness: Brightness.dark,
);
}
return MaterialApp(
theme: ThemeData(
colorScheme: lightColorScheme,
useMaterial3: true,
),
darkTheme: ThemeData(
colorScheme: darkColorScheme,
useMaterial3: true,
),
home: const RootPage(),
debugShowCheckedModeBanner: false,
);
},
);
}
}
class RootPage extends StatefulWidget {
const RootPage({Key? key}) : super(key: key);
#override
State<RootPage> createState() => _RootPageState();
}
class _RootPageState extends State<RootPage> {
int currentPage = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
),
body: const HomePage(),
floatingActionButton: FloatingActionButton(
onPressed: () {
debugPrint("Button pressed");
},
child: const Icon(Icons.add),
),
bottomNavigationBar: NavigationBar(
destinations: const [
NavigationDestination(icon: Icon(Icons.home_filled), label: "Start"),
NavigationDestination(
icon: Icon(Icons.star_outline), label: "Markiert"),
NavigationDestination(
icon: Icon(Icons.people_alt_outlined), label: "Freigegeben"),
NavigationDestination(
icon: Icon(Icons.folder_open_outlined), label: "Dateien"),
],
onDestinationSelected: (int i) {
setState(() {
currentPage = i;
});
},
selectedIndex: currentPage,
),
);
}
}

Related

Flutter ReorderableDragStartListener issue detecting gesture on mobile device (but works fine in web browser)

I want to have reorderable list in flutter with custom drag handle (that works immediately, without long press first).
To achieve that I did:
buildDefaultDragHandles: false,
and I used ReorderableDragStartListener.
code:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final List<int> _items = List<int>.generate(50, (int index) => index);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ReorderableListView(
buildDefaultDragHandles: false,
children: <Widget>[
for (int index = 0; index < _items.length; index++)
Container(
key: Key('$index'),
color: _items[index].isOdd ? Colors.blue[100] : Colors.red[100],
child: Row(
children: <Widget>[
Container(
width: 64,
height: 64,
padding: const EdgeInsets.all(8),
child: ReorderableDragStartListener(
index: index,
child: Card(
color: Colors.green,
elevation: 2,
),
),
),
Text('Item ${_items[index]}'),
],
),
),
],
onReorder: (int oldIndex, int newIndex) {
print('oldIndex $oldIndex, newIndex $newIndex');
},
),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
On desktop (e.g. when run in Edge) it works as expected, drag handle is clicked (mouse down) and dragged up or down to change order.
The problem is on mobile device. When I tap down, and I move finger up or down - the scroll is performed. When however I tap down, and move finger little left or right, and then up/down -> then reordering happens. (tested in android emulator and real android device).
Question is - why on mobile I need to do this little annoying additional left/right move before chaining order? How to fix it?
How it works on desktop (Edge):
How it work on Android (bug!):
I solved it using custom ReorderableDragStartListener, when I set tap delay to 1ms. Since this approach does not require moving finger left/right before dragging, and 1ms is low time, it works like a charm.
code:
import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';
class CustomReorderableDelayedDragStartListener extends ReorderableDragStartListener {
final Duration delay;
const CustomReorderableDelayedDragStartListener({
this.delay = kLongPressTimeout,
Key? key,
required Widget child,
required int index,
bool enabled = true,
}) : super(key: key, child: child, index: index, enabled: enabled);
#override
MultiDragGestureRecognizer createRecognizer() {
return DelayedMultiDragGestureRecognizer(delay: delay, debugOwner: this);
}
}
usage:
CustomReorderableDelayedDragStartListener(
delay: const Duration(milliseconds: 1), // or any other duration that fits you
index: widget.index, // passed from parent
child: Container(
child: const Icon( // or any other graphical element
Icons.drag_handle
),
),
)
Try this
import 'package:flutter/material.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: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final List<int> _items = List<int>.generate(20, (int index) => index);
#override
Widget build(BuildContext context) {
final ColorScheme colorScheme = Theme.of(context).colorScheme;
final Color oddItemColor = colorScheme.primary.withOpacity(0.05);
final Color evenItemColor = colorScheme.primary.withOpacity(0.15);
return ReorderableListView(
buildDefaultDragHandles: false,
children: <Widget>[
for (int index = 0; index < _items.length; index++)
Container(
key: Key('$index'),
color: _items[index].isOdd ? oddItemColor : evenItemColor,
child: Row(
children: <Widget>[
Container(
width: 64,
height: 64,
padding: const EdgeInsets.all(8),
child: ReorderableDragStartListener(
index: index,
child: Card(
color: colorScheme.primary,
elevation: 2,
),
),
),
Text('Item ${_items[index]}'),
],
),
),
],
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final int item = _items.removeAt(oldIndex);
_items.insert(newIndex, item);
});
},
);
}
}

Flutter Nested Navigation Back Button handling on Android

Background
I have a Navigator widget on my InitialPage and I am pushing two routes ontop of it (NestedFirstRoute and NestedSecondRoute). When I press the physical back button on Android, Both the routes in Navigator are popped (which is expected).
Use case
So I would like to handle this case when the back button is pressed only the top route (NestedSecondRoute) must be popped.
Solution I tried
To deal with this issue I have wrapped the Navigator widget in WillPopScope to handle the back button press events and assigned keys to nested routes so as to use them when popping routes in the willPop scope.
I get an exception on this line
if (NestedFirstPage.firstPageKey.currentState!.canPop()) {
Exception has occurred.
_CastError (Null check operator used on a null value)
Heres the minimal and complete code sample
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
final _navigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: (settings) {
switch (settings.name) {
case NestedFirstPage.route:
return MaterialPageRoute(
builder: (context) {
return WillPopScope(
onWillPop: () async {
if (NestedFirstPage.firstPageKey.currentState!.canPop()) {
NestedFirstPage.firstPageKey.currentState!.pop();
return false;
} else if (NestedSecondPage.secondPageKey.currentState!
.canPop()) {
NestedSecondPage.secondPageKey.currentState!.pop();
return false;
}
return true;
},
child: Navigator(
key: _navigatorKey,
onGenerateRoute: (settings) {
switch (settings.name) {
case Navigator.defaultRouteName:
return MaterialPageRoute(
builder: (context) => const NestedFirstPage(),
settings: settings,
);
case NestedSecondPage.route:
return MaterialPageRoute(
builder: (context) => const NestedSecondPage(),
settings: settings,
);
}
},
),
);
},
settings: settings,
);
}
},
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const InitialPage(title: 'Initial Page'),
);
}
}
class InitialPage extends StatelessWidget {
const InitialPage({Key? key, required this.title}) : super(key: key);
final String title;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
OutlinedButton(
onPressed: () {
Navigator.pushNamed(context, NestedFirstPage.route);
},
child: const Text('Move to Nested First Page'),
),
],
),
),
);
}
}
class NestedFirstPage extends StatelessWidget {
const NestedFirstPage({Key? key}) : super(key: key);
static final GlobalKey<NavigatorState> firstPageKey =
GlobalKey<NavigatorState>();
static const String route = '/nested/first';
#override
Widget build(BuildContext context) {
return Scaffold(
key: firstPageKey,
appBar: AppBar(title: const Text('Nested First Page')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('First page'),
OutlinedButton(
child: const Text('Move to Nested Second Page'),
onPressed: () {
Navigator.pushNamed(context, NestedSecondPage.route);
},
),
],
),
),
);
}
}
class NestedSecondPage extends StatelessWidget {
const NestedSecondPage({Key? key}) : super(key: key);
static final GlobalKey<NavigatorState> secondPageKey =
GlobalKey<NavigatorState>();
static const String route = '/nested/second';
#override
Widget build(BuildContext context) {
return Scaffold(
key: secondPageKey,
appBar: AppBar(title: const Text('Nested Second Page')),
body: const Center(
child: Text('Second Page'),
),
);
}
}
Here's a slightly modified version of the above code which will allow pushing nested routes and can be popped via android back button
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
routes: {
'/nested/first': (context) => const NestedFirstPage(),
'/nested/first/second': (context) => const NestedSecondPage()
},
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const RouteManager());
}
}
class InitialPage extends StatelessWidget {
const InitialPage({Key? key, required this.title}) : super(key: key);
final String title;
static const String route = '/';
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (navigatorKey.currentState != null &&
navigatorKey.currentState!.canPop()) {
navigatorKey.currentState!.pop();
return true;
}
return false;
},
child: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
OutlinedButton(
onPressed: () {
navigate(context, NestedFirstPage.route);
},
child: const Text('Move to Nested First Page'),
),
],
),
),
),
);
}
}
Future<void> navigate(BuildContext context, String route,
{bool isDialog = false, bool isRootNavigator = true}) =>
Navigator.of(context, rootNavigator: isRootNavigator).pushNamed(route);
final navigatorKey = GlobalKey<NavigatorState>();
class RouteManager extends StatelessWidget {
const RouteManager({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
switch (settings.name) {
case '/nested/first':
builder = (BuildContext _) => const NestedFirstPage();
break;
case '/nested/first/second':
builder = (BuildContext _) => const NestedSecondPage();
break;
default:
builder =
(BuildContext _) => const InitialPage(title: 'Initial Page');
}
return MaterialPageRoute(builder: builder, settings: settings);
});
}
}
class NestedFirstPage extends StatelessWidget {
static final GlobalKey<NavigatorState> firstPageKey =
GlobalKey<NavigatorState>();
static const String route = '/nested/first';
const NestedFirstPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
key: firstPageKey,
appBar: AppBar(title: const Text('Nested First Page')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('First page'),
OutlinedButton(
child: const Text('Move to Nested Second Page'),
onPressed: () {
navigate(context, NestedSecondPage.route);
},
),
],
),
),
);
}
}
class NestedSecondPage extends StatelessWidget {
const NestedSecondPage({Key? key}) : super(key: key);
static final GlobalKey<NavigatorState> secondPageKey =
GlobalKey<NavigatorState>();
static const String route = '/nested/first/second';
#override
Widget build(BuildContext context) {
return Scaffold(
key: secondPageKey,
appBar: AppBar(title: const Text('Nested Second Page')),
body: const Center(
child: Text('Second Page'),
),
);
}
}
Heres a real world example with a Nested Bottomavigationbar.

Flutter Nested Navigator back Issue on Web

The app has a nested navigator as below
import 'package:aa/routes.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.teal,
),
initialRoute: root,
onGenerateRoute: AppRouter.mainRouteSettings,
navigatorKey: RouteConfig().appRouteKey,
);
}
}
class Test extends StatelessWidget {
const Test({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
RouteConfig().main.currentState!.maybePop();
return false;
},
child: Scaffold(
body: Container(
margin: const EdgeInsets.all(100),
decoration: BoxDecoration(
color: Colors.grey[500], borderRadius: BorderRadius.circular(20)),
child: Navigator(
key: RouteConfig().main,
initialRoute: one,
onGenerateRoute: AppRouter.generateRoute,
),
),
),
);
}
}
import 'package:aa/main.dart';
import 'package:flutter/material.dart';
//Pre
const String root = '/';
const String preRoute = '/preRoute';
const String one = '/';
const String two = '/two';
const String three = '/three';
class RouteConfig {
static final RouteConfig _routeConfig = RouteConfig._internal();
factory RouteConfig() {
return _routeConfig;
}
RouteConfig._internal();
///App Navigator Key
GlobalKey<NavigatorState> appRouteKey = GlobalKey<NavigatorState>();
///Pre Auth Key
GlobalKey<NavigatorState> main = GlobalKey<NavigatorState>();
}
class AppRouter {
static Route mainRouteSettings(RouteSettings settings) {
late Widget page;
switch (settings.name) {
case root:
page = Scaffold(
body: Container(
child: TextButton(
onPressed: () => RouteConfig()
.appRouteKey
.currentState!
.pushReplacementNamed(preRoute),
child: Center(child: Text('Click Me')))),
);
break;
case preRoute:
page = Test();
break;
default:
page = const Center(child: Text('Not Found'));
}
return MaterialPageRoute<dynamic>(
builder: (context) {
return page;
},
settings: settings,
);
}
static Route generateRoute(RouteSettings settings) {
late Widget page;
print(settings.name);
switch (settings.name) {
case one:
page = Builder(builder: (context) {
return WillPopScope(
onWillPop: () async => !Navigator.of(context).userGestureInProgress,
child: Container(
color: Colors.pink,
margin: const EdgeInsets.all(3),
child: Center(
child: Column(
children: [
TextButton(
onPressed: () {
RouteConfig().main.currentState!.pop();
},
child: Text('pop'),
),
TextButton(
onPressed: () {
RouteConfig().main.currentState!.pushNamed(two);
},
child: Text('dcdf'),
),
],
),
),
),
);
});
break;
case two:
page = const Text('Two');
break;
case three:
page = const Text('Three');
break;
default:
page = const Center(child: Text('Not Found'));
}
return MaterialPageRoute<dynamic>(
builder: (context) {
return page;
},
settings: settings,
);
}
}
I am able to swipe back in the nested navigator.
for example, after I tap on the pop button it pops the initial route of the nested navigator how can the Nestednavigator go back when it's the initial route of the app how to prevent this behavior.
Refer the Video for example
The workaround was
Programmatically we can navigate back it's a bug currently in flutter
to prevent browser back and hardware back add a willpopscope with return false to prevent back or swipe for back in ios

how to use bottomNavigationBar while using ListView.builder

I want to use bottomNavigationBar with listview, I tried each one of them separately and they work fine, but when I use them together, the bottomNavigationBar doesn't work, you can press the icons but nothing happens.
note: I'm using an older version of dart to avoid null-safety which is dumb, but the reason is the book I read is from 2019 so I couldn't follow without using an older version.
note 2: I'm very new to programming.
main.dart
//#dart=2.9
import 'package:flutter/material.dart';
import 'package:ch8_bottom_navigation/pages/home.dart';
void main() => runApp(Myapp());
class Myapp extends StatelessWidget {
//this widget is the root of the app
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Starter Template',
theme: ThemeData (
primarySwatch: Colors.blue,
platform: TargetPlatform.iOS,
),
home: Home(),
);
}
}
home.dart
//#dart=2.9
import 'package:flutter/material.dart';
import 'discover.dart';
import 'home2.dart';
import 'account.dart';
class Home extends StatefulWidget {
const Home({Key key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int _currentIndex = 0;
List _listPages = [];
Widget _currentPage;
#override
void initState() {
super.initState();
_listPages
..add(Home2())
..add(Discover())
..add(Account());
_currentPage = Discover();
}
void _changePage(int selectedIndex) {
setState(() {
_currentIndex = selectedIndex;
_currentPage = _listPages[selectedIndex];
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ListView.builder(
itemCount:20 ,
itemBuilder: (BuildContext context , int index) {
if (index >= 0 && index <= 0) {
return Home2 (index:index);
}
else return null;
},
),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.live_tv),
label: ('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.explore_outlined),
label: ('Discover'),
),
BottomNavigationBarItem(
icon: Icon(Icons.account_box_outlined),
label: ('Account'),
),
],
onTap: (selectedIndex) => _changePage(selectedIndex),
),
);
}
}
home2.dart
//#dart=2.9
import 'package:flutter/material.dart';
class Home2 extends StatelessWidget {
const Home2({Key key , #required this.index}) : super(key: key);
final int index;
#override
Widget build(BuildContext context) {
return Container(
child:Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
color: Colors.white70,
child: ListTile(
leading: Image(
image: AssetImage('assets/images/blackwidow.jpg'),
),
title: Text('Black Widow'),
subtitle: Text('By Disney'),
trailing: Icon(Icons.movie),
selected: true,
onTap: () {
print('Trapped on Row $index');
},
),
)
);
}
}
body: SafeArea(child: _currentPage),
or
body: SafeArea(child: _listPages[selectedIndex]),
Did you try this way?

How to change the radius of radio button

I am trying to make a radio button like in the picture
But I cannot change the radius of the radio button to this size. Is there any way to increase the size of the radio button?
I have put the radio button on the card widget and changed the elevation of the card widget when the submit button is clicked. But I cannot change the size of the radiobutton.
Here is the code which I tried
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String question = 'Q 1', answer = 'A 3', defaultValue = 'nil';
List<String> options = ['A 1', 'A 2', 'A 3', 'A 4'], info = ['', '', '', ''];
List<double> elevationList = List.filled(4, 0.0);
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: <Widget>[
ListTile(title: Text(question)),
ListView.builder(
shrinkWrap: true,
itemCount: options.length,
itemBuilder: (cc, ii) {
return Card(
elevation: elevationList[ii],
color: Colors.white,
child: ListTile(
title: Text(options[ii]),
subtitle: Text(info[ii]),
leading: Radio(
value: options[ii],
groupValue: defaultValue,
onChanged: (String value) {
setState(() {
defaultValue = value;
});
},
),
),
);
},
),
RaisedButton(
onPressed: () {
if (defaultValue == answer) {
setState(() {
int ind = options.indexOf(defaultValue);
elevationList[ind] = 3.0;
info[ind] = 'Correct Answer !';
});
} else {
setState(() {
int wrongInd = options.indexOf(defaultValue);
info[wrongInd] = 'Wrong Answer !';
int correctInd = options.indexOf(answer);
elevationList[correctInd] = 3.0;
info[correctInd] = 'Correct Answer !';
});
}
},
child: Text('Submit'),
)
],
),
),
);
}
}
How about this?
leading: Transform.scale(
scale: 2,
child: Radio(
value: options[ii],
groupValue: defaultValue,
onChanged: (String value) {
setState(() {
defaultValue = value;
});
},
),
),

Categories

Resources