It's written here That
/// The scaffold will expand to fill the available space. That usually
/// means that it will occupy its entire window or device screen. When
/// the device's keyboard appears the Scaffold's ancestor [MediaQuery]
/// widget's [MediaQueryData.viewInsets] changes and the Scaffold will
/// be rebuilt. By default the scaffold's [body] is resized to make
/// room for the keyboard.
According to this if there is a TextField at the bottom, the Scaffold will resize itself and it does happen. But when I put a TextField inside a modalBottomSheet it doesn't get pushed up by the keyboard. The Keyboard overlaps the modalBottomSheet (with the TextField). If the Scaffold itself gets resized how modalBottomSheet stays at its place? And resizeToAvoidBottomInsethas no effect on modalBottomSheet.
Here is the sample code.
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
showModalBottomSheet(
context: context, builder: (context) => ShowSheet());
},
),
);
}
}
class ShowSheet extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
height: 200,
child: TextField(
autofocus: true,
),
);
}
}
I apologize if this question is dumb but I didn't understand this.
I still don't know the reason may be because modalBottomSheet is using PopupRoute so it's a different route not sure. Anyway, here I found the solution I just needed to put some bottom viewInsets padding.
Widget build(BuildContext context) {
return Padding(
padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Container(
color: Colors.blue,
height: 200,
child: TextField(
autofocus: true,
),
),
);
}
Also I needed to set isScrollControlled: true, of showModalBottomSheet()
Related
I'm creating a ListView with a builder function. I use the widget inspector to assess the any issues with the layout of the widgets.
Usually, the listView shows downwards green arrows as shown here:
[ListView layout][1]
However, in my current app, whenever I create a listView, it shows this green overlay on the listView. This creates artefacts with Image widget nested in stack; the images flicker when scrolling. [artefact layout][2]
This layout does look like it will take the space of 'drawer' in scaffold however, this page does not have a drawer, although all the other pages do.
Please find the code below for your reference.
const BlogListPage({Key? key}) : super(key: key);
#override
State<BlogListPage> createState() => _BlogListPageState();
}
class _BlogListPageState extends State<BlogListPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: ASAppBar(
title: const Text('Blogs'),
),
body: ListView.builder(
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return Container(
margin:
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
color: Colors.amber,
child: const SizedBox(
height: 100,
width: double.infinity,
),
);
},
));
}
} ```
[1]: https://i.stack.imgur.com/O7Pnc.png
[2]: https://i.stack.imgur.com/0g5HF.png
I’m working on the concept that you can see on the screenshot below:
design concept
Note: the arrows are not the part of the UI, but were added to demonstrate the draggable functionality.
The screen has a SliverAppBar that displays location title, Sliver body that contains location description, and has a DraggableScrollableSheet (or a similar alternative).
When the location description is scrolled up, the title collapses.
When the DraggableScrollableSheet is scrolled up it expands to the full height of the screen.
I tried many times to put it together, but something is always off.
My last attempt was to add DraggableScrollableSheet as a ‘bottom sheet:’ in Scaffold. Since I have a BottomAppBar, it breaks the UI, and looks the following way:
current UI behavior
Scaffold
#override
Widget build(BuildContext context) {
return Scaffold(
body: body,
extendBody: true,
appBar: appBar,
bottomSheet: hasBottomSheet
? DraggableScrollableSheet(
builder:
(BuildContext context, ScrollController scrollController) {
return Container(
color: Colors.blue[100],
child: ListView.builder(
controller: scrollController,
itemCount: 25,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text('Item $index'));
},
),
);
},
)
: null,
backgroundColor: Colors.white,
floatingActionButtonLocation: fab_position,
floatingActionButton: hasActionButton ? ScannerFAB() : null,
bottomNavigationBar: AppBarsNav(hasNavButtons: hasNavButtons));
}
Scaffold body
class LocationPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScaffoldWithNav(
hasBottomSheet: true,
body: CustomScrollView(
slivers: <Widget>[
SliverBar(
title: "Location",
hasBackground: true,
backgroundImagePath: 'assets/testImage.jpg'),
SliverToBoxAdapter(
child: Text("very long text "),
),
SliverPadding(
padding: EdgeInsets.only(bottom: 70),
),
],
),
);
}
}
BottomAppBar FAB
class ScannerFAB extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FloatingActionButton(
child: WebsafeSvg.asset('assets/qr-code.svg',
color: Colors.white, height: 24, width: 24),
);
}
}
The FAB jumps, the content is hidden.
When I set a fixed-sized container, the content comes back, but the FAB is still living its own life:)
current UI behavior2
If anyone has any idea how to solve this issue/those issues please share, I’ll be very grateful!
You can try to add another Scaffold on current body and put the DraggableScrollableSheet inside it. Then the DraggableScrollableSheet won't affect the FAB outside.
#override
Widget build(BuildContext context) {
return Scaffold(
body: Scaffold(
body: body,
bottomSheet: ... // move DraggableScrollableSheet to here
),
...
floatingActionButton: ... // keep FAB here
...
)
You can use Stack into Body, for example:
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children. [
SingleChildScrollView(),
DraggableScrollableSheet(),
]
),
...
floatingActionButton: ... // keep FAB here
...
)
I have a RaisedButton widget inside of a Center widget as one of the widgets in a Column of widgets. I want to add a CircularProgressIndicator to the right side of this button and show it when the button is pressed. Yet I want to leave the button centred when the progress bar is shown. In other words I want the button always be in the center and the progress bar aligned to this button.
I tried to use a Row here but this pushes the button and it becomes not centred any more.
EDIT1: Looking at the result of the solution provided by #Anil Chauhan (thanks for the answer):
Like I said before that I tried to use Row like he did, the problem is that in this case the button is not in the centred in the screen and is pushed by the progress bar. And I need it to stay in the middle of it's row.
EDIT2: #Anil Chauhan edited answer now works for a specific case in which the button is predetermined size. But if the size of the button is changed based on the language of the text (in apps that have several languages) this solution will not work.
This is the reason the question I asked is: "How to align widget to another widget". Because if I could that I don't have to worry about the button text size any more.
What would be the right way to handle this in Flutter?
class MyPage extends StatefulWidget {
#override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
bool _showIndicator = false;
void _onButtonClicked() {
setState(() {
_showIndicator = !_showIndicator;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Row(
children: <Widget>[
const Expanded(child: SizedBox()),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: RaisedButton(
child: Text("I am Too Big"),
onPressed: _onButtonClicked,
),
),
Expanded(
child: _showIndicator
? const Align(
alignment: Alignment.centerLeft,
child: CircularProgressIndicator(),
)
: const SizedBox(),
),
],
),
),
);
}
}
Here is my explanation:
The RaisedButton size is depends on its child. If you add it to Row it will automatically align to left(or start).
Expanded widget will fill the remaining space in Flex widget(Row & Column are child classes of Flex). If you add more than one Expanded widgets, it will split equally. So I added two Expanded to both the side of button to make it center.
Now We should give child for Expanded Widget.
For the first one(left) we don't have anything to display so I added SizedBox.
For the second one(right) we need CircularProgressIndicator. so I added it.
The Expanded widget will try to make its child to fill the space inside of it. So the CircularProgressIndicator will become Ellipse shaped. We can avoid this by using Align Widget.
Try this:
Updated:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyAppOne(),
);
}
}
class MyAppOne extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyAppOne>{
bool show = false;
#override
Widget build(BuildContext context){
return Scaffold(
body: Stack(
alignment: Alignment.center,
children: <Widget>[
Container(
alignment: Alignment.center,
child: RaisedButton(
onPressed: () {
setState(() {
show =!show;
});
},
child: Text('Show'),
),
),
Positioned(
right: MediaQuery.of(context).size.width * .20,
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(10.0),
child: show ? CircularProgressIndicator() : Container(),
),
)
],
)
);
}
}
Flutter's Column and Row widgets have two convenient properties called mainAxisAlignment and crossAxisAlignment. I assume since you're using a Column and want the CircularProgressIndicator to the right of the button, you might be want to use crossAxisAlignment since the cross-axis of a Column is along the horizontal.
If possible, please share your code for better understanding and support of the issue.
I want refresh my page without having a scrollable content, i.e. without having a ListView et al.
When I want use RefreshIndicator, the documentation says it needs a scrollable widget like ListView.
But if I want to refresh and want to use the refresh animation of RefreshIndicator without using a ListView, GridView or any other scorllable widget, how can i do that?
You can simply wrap your content in a SingleChildScrollView, which will allow you to use a RefreshIndicator. In order to make the pull down to refresh interaction work, you will have to use AlwaysScrollableScrollPhysics as your content will most likely not cover more space than available without a scroll view:
RefreshIndicator(
onRefresh: () async {
// Handle refresh.
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: /* your content */,
),
);
You can just use GestureDetector, I have created a sample for you, but it's not perfect, you can customize it to your own needs, it just detects when you swipe from the top.
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
var refresh=false;
void refreshData(){
if(!refresh){
refresh=true;
print("Refreshing");
Future.delayed(Duration(seconds: 4),(){
refresh =false;
print("Refreshed");
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text("Test"),
centerTitle: true,
),
body: GestureDetector(
child: Container(
color: Colors.yellow,
height: double.infinity,
width: double.infinity,
child: Center(child: Text('TURN LIGHTS ON')),
),
onVerticalDragUpdate: (DragUpdateDetails details){
print("direction ${details.globalPosition.direction}");
print("distance ${details.globalPosition.distance}");
print("dy ${details.globalPosition.dy}");
if(details.globalPosition.direction < 1 && (details.globalPosition.dy >200 && details.globalPosition.dy < 250)){
refreshData();
}
},
));
}
}
I want my bottom sheet to stay on the screen till I close it from a code. Normally the bottom sheet can be closed by pressing back button(device or appbar) or even just by a downward gesture. How can I disable that?
_scaffoldKey.currentState
.showBottomSheet<Null>((BuildContext context) {
final ThemeData themeData = Theme.of(context);
return new ControlBottom(
songName: songName,
url: url,
play: play,
pause: pause,
state: test,
themeData: themeData,
);
}).closed.whenComplete((){
});
Control botton is a different widget.
Scaffold now has a bottom sheet argument and this bottom sheet cannot be dismissed by swiping down the screen.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(....),
bottomSheet: Container(
child: Text('Hello World'),
),
);
}
Also you can use WillPopScope Widget to control pressing back button:
Widget _buildBottomSheet() {
return WillPopScope(
onWillPop: () {
**here you can handle back button pressing. Just leave it empty to disable back button**
},
child: **your bottom sheet**
)),
);
}
You can remove the appbar back button by providing an empty container in leading property.
AppBar(
leading: Container(),
);
But we don't have any control over device back button & bottomsheet will disappear on back pressed.
One of the many alternative approach could be using a stack with positioned & opacity widget
Example :
Stack(
children: <Widget>[
// your code here
Positioned(
left: 0.0,
right: 0.0,
bottom: 0.0,
child: Opacity(
opacity: _opacityLevel,
child: Card(
child: //Your Code Here,
),
),
),
// your code here
],
);
You can change _opacityLevel from 0.0 to 1.0 when a song is selected.
From what I can make out from your code is that you will be having a listView on top & music controls on the bottom. Make sure to add some Padding at the end of listView so that your last list item does not stay hidden behind your music controller card when you have scrolled all the way down.
If you want to further customize the look & feel of your music controller. You could use animationController or sizeAnimation to slide it from the bottom like a bottomSheet.
I hope this helps.
Add this parameters to showmodalbottomsheet,
isDismissible: false, enableDrag: false,
I wanted a bottomsheet that is draggable up and down, but does not close. So, first of all I created a function for my modalBottomSheet.
Future modalBottomSheetShow(BuildContext context) {
return showModalBottomSheet(
backgroundColor: Colors.transparent,
context: context,
builder: (context) => buildSheet(),
isDismissible: false,
elevation: 0,
);
}
Next, I used .whenComplete() method of showModalBottomSheet() to recursively call the modalBottomSheetShow() function.
Future modalBottomSheetShow(BuildContext context) {
return showModalBottomSheet(
backgroundColor: Colors.transparent,
context: context,
builder: (context) => buildSheet(),
isDismissible: false,
elevation: 0,
).whenComplete(() => modalBottomSheetShow(context));
}
Next, I simply call the modalBottomSheetShow() whenever I wanted a bottomsheet. It cannot be closed, until the recursion ends. Here is the entire code for reference:
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
static const idScreen = "HomePage";
#override
State<HomePage> createState() => _HomePageState();
}
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) async {
modalBottomSheetShow(context);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
toolbarHeight: 0,
elevation: 0,
backgroundColor: Colors.black,
),
);
}
Widget buildSheet() {
return DraggableScrollableSheet(
initialChildSize: 0.6,
builder: (BuildContext context, ScrollController scrollController) {
return Container(
decoration: BoxDecoration(color: Colors.white, boxShadow: [
BoxShadow(
color: Color(0x6C000000),
spreadRadius: 5,
blurRadius: 20,
offset: Offset(0, 0),
)
]),
padding: EdgeInsets.all(16),
);
},
);
}
Future modalBottomSheetShow(BuildContext context) {
return showModalBottomSheet(
backgroundColor: Colors.transparent,
context: context,
builder: (context) => buildSheet(),
isDismissible: false,
elevation: 0,
).whenComplete(() => modalBottomSheetShow(context));
}
}