Adding DraggableScrollableSheet to the bottom of a Sliver page - flutter

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
...
)

Related

Adding item to widget dynamically doesn't update widget's height

When I add a widget to a sliding_up_panel widget dynamically after the initial load, the panel's height does not increase, meaning that the bottom of the panel gets cut off and I get the A RenderFlex overflowed by 27 pixels on the bottom. error.
What can I do to ensure the panel updates its height in this case please?
To Reproduce
Add a widget to the panel dynamically via the condition ? widget : widget logic. For example:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("SlidingUpPanelExample"),
),
body: Stack(
children: <Widget>[
Center(child: Text("This is the Widget behind the sliding panel"),),
SlidingUpPanel(
panel: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
myFirstVariable ? Text("This is a sliding Widget") : Container(),
mySecondVariable ? Text("This is another sliding Widget") : Container(),
],
),
)
)
],
)
);
}
Thanks so much!
the SlidingUpPanel widget doesn't adjust its height according to its children.
A solution to your problem would be add a SingleChildScrollView to your panel content and use the panelBuilder instead of the panel property.
The panelBuilder takes the burden of managing scrolling and sliding conflicts by providing you a controller that you can feed to your SingleChildScrollView, your code will change to be like the following :
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("SlidingUpPanelExample"),
),
body: Stack(
children: <Widget>[
Center(child: Text("This is the Widget behind the sliding panel"),),
SlidingUpPanel(
panelBuilder:(sc) => SingleChildScrollView(
controller: sc,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
myFirstVariable ? Text("This is a sliding Widget") : Container(),
mySecondVariable ? Text("This is another sliding Widget") : Container(),
],
),
)
)
],
)
);
}
Like this, you won't have any overflow issues, and when the content of the panel overflows the SingleChildScrollView will become scrollable.
hope this helps:
use a controller,
You can control the height of the panel depending on the items on the list (0.0 to 1.0). Ensure to update the UI once you add a new item.
use a List
This will solve the renderOverflow you mentioned
/// just for the example
List<Widget> items = <Widget>[];
//adding items
addItem() {
items.add(Container(
constraints: BoxConstraints(maxHeight: 200, minHeight: 200),
color: Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0),
));
/// if you know in advance the height of the widgets been rendered: 200
/// if you know in advance the height of the panel: 500, or context.size.height / 2 (which would be 50% of screen) asume 600
///
/// increase panel height panel according to the number of items
///
_pc.panelPosition =
(items.length * 200) / 600 > 1.0 ? 1 : (items.length * 200) / 600;
/// Hey
/// SET State after adding the item
/// to refresh UI
}
Widget _scrollingList(ScrollController sc) {
return ListView.builder(
controller: sc,
itemCount: items.length,
itemBuilder: (context, index) {
return items[index];
},
);
}
PanelController _pc = new PanelController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("SlidingUpPanelExample"),
),
floatingActionButton: FloatingActionButton(
onPressed: addItem,
),
body: Stack(
children: <Widget>[
Center(
child: Text("This is the Widget behind the sliding panel"),
),
SlidingUpPanel(
controller: _pc,
panelBuilder: (ScrollController sc) => _scrollingList(sc),
),
],
));
Plugin has the methods: minHeight and maxHeight to do the work. I think is not posible change it dynamically without set that attributes.

Flutter - Screen focus on a certain widget

I need help to do the following: when I press List 1, the screen focuses on List 1; I need the same for the rest of the options
This is the code for the example:
code
This behavior already exists in web pages but I haven't found this same behavior at the mobile app level. Thank you
Here is a small code snippet of something similar which might help you achieve you desired results.
By clicking the fab icon it will scroll down to item 35 within the ListView.
class MyHomePage extends StatelessWidget {
final _scrollController = ScrollController();
final _cardHeight = 200.0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.orange,
onPressed: () => _animateToIndex(35),
child: Icon(Icons.add),
),
body: ListView.builder(
controller: _scrollController,
itemCount: 100,
itemBuilder: (_, i) => Container(
height: _cardHeight,
child: Card(
color: Colors.lightBlue,
child: Center(
child: Text("Scroll Item $i", style: TextStyle(fontSize: 28.0),),
),
),
),
),
);
}
_animateToIndex(index) {
_scrollController.animateTo(_cardHeight * index,
duration: Duration(seconds: 1), curve: Curves.fastOutSlowIn);
}
}
You'll need to have a scrollable Widget (like ListView, SingleScrollableWidget) instead of a Column in ListSecondPage.
Then add a ScrollController to it and ListSecondPage should receive which button was tapped. Based on that selection you can scroll to the desired location with the ScrollController

modalBottomSheet is overlapped up by the keyboard

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()

Refresh widget or page in Flutter without ListView et al

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();
}
},
));
}
}

Flutter How to remove overscroll effect from ListView [duplicate]

By default, flutter adds a glowing effect on ListView/GridView/... to overscrolls on android phones
I would like to remove this effect entirely or on one specific scrollable.
I know that I can change ScrollPhysics to change between Bounce/Clamp. But this doesn't actually remove the glow effect.
What can I do ?
The glow effect comes from GlowingOverscrollIndicator added by ScrollBehavior
To remove this effect, you need to specify a custom ScrollBehavior. For that, simply wrap any given part of your application into a ScrollConfiguration with the desired ScrollBehavior.
The following ScrollBehavior will remove the glow effect entirely :
class MyBehavior extends ScrollBehavior {
#override
Widget buildOverscrollIndicator(
BuildContext context, Widget child, ScrollableDetails details) {
return child;
}
}
To remove the glow on the whole application, you can add it right under MaterialApp :
MaterialApp(
builder: (context, child) {
return ScrollConfiguration(
behavior: MyBehavior(),
child: child,
);
},
home: new MyHomePage(),
);
To remove it on a specific ListView, instead wrap only the desired ListView :
ScrollConfiguration(
behavior: MyBehavior(),
child: ListView(
...
),
)
This is also valid if you want to change the effect. Like adding a fade when reaching borders of the scroll view.
The glow will disappear by changing the ListView's physics property to BouncingScrollPhysics to imitate the List behavior on iOS.
ListView.builder(
physics: BouncingScrollPhysics(),
}
The above solution did not work for me. I did this from another solution.
Wrap it with this widget to remove the shadow completely:
NotificationListener<OverscrollIndicatorNotification>(
onNotification: (overscroll) {
overscroll.disallowGlow();
},
child: new ListView.builder(
//Your stuff here.
),
),
You can try BouncingScrollPhysics with all list or grid or scrollview:
//ScrollView:
SingleChildScrollView(
physics: BouncingScrollPhysics(),
)
//For ListView:
ListView.builder(
physics: BouncingScrollPhysics(),
}
//GridView
GridView.Builder(
physics: BouncingScrollPhysics(),
)
You can wrap your SingleChildScrollView or ListView.
NotificationListener<OverscrollIndicatorNotification>(
onNotification: (OverscrollIndicatorNotification overscroll) {
overscroll.disallowGlow();
return;
},
child: SingleChildScrollView()
)
Update on 2021
as buildViewportChrome is deprecated on March `21, we may have new way to implement this
A. Working Solution
class MyCustomScrollBehavior extends MaterialScrollBehavior {
#override
Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) {
return child;
}
}
class MainApp extends StatelessWidget {
const MainApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
scrollBehavior: MyCustomScrollBehavior(),
title: 'App Title',
home: HomeUI(),
);
}
}
B. Explanation
By default, Flutter wraps any child widget into GlowingOverscrollIndicator as below code.
#override
Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) {
switch (getPlatform(context)) {
case TargetPlatform.iOS:
case TargetPlatform.linux:
case TargetPlatform.macOS:
case TargetPlatform.windows:
return child;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
return GlowingOverscrollIndicator(
axisDirection: details.direction,
color: Theme.of(context).colorScheme.secondary,
child: child, // < ---------- our Child Widget is wrapped by Glowing Indicator
);
}
}
So we can easily override it, by directly return child without wrapping it to GlowingOverscrollIndicator
class MyCustomScrollBehavior extends MaterialScrollBehavior {
#override
Widget buildOverscrollIndicator(
BuildContext context, Widget child, ScrollableDetails details) {
return child;
}
}
You don't need to build your own custom ScrollBehavior class. Instead, just wrap your scrollable widget in a ScrollConfiguration widget and set the behavior property to:
const ScrollBehavior().copyWith(overscroll: false).
Full code example:
ScrollConfiguration(
behavior: const ScrollBehavior().copyWith(overscroll: false),
child: PageView(
physics: const PageScrollPhysics(),
controller: model.pageController,
children: [
PageOne(),
PageTwo(),
PageThree(),
PageFour(),
],
),
),
try this work for me mybe work for you to
ScrollConfiguration(
behavior: new ScrollBehavior()..buildViewportChrome(context, null, AxisDirection.down),
child: SingleChildScrollView()
);
You can also try
SingleChildScrollView(
physics: ClampingScrollPhysics(),
)
If you migrated to null safety, you might get issues with the behavior. You can use this method that works with null safety:
NotificationListener<OverscrollIndicatorNotification>(
onNotification: (OverscrollIndicatorNotification? overscroll) {
overscroll!.disallowGlow();
return true;
},
child: child,
),
The currently accepted answer is outdated in the current version of Flutter.
Scroll behavior's ScrollBehavior.copyWith() method has an overscroll flag which can be set to false to avoid having to create your own ScrollBehavior class.
For example:
ScrollConfiguration(
behavior: MaterialScrollBehavior().copyWith(overscroll: false),
child : someScrollableWidget
)
`
It isn't good practice to just change the scroll behavior, as you may lose the native scrolling feel when running your app on different devices.
I have used below one for Scroll body without Scroll glow effect
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ScrollConfiguration(
behavior: new ScrollBehavior()
..buildViewportChrome(context, null, AxisDirection.down),
child: SingleChildScrollView(
After Flutter 2.10 update Previous NotificationListener parameter code has been removed/deprecated.
New Code
NotificationListener<OverscrollIndicatorNotification>(
onNotification: (overscroll) {
overscroll.disallowIndicator(); //previous code overscroll.disallowGlow();
return true;
},
child: ListView(
padding: const EdgeInsets.symmetric(
horizontal: 15, vertical: 15),
scrollDirection: Axis.horizontal,
children: List.generate(
items.length,
(index) => Padding(
padding: const EdgeInsets.only(right: 15),
child: AspectRatio(
aspectRatio: 13 / 9,
child:
LayoutBuilder(builder: (context, boxcon) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
boxShadow: const [
BoxShadow(
color: Colors.black12,
spreadRadius: 5,
blurRadius: 12)
],
image: DecorationImage(
fit: BoxFit.cover,
image: NetworkImage(items[index])),
color: greengradientcolor,
),
);
}),
))),
),
),