I would like to keep showing a floating button or widget in Flutter even though the page is changed by Navigator.of(context).push() like mini music player which placed in the bottom.
How can I implement that ??
You can extract the scaffold as a layout that contains a bottom sheet, and use this layout in every page you build, passing in the title, body, etc., so that the bottom sheet is persistent in all pages. Snippet below.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Persistent Bottom Sheet',
theme: ThemeData(
primarySwatch: Colors.blue,
brightness: Brightness.dark,
),
initialRoute: "/",
routes: {
"/": (context) => Home(),
"/library": (context) => Library(),
},
);
}
}
class Home extends StatelessWidget {
Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Layout(
title: "Home",
body: Center(
child: Text("Home"),
),
actions: <Widget>[
InkWell(
onTap: () {
Navigator.of(context).pushNamed("/library");
},
child: Tooltip(
message: "Go To Library",
child: Padding(
padding: const EdgeInsets.all(12),
child: Icon(Icons.library_music),
),
),
)
],
);
}
}
class Library extends StatelessWidget {
Library({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Layout(
title: "Library",
body: Center(
child: Text("Library"),
),
);
}
}
class Layout extends StatelessWidget {
final String title;
final Widget body;
final List<Widget>? actions;
const Layout({
Key? key,
required this.title,
required this.body,
this.actions,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(title),
actions: actions,
),
body: body,
bottomSheet: Container(
width: double.infinity,
padding: const EdgeInsets.all(15),
color: Theme.of(context).cardColor,
child: Text("Persistent Bottom Sheet"),
),
);
}
}
Related
I want to know how to replicate this slide-up widget in Flutter when I click a button.
Final product :https://imgur.com/a/HEZsMwN
(Stack Overflow won't let me post pictures)
Here is a code example using a variation of the starting app:
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'),
);
}
}
//this is the custom function
showTextAreaSheet(BuildContext context) {
return showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (BuildContext context) => Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom), //keeps above keyboard
child: const TextField(
autofocus:true,
decoration: InputDecoration(
labelText: "New Task",
labelStyle: TextStyle(
color: Colors.black87,
fontWeight: FontWeight.w600,
)
),
),
) );
}
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> {
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Text(
'Press button to open Text Area',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {showTextAreaSheet(context);},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
I am using a showModalBottomSheet function that I call through the showTextAreaSheet(BuildContext context).
The important part is the padding around the TextField that keeps the text field above the keyboard at all times.
it's called Bottom Sheet
Here is an example
I followed this tutorial on how to create a bottom navbar and it works great apart from the fact that I dont know how to appropriately add a drawer.
Currently my code looks something like this for the screen that holds the navigational bar:
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async =>
!await navigatorKeys[currentTab].currentState.maybePop(),
child: Scaffold(
body: Stack(children: <Widget>[
_buildOffstageNavigator(TabItem.red),
_buildOffstageNavigator(TabItem.green),
_buildOffstageNavigator(TabItem.blue),
]),
bottomNavigationBar: BottomNavigation(
currentTab: currentTab,
onSelectTab: _selectTab,
),
),
);
}
And like this for my home screen:
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
endDrawer: const NavigationDrawerWidget(),
body: const _HomePageBody(),
);
}
}
All of this is functioning, however, the drawer does not hide the navigational bar when its opened. I have thought about placing the drawer instead on the screen that holds the but that introduces more problems than it fixes. Such as the hamburger icon does not appear unless I also add an appbar to the said screen and I only want it to be present on the homepage and not its "subpages" (pages that I can access through the homepage but are not the pages present on the navbar).
My next thought is that I could possibly hide the navbar when the drawer opens and reveal it when the drawer is closed again. But then there is the difficulty of animating in such a way to make it look nice and seems like a long winded solution.
At this point it seems like the navbar is the problem but I have tried redoing it so many times such that the end result would be a bar that is present on all pages (+ subpages) and saves state that it would be unfortunate to change it up again.
I would appreciate any suggestions or links to other projects/tutorials that are doing something similar. Thanks :)
Edit
here is a reproducible example, I wipped it up quite quickly so sorry for it being a bit messy
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 const MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int pageIndex = 0;
final pages = [
const Page(
title: "page 1",
drawer: true,
),
const Page(
title: "page 2",
drawer: false,
),
const Page(
title: "page 3",
drawer: false,
),
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: pages[pageIndex],
bottomNavigationBar: Container(
color: Theme.of(context).primaryColor,
height: 60,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
onPressed: () {
setState(() {
pageIndex = 0;
});
},
icon: const Icon(
Icons.home_outlined,
),
),
IconButton(
onPressed: () {
setState(() {
pageIndex = 1;
});
},
icon: const Icon(
Icons.work_outline_outlined,
),
),
IconButton(
onPressed: () {
setState(() {
pageIndex = 2;
});
},
icon: const Icon(
Icons.widgets_outlined,
),
),
],
),
),
);
}
}
class Page extends StatelessWidget {
const Page({Key? key, required this.title, required this.drawer})
: super(key: key);
final String title;
final bool drawer;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
endDrawer: drawer ? const Drawer() : null,
body: Center(
child: Text(
title,
style: const TextStyle(
fontSize: 45,
fontWeight: FontWeight.w500,
),
),
),
);
}
}
I think you can use single scaffold and include your drawer and appBar on 1st scaffold.
return Scaffold(
endDrawer: pageIndex == 0 ? const Drawer() : null,
body: pages[pageIndex],
appBar: AppBar(),
bottomNavigationBar: Container(
I'm building my app with Flutter 2.10.5 and Dart 2.16.2.
When i try to refresh the demo content whith a pull, nothing happens. I have multiple navigation routes for different content. So the demo is a litte bit complex.
The main.dart includes the basic code for the app. I use the NavDrawer Widget to build the different pages. Every route is defined in the navigation.dart file, which reference to the content widgets.
My code so far is:
import 'dart:core';
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
void main() async {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of the application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo Company',
theme: ThemeData(),
debugShowCheckedModeBanner: false,
home: const HomePage(title: 'Demo Company'),
);
}
}
class _HomePageState extends State<HomePage> {
#override
initState() {
super.initState();
}
Widget _infoTile(String title, String subtitle) {
return ListTile(
title: Text(title),
subtitle: Text(subtitle.isEmpty ? 'Not set' : subtitle),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: const NavDrawer(),
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
children: <Widget>[
_infoTile('App name', 'Demo App....'),
// Multiple Liste Tiles...
],
),
),
);
}
}
//----------------------------------------------------------------------
// navigation.dart
//----------------------------------------------------------------------
class NavDrawer extends StatelessWidget {
const NavDrawer({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const <Widget>[
Text(
'Navigation',
style: TextStyle(color: Colors.white, fontSize: 30),
),
SizedBox(height: 30.0),
Text('Firstname', style: TextStyle(color: Colors.black, fontSize: 15)),
Text('Accountname', style: TextStyle(color: Colors.black, fontSize: 15)),
],
),
),
ListTile(
leading: const Icon(Icons.notifications),
title: const Text('Demo'),
onTap: () {
Navigator.push(
context,
Demo.route(),
);
},
),
// Multiple Navigation List Tiles...
],
),
);
}
}
//----------------------------------------------------------------------
// demo.dart
//----------------------------------------------------------------------
class HomePage extends StatefulWidget {
const HomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<HomePage> createState() => _HomePageState();
}
class Demo extends StatefulWidget {
const Demo({Key? key}) : super(key: key);
static Route route() {
return CupertinoPageRoute(builder: (_) => const Demo());
}
#override
_DemoState createState() => _DemoState();
}
class _DemoState extends State<Demo> {
final _data = <WordPair>[];
#override
void initState() {
super.initState();
_data.addAll(generateWordPairs().take(20));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Woolha.com Flutter Tutorial'),
),
body: _buildList(),
);
}
Widget _buildList() {
return RefreshIndicator(
onRefresh: _refreshData,
child: ListView.builder(
padding: const EdgeInsets.all(20.0),
itemBuilder: (context, index) {
WordPair wordPair = _data[index];
return _buildListItem(wordPair.asString, context);
},
itemCount: _data.length,
),
);
}
Widget _buildListItem(String word, BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(word),
),
);
}
Future _refreshData() async {
await Future.delayed(const Duration(seconds: 3));
_data.clear();
_data.addAll(generateWordPairs().take(20));
setState(() {});
}
}
class ShowMessages extends StatelessWidget {
final String type;
final Color color;
const ShowMessages({Key? key, required this.type, required this.color}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView(
//color: color,
physics: const AlwaysScrollableScrollPhysics(),
children: [
ListTile(
title: Text(
type,
style: Theme.of(context).textTheme.bodyText1,
),
),
]);
}
}
Copy this code to DartPad
What is wrong?
Well for me this code... works
I copied it into Dartpad, then Dev Tools in browser (F12) > Device Emulation > Responsive. And you can use pull to refresh.
Of course this doesn't work using web view and mouse. I believe this gesture is not supported.
I'm using a nested MaterialApp such that, FirstMaterialApp has SecondMaterialApp as its child. I'm facing an issue when calling showDialog from SecondMaterialApp, that is it appears on the entire screen as if it is opened from the FirstMaterialApp.
I want that the dialog remains confined to the boundaries of the SecondMaterialApp.
In the image, I have intentionally stretched the Dialog across the width so that it is apparent that it covers the FirstMaterialApp.
First MaterialApp
class FirstMaterialApp extends StatelessWidget {
const FirstMaterialApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'First Material App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('First App Scaffold'),
),
body: Center(
child: DeviceFrame(
device: Devices.ios.iPhone12, screen: const SecondMaterialApp()),
));
}
}
Second MateriaApp
class SecondMaterialApp extends StatelessWidget {
const SecondMaterialApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Second Materia App', home: SecondScaffold());
}
}
class SecondScaffold extends StatelessWidget {
const SecondScaffold({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(top: 40.0),
child: Scaffold(
appBar: AppBar(
title: const Text('Second App Home'),
),
body: Center(
child: TextButton(
child: const Text('Open Dialog'),
onPressed: () async {
await showDialog(
context: context,
builder: (buildContext) => CustomDialog());
},
),
),
),
);
}
}
class CustomDialog extends StatelessWidget {
const CustomDialog({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Dialog(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [
Text(
'Dialog',
style: TextStyle(fontSize: 20.0),
),
Text(
'Message Text',
),
],
),
);
}
}
Found the solution by using a showDialog parameter named useRootNavigator. Setting it to false provided the required results.
Now the dialog is confined to the boundaries of child MaterialApp and the backgroundOverly from showDialog covers only the second material app.
-> use Your Second File code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class SecondMaterialApp extends StatelessWidget {
const SecondMaterialApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Second Materia App', home: SecondScaffold());
}
}
class SecondScaffold extends StatelessWidget {
const SecondScaffold({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(top: 40.0),
child: Scaffold(
appBar: AppBar(
title: const Text('Second App Home'),
),
body: Center(
child: TextButton(
child: const Text('Open Dialog'),
onPressed: () async {
await showDialog(
context: context,
builder: (buildContext) => const CustomDialog());
},
),
),
),
);
}
}
class CustomDialog extends StatelessWidget {
const CustomDialog({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return CupertinoAlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const \[
Text(
'Dialog',
style: TextStyle(fontSize: 20.0),
),
Text(
'Message Text',
),
\],
),
);
}
}
create material before dialog
return Material(child: Dialogue());
I want to implement something like an Instagram profile page with the NestedScrollView widget. So I combine this with a TabBar widget. I have two tabs. Detail and comments. Then I have other widgets on top of the TabBar. Everything works as expected so far. But the problem starts when I scroll. Even though there is nothing inside my tabs, NestedScrollView scrolls too much, and my TabBar widget comes to the top of the screen. In Instagram, if you have no photos, you can not scroll the page. But in my application, I can scroll. And this is the behavior I want to prevent. How can I do this? I also share my codes and screenshots of the unwanted behavior.
This is the page
This is the over scroll even though there is nothing to show inside the tab bar
These are the codes:
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Home Page"),
),
body: DefaultTabController(
length: 2,
child: NestedScrollView(
headerSliverBuilder: (context, isScrolled) {
return [
SliverToBoxAdapter(
child: Container(
margin: const EdgeInsets.all(16),
child: const Placeholder(
fallbackHeight: 300,
fallbackWidth: double.infinity,
color: Colors.amberAccent,
),
),
),
SliverPersistentHeader(
pinned: true,
delegate: StickyTabBarDelegate(
child: const TabBar(
labelColor: Colors.black,
tabs: [
Tab(text: "Detail"),
Tab(text: "Comments"),
],
),
),
)
];
},
body: const TabBarView(
children: [
DetailTab(),
CommentsTab(),
],
),
),
),
);
}
}
class DetailTab extends StatelessWidget {
const DetailTab({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container();
}
}
class CommentsTab extends StatelessWidget {
const CommentsTab({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container();
}
}
class StickyTabBarDelegate extends SliverPersistentHeaderDelegate {
final TabBar child;
StickyTabBarDelegate({required this.child});
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
color: Colors.white,
child: child,
);
}
#override
double get maxExtent => child.preferredSize.height;
#override
double get minExtent => child.preferredSize.height;
#override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
return true;
}
}