I want to display a circular bar like loading in front of other widgets.
Below is the code which i am currently using. It shows the Circular loading but its between other widget.
It should be on top. Based on the suggestion I tried using Stack but it is still showing in between the widget. What am I doing wrong here?
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
bool visible = true ; //Testing purpose it is true. Actually it is false.
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: Stack(
children: <Widget>[
new Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: FractionalOffset.bottomCenter,//Alignment.bottomLeft,
colors: [Colors.green[900], Colors.lightBlueAccent]),
),
),SingleChildScrollView(
child: new Form(
key: _formKey,
autovalidate: _autoValidate,
child: Center(
child: Column(
children: <Widget>[
Row(
children: <Widget>[
VerticalText(),
TextLogin(),
],
),
Divider(),
Container(
width: 280,
),
Container(
width: 280,
child: TextFormField(
style: TextStyle(
color: Colors.white,
),
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Enter Email',
fillColor: Colors.lightBlueAccent,
labelText: 'Email',
labelStyle: TextStyle(
color: Colors.white70,
fontSize: 20,
),
),
controller: emailController,
autocorrect: true,
validator: validateEmail,
onSaved: (String val) {
_email = val;
},
)
),
Container(
width: 280,
child: TextFormField(
style: TextStyle(
color: Colors.white,
),
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Enter Password',
fillColor: Colors.lightBlueAccent,
labelText: 'Password',
labelStyle: TextStyle(
color: Colors.white70,
fontSize: 20,
),
),
controller: passwordController,
autocorrect: true,
obscureText: true,
validator: validatePassword,
onSaved: (String val) {
_password = val;
},
)
),
RaisedButton(
onPressed: _validateInputs,
color: Colors.green,
textColor: Colors.white,
padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
child: Text('Login'),
),
Visibility(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
child: CircularProgressIndicator(
strokeWidth: 4.0,
valueColor : AlwaysStoppedAnimation(Colors.white),
),
height: 200.0,
width: 200.0,
),
],
),
),
visible: visible,
),
],
),
)
)
)
],
),
);
}
I have seen similar questions in SO but still getting hard time to resolve it.
How to work with progress indicator in flutter?
flutter how to get a circular progress indicator working
Flutter : create an overlay progress bar
How to display CircularProgressIndicator on top of all widget in a centre of the screen
The real meaning of circularProgressIndicator is to be run while loading process without letting user interrupt.
To remove interruption by user in between, one should put circularProgressIndicator in a dialog with property barrierDismissible: false.
One should create this loading indicator as reusable widget.
You can craete a method like below and can use it anywhere over the screen without defining in screen. (this does not require stack also.). Dialog will appear on the center of the screen.
buildShowDialog(BuildContext context) {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return Center(
child: CircularProgressIndicator(),
);
});
}
For Reference: Checkout code below:
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Demo(),
);
}
}
class Demo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Circle Progress Indicator Demo"),
),
body: Center(
child: MaterialButton(
child: Text("Press Me!"),
onPressed: () => buildShowDialog(context),
),
),
);
}
buildShowDialog(BuildContext context) {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return Center(
child: CircularProgressIndicator(),
);
});
}
}
Output:
Like said, the real meaning of CircularProgressIndicator is to be run while loading a process without letting user interrupt.
Create this loading indicator as a reusable .dart file.
With this you can use the LoadingIndicator flexible everywhere and overlay the current screen.
You just have to make a .dart file containing:
import 'package:flutter/material.dart';
buildLoading(BuildContext context) {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
),
);
});
}
Then you can use it anywhere using
buildLoading(context);
and dismiss it when loading is done using
Navigator.of(context).pop();
You can use Positioned Widget in Stack Widget for Showing CircularProgressIndicator in front of all widgets like below..
Please put values according to your convenience in all four values bottom,left,right,top...
Positioned(
bottom: ,
left: ,
right: ,
top : ,
child: Container(
child : CircularProgressIndicator()
)
...
)
I removed Visiblity code with this one.
Old Code
Visibility(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
child: CircularProgressIndicator(
strokeWidth: 4.0,
valueColor : AlwaysStoppedAnimation(Colors.white),
),
height: 200.0,
width: 200.0,
),
],
),
),
visible: visible,
),
New Code
visible ? Container(
color: Colors.black.withOpacity(0.5),
child: Center(
child:SizedBox(// Center(
child: CircularProgressIndicator(
strokeWidth: 4.0,
valueColor : AlwaysStoppedAnimation(Colors.white),
),
height: 200.0,
width: 200.0,
),
),
)
: Container()
And most importantly it needs to call in the first Widget.
return Scaffold(
key: _scaffoldKey,
body: Stack(
children: <Widget>[
/////Some containers,child Widget etc/////////
visible ? Container(
color: Colors.black.withOpacity(0.5),
child: Center(
child:SizedBox(// Center(
child: CircularProgressIndicator(
strokeWidth: 4.0,
valueColor : AlwaysStoppedAnimation(Colors.white),
),
height: 200.0,
width: 200.0,
),
),
)
: Container()
] //Widget
), //Stack
); //Scaffold
I am not sure i explained it properly or not. But Bottom line is it shouldn't be inside the other child widget.
It should be placed inside first children: <Widget>[ not inside the nested.
As above most of them gave answer to make separate reusable function. So here is mine if you want to fetch some data and display circular progress indicator while data is being fetched.
///Function to get data from a future and display a dialog with circlular progress indicator while data is fetched.
getFutureData(BuildContext context, var future) async {
var data;
await showDialog(
context: context,
barrierDismissible: false,
builder: (context) => Center(
child: FutureBuilder(
future: future,
builder: (context, snapshot) {
if (snapshot.hasData) {
data = snapshot.data;
Navigator.pop(context);
}
return CircularProgressIndicator();
},
),
),
);
return data;
}
Related
I'm trying to build a preferences dialog which contains user options for theme, font etc. I've got the selection within the dialog working responsively, but setState() doesn't update the state of the parent from within the dialog (despite the fact that I named the StateSetter function for my StatefulBuilder "updateDialogState").
How can I resolve this?
Minimum example:
class WritingScreenState extends State<WritingScreen> {
late List<Section> sections;
#override
void initState() {
super.initState();
sections = []
}
#override
Widget build(BuildContext context) {
WrittenTextTheme currentTheme = WrittenTextTheme.light;
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: Icon(Icons.tune),
tooltip: "Preferences",
onPressed: () {
showDialog(context: context, builder: (context) {
return AlertDialog(
title: Text("Preferences"),
content: StatefulBuilder(
builder: (context, StateSetter updateDialogState) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Theme"),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
flex: 1,
child: InkWell(
onTap: () {
updateDialogState(() {
currentTheme = WrittenTextTheme.light;
});
setState(() {
currentTheme = WrittenTextTheme.light;
});
},
child: Container(
height: 50,
decoration: BoxDecoration(
color: Colors.white,
border: currentTheme == WrittenTextTheme.light ?
Border.all(color: Theme.of(context).primaryColor, width: 3) :
Border.all(),
borderRadius: BorderRadius.circular(6)
),
child: Align(
alignment: Alignment.center,
child: Text(
"Aa",
style: TextStyle(
fontSize: 28,
color: Colors.grey[800],
)
),
),
),
),
),
Flexible(
flex: 1,
child: InkWell(
onTap: () {
updateDialogState(() {
currentTheme = WrittenTextTheme.dark;
});
setState(() {
currentTheme = WrittenTextTheme.dark;
});
},
child: Container(
height: 50,
decoration: BoxDecoration(
color: Colors.blueGrey[800],
border: currentTheme == WrittenTextTheme.dark ?
Border.all(color: Theme.of(context).primaryColor, width: 3) :
Border.all(),
borderRadius: BorderRadius.circular(6)
),
child: Align(
alignment: Alignment.center,
child: Text(
"Aa",
style: TextStyle(
fontSize: 28,
color: Colors.grey[100],
)
),
),
),
),
),
]
),
],
);
}
),
actions: [
ElevatedButton(
child: Text("SAVE"),
onPressed: () {
//TODO: save changes
Navigator.pop(context);
},
)
],
);
});
}
),
],
),
);
}
}
setState is indeed working. The problem resides here, the code in the build method will again be initialized when setState is called.
#override
Widget build(BuildContext context) {
WrittenTextTheme currentTheme = WrittenTextTheme.light;
Update your application logic so that on setState you don't loose the new value set.
I'm using Provider in my flutter app. I was able to change one widget with another one, without using set state. That was great!
My feature consist that when I tap in the button 'Añadir al carrito', a number over the shopping car change, increasing one by one.
This is the part of my code that I put in the button, that is inside the body of my scaffold using the provider.of:
final _numCompras = Provider.of<MyShoppingCart>(context);
return Padding(
padding: const EdgeInsets.only(top: 15.0),
child: GestureDetector(
onTap: () {
_numCompras.numCompras = _numCompras.numCompras + 1;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text('Producto Añadido'),
duration: const Duration(seconds: 1),
action: SnackBarAction(
label: '',
onPressed: () {
},
),
));
},
child: Container(
height: 80,
width: double.infinity,
decoration: BoxDecoration(
color: Constants.kColorRojo,
borderRadius: BorderRadius.circular(60),
border: Border.all(width: 1, color: Colors.black),
),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Añadir al Carrito',
style: TextStyle(
color: Colors.white,
fontFamily: 'Poppins',
fontSize: 20,
),
),
SizedBox(
width: 15,
),
Icon(
Icons.shopping_cart_outlined,
color: Colors.white,
size: 30,
),
],
),
),
),
),
);
}
This is my class using ChangeNotifier
int _numCompras = 0;
get numCompras => _numCompras;
set numCompras(int newValue) {
_numCompras = newValue;
notifyListeners();
}
}
And in my main.dart I`m putting the ChangeNotifierProvider, because I thing that one contain all of the rest widgets.
class JuniorsApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<MyShoppingCart>(
create: (context) => MyShoppingCart(),
//builder: ,
)
],
child: MaterialApp(
theme: ThemeData.light().copyWith(
primaryColor: Color(0xFFd51921),
scaffoldBackgroundColor: Color(0xFFf2f2f2),
),
home: PageStart(),
initialRoute: PageStart().ID_PageStart,
routes: {
page_IniciarSesion().ID_PageIniciarSesion: (context) =>
page_IniciarSesion(),
PageStart().ID_PageStart: (context) => PageStart(),
RegistroNombrePage().ID_PageRegistroNombre: (context) =>
RegistroNombrePage()
},
debugShowCheckedModeBanner: false,
),
);
}
}
And finally the code of my appBar that is the widget that I`m changing:
Widget build(BuildContext context) {
bool _hasBackButton = hasBackArrow ?? true;
final height = MediaQuery.of(context).size.height;
final width = MediaQuery.of(context).size.width;
return Selector<MyShoppingCart,int>(
selector: (_, model) => model.numCompras,
builder: (context, numCompras, _) {
return SafeArea(
child: Material(
elevation: 15,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (_hasBackButton)
RoundedIconButton(
iconData: Icons.arrow_back_ios,
press: () {
Navigator.pop(context, MaterialPageRoute(
builder: (context) {
return Selector(builder: (context, numCompras, _){}, selector: (_, model) => model.numCompras);
},
)
);
}
),
Text(
this.title == null ? 'Junior \'s Tacos' : this.title,
style: Constants.kFuenteAppBar,
),
this.hasCart
? _widgetShoppingCar(context, height, width, numCompras)
: Spacer() ,
],
),
),
),
);
},
);
}
Widget _widgetShoppingCar(context, height, width, _numCompras) {
return Stack(
children: [
RoundedIconButton(
iconData: Icons.shopping_cart_outlined,
press: () {
_viewShop = !_viewShop;
this.changeAppBarShop(_viewShop);
/*Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CartPage(),
),
);*/
},
),
Positioned(
bottom: height * 0.04,
left: width * 0.09,
child: Container(
width: width * 0.05,
height: height * 0.025,
decoration: BoxDecoration(
color: Colors.red[500],
border: Border.all(
color: Colors.red[500],
),
borderRadius: BorderRadius.all(Radius.circular(20))),
child: Center(
child: Text(
'${_numCompras}',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
),
)),
),
)
],
);
}
The thing is that it works fine, but when I do Navigator.pop from the appBar. The count over the shopping car come back to zero.
Here is how its working at the moment
Could anyone give a me a tip about this?
thanks!
Well, if anyone have this kind of problem. The issue for me was that I have two ChangeNotifierProvider. One in my main.dart and the other one in my widget that contain the button. I remove the last one and now its working.
I have made the listview.builder in my page. In that i have designed my card . Now i want to generate card in the Listveiw.buider one by one by clicking a onpressed method in a floating action button. Which method/function should i write in that onpressed ? and how to generate card in that and what should i change in itemCount and itemBuilder. I need that roadmap. Thanks.
ListView.builder(
itemCount: 5,
itemBuilder: (context, index) {
return Dismissible(
key:Key(null),
direction: DismissDirection.startToEnd,
onDismissed: (direction) {},
confirmDismiss: (direction) {
return showDialog(
context: context, builder: (_) => DeleteCard());
},
background: Container(
color: Colors.red,
padding: EdgeInsets.only(left: 16),
child: Icon(
Icons.delete,
color: Colors.white,
),
alignment: Alignment.centerLeft,
),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0)),
color: Colors.white70,
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Container(
width: 120,
padding: EdgeInsets.all(10.0),
child: Text(
"Project ",
style: TextStyle(
fontSize: 11, fontWeight: FontWeight.bold),
),
),
Container(
padding: EdgeInsets.all(10.0),
child: ProjectName(),
),
],
),
// item add start
Container(
padding: EdgeInsets.all(10.0),
child: TextFormField(
decoration: InputDecoration(
hintText: "Item name" ,hintStyle: TextStyle(fontSize: 12),
),
),
),
Container(
padding: EdgeInsets.all(10.0),
child: TextFormField(
decoration: InputDecoration(
hintText: "Amount",hintStyle: TextStyle(fontSize: 12),
),
keyboardType: TextInputType.number,
),
),
],
),
),
);
}
),
This is something you are looking for I think. Just replace the ListTile with your own Card
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
var listLength = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Flutter App :)"),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
listLength++;
});
}),
body: Container(
child: ListView.builder(
itemCount: listLength,
itemBuilder: (context, index) {
return ListTile(
leading: Text("$index"),
title: Text("List Tile as Widget"),
);
})),
);
}
}
I have solved it by using a var which initial value in 0 then make a function in set state and increment the variable. Then I called the function in my floating action button and in listview.builder item count I set the var name. I hope it will help others it's a easy solution to generate card by using onpressed.
I was trying things out with ModalBottomSheet. How can I achieve 90% height of device screen size for modal sheet. I did mediaquery but still it does not give me more than half of the screen size. How do I solve this?
Here is the code:
class _TestFileState extends State<TestFile> {
modalSheet() {
showModalBottomSheet(
context: context,
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(15.0), topRight: Radius.circular(15.0)),
),
builder: (context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
ListTile(
leading: Icon(Icons.email),
title: Text('Send email'),
onTap: () {
print('Send email');
},
),
ListTile(
leading: Icon(Icons.phone),
title: Text('Call phone'),
onTap: () {
print('Call phone');
},
),
],
);
});
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Center(child: Text('Testing Modal Sheet')),
),
body: Center(
child: InkWell(
onTap: () {
modalSheet();
},
child: Container(
color: Colors.indigo,
height: 40,
width: 100,
child: Center(
child: Text(
'Click Me',
style: TextStyle(color: Colors.white),
),
)),
),
),
),
);
}
}
Here is the output:
you have to pass isScrollControlled: true and use mediaquery as given below
showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (context) {
return Container(
height: MediaQuery.of(context).size.height * 0.5,
color: Colors.red,
//height: MediaQuery.of(context).size.height,
);
});
As I remember that's a restriction about the native implementation of Flutter modal bottom sheet.
You can use the package modal_bottom_sheet to achieve that.
Install:
dependencies:
modal_bottom_sheet: ^0.2.2
And minimal example:
showMaterialModalBottomSheet(
context: context,
expand: true, //this param expands full screen
builder: (context, scrollController) => Container(),
)
I'm having very hard time to implement "Standard Bottom Sheet" in my application - with that I mean bottom sheet where "header" is visible and dragable (ref: https://material.io/design/components/sheets-bottom.html#standard-bottom-sheet). Even more: I can not find any example of it anywhere:S. the closes I came to wished result is by implementing DraggableScrollableSheet as bottomSheet: in Scaffold (only that widget has initialChildSize) but seams like there is no way to make a header "sticky" bc all the content is scrollable:/.
I also found this: https://flutterdoc.com/bottom-sheets-in-flutter-ec05c90453e7 - seams like there the part about "Persistent Bottom Sheet" is the one I'm looking for but artical is not detailed so I can not figure it out exacly the way to implement it plus the comments are preaty negative there so I guess it's not totally correct...
Does Anyone has any solution?:S
The standard bottom sheet behavior that you can see in the material spec can be achived using DraggableScrollableSheet.
Here I am going to explain it in detail.
Step 1:
Define your Scaffold.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Draggable sheet demo',
home: Scaffold(
///just for status bar color.
appBar: PreferredSize(
preferredSize: Size.fromHeight(0),
child: AppBar(
primary: true,
elevation: 0,
)),
body: Stack(
children: <Widget>[
Positioned(
left: 0.0,
top: 0.0,
right: 0.0,
child: PreferredSize(
preferredSize: Size.fromHeight(56.0),
child: AppBar(
title: Text("Standard bottom sheet demo"),
elevation: 2.0,
)),
),
DraggableSearchableListView(),
],
)),
);
}
}
Step 2:
Define DraggableSearchableListView
class DraggableSearchableListView extends StatefulWidget {
const DraggableSearchableListView({
Key key,
}) : super(key: key);
#override
_DraggableSearchableListViewState createState() =>
_DraggableSearchableListViewState();
}
class _DraggableSearchableListViewState
extends State<DraggableSearchableListView> {
final TextEditingController searchTextController = TextEditingController();
final ValueNotifier<bool> searchTextCloseButtonVisibility =
ValueNotifier<bool>(false);
final ValueNotifier<bool> searchFieldVisibility = ValueNotifier<bool>(false);
#override
void dispose() {
searchTextController.dispose();
searchTextCloseButtonVisibility.dispose();
searchFieldVisibility.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return NotificationListener<DraggableScrollableNotification>(
onNotification: (notification) {
if (notification.extent == 1.0) {
searchFieldVisibility.value = true;
} else {
searchFieldVisibility.value = false;
}
return true;
},
child: DraggableScrollableActuator(
child: Stack(
children: <Widget>[
DraggableScrollableSheet(
initialChildSize: 0.30,
minChildSize: 0.15,
maxChildSize: 1.0,
builder:
(BuildContext context, ScrollController scrollController) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16.0),
topRight: Radius.circular(16.0),
),
boxShadow: [
BoxShadow(
color: Colors.grey,
offset: Offset(1.0, -2.0),
blurRadius: 4.0,
spreadRadius: 2.0)
],
),
child: ListView.builder(
controller: scrollController,
///we have 25 rows plus one header row.
itemCount: 25 + 1,
itemBuilder: (BuildContext context, int index) {
if (index == 0) {
return Container(
child: Column(
children: <Widget>[
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: EdgeInsets.only(
top: 16.0,
left: 24.0,
right: 24.0,
),
child: Text(
"Favorites",
style:
Theme.of(context).textTheme.headline6,
),
),
),
SizedBox(
height: 8.0,
),
Divider(color: Colors.grey),
],
),
);
}
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0),
child: ListTile(title: Text('Item $index')));
},
),
);
},
),
Positioned(
left: 0.0,
top: 0.0,
right: 0.0,
child: ValueListenableBuilder<bool>(
valueListenable: searchFieldVisibility,
builder: (context, value, child) {
return value
? PreferredSize(
preferredSize: Size.fromHeight(56.0),
child: Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1.0,
color: Theme.of(context).dividerColor),
),
color: Theme.of(context).colorScheme.surface,
),
child: SearchBar(
closeButtonVisibility:
searchTextCloseButtonVisibility,
textEditingController: searchTextController,
onClose: () {
searchFieldVisibility.value = false;
DraggableScrollableActuator.reset(context);
},
onSearchSubmit: (String value) {
///submit search query to your business logic component
},
),
),
)
: Container();
}),
),
],
),
),
);
}
}
Step 3:
Define the custom sticky SearchBar
class SearchBar extends StatelessWidget {
final TextEditingController textEditingController;
final ValueNotifier<bool> closeButtonVisibility;
final ValueChanged<String> onSearchSubmit;
final VoidCallback onClose;
const SearchBar({
Key key,
#required this.textEditingController,
#required this.closeButtonVisibility,
#required this.onSearchSubmit,
#required this.onClose,
}) : super(key: key);
#override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return Container(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 0),
child: Row(
children: <Widget>[
SizedBox(
height: 56.0,
width: 56.0,
child: Material(
type: MaterialType.transparency,
child: InkWell(
child: Icon(
Icons.arrow_back,
color: theme.textTheme.caption.color,
),
onTap: () {
FocusScope.of(context).unfocus();
textEditingController.clear();
closeButtonVisibility.value = false;
onClose();
},
),
),
),
SizedBox(
width: 16.0,
),
Expanded(
child: TextFormField(
onChanged: (value) {
if (value != null && value.length > 0) {
closeButtonVisibility.value = true;
} else {
closeButtonVisibility.value = false;
}
},
onFieldSubmitted: (value) {
FocusScope.of(context).unfocus();
onSearchSubmit(value);
},
keyboardType: TextInputType.text,
textInputAction: TextInputAction.search,
textCapitalization: TextCapitalization.none,
textAlignVertical: TextAlignVertical.center,
textAlign: TextAlign.left,
maxLines: 1,
controller: textEditingController,
decoration: InputDecoration(
isDense: true,
border: InputBorder.none,
hintText: "Search here",
),
),
),
ValueListenableBuilder<bool>(
valueListenable: closeButtonVisibility,
builder: (context, value, child) {
return value
? SizedBox(
width: 56.0,
height: 56.0,
child: Material(
type: MaterialType.transparency,
child: InkWell(
child: Icon(
Icons.close,
color: theme.textTheme.caption.color,
),
onTap: () {
closeButtonVisibility.value = false;
textEditingController.clear();
},
),
),
)
: Container();
})
],
),
),
);
}
}
See the screenshots of the final output.
state 1:
The bottom sheet is shown with it's initial size.
state 2:
User dragged up the bottom sheet.
state 3:
The bottom sheet reached the top edge of the screen and a sticky custom SearchBar interface is shown.
That's all.
See the live demo here.
As #Sergio named some good alternatives it still needs more coding to make it work as it should with that said, I found Sliding_up_panel so for anyone else looking for solution You can find it here .
Still, I find it really weird that built in bottomSheet widget in Flutter does not provide options for creating "standard bottom sheet" mentioned in material.io :S
If you are looking for Persistent Bottomsheet than please refer the source code from below link
Persistent Bottomsheet
You can refer the _showBottomSheet() for your requirement and some changes will fulfil your requirement
You can do it using a stack and an animation:
class HelloWorldPage extends StatefulWidget {
#override
_HelloWorldPageState createState() => _HelloWorldPageState();
}
class _HelloWorldPageState extends State<HelloWorldPage>
with SingleTickerProviderStateMixin {
final double minSize = 80;
final double maxSize = 350;
void initState() {
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 500))
..addListener(() {
setState(() {});
});
_animation =
Tween<double>(begin: minSize, end: maxSize).animate(_controller);
super.initState();
}
AnimationController _controller;
Animation<double> _animation;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: <Widget>[
Positioned(
bottom: 0,
height: _animation.value,
child: GestureDetector(
onDoubleTap: () => _onEvent(),
onVerticalDragEnd: (event) => _onEvent(),
child: Container(
color: Colors.red,
width: MediaQuery.of(context).size.width,
height: minSize,
),
),
),
],
),
);
}
_onEvent() {
if (_controller.isCompleted) {
_controller.reverse(from: maxSize);
} else {
_controller.forward();
}
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
}
Can easily be achieved with showModalBottomSheet. Code:
void _presentBottomSheet(BuildContext context) {
showModalBottomSheet(
context: context,
builder: (context) => Wrap(
children: <Widget>[
SizedBox(height: 8),
_buildBottomSheetRow(context, Icons.share, 'Share'),
_buildBottomSheetRow(context, Icons.link, 'Get link'),
_buildBottomSheetRow(context, Icons.edit, 'Edit Name'),
_buildBottomSheetRow(context, Icons.delete, 'Delete collection'),
],
),
);
}
Widget _buildBottomSheetRow(
BuildContext context,
IconData icon,
String text,
) =>
InkWell(
onTap: () {},
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16),
child: Icon(
icon,
color: Colors.grey[700],
),
),
SizedBox(width: 8),
Text(text),
],
),
);