I have a panel widget that can be dragged vertically in and out from the bottom of the screen. In that panel widget, there is a ListView that is scrollable.
What I'm trying to achieve is, having the panel handle the drag for opening and closing without the nested listview interfering. Once, the panel is open, the listview become scrollable and if the listview is scrolled down while already at the top, the panel handle the gesture instead and closes.
Like so:
I tried to enable/disable scrolling physics on the ListView based on the Panel position but turned out not to be possible that way.
Any ideas ? :)
You can achieve that with DraggableScrollableSheet.
Here is a quick example of how you can use it:
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
Center(child: Text('Some content')),
DraggableScrollableSheet(
minChildSize: 0.2,
initialChildSize: 0.2,
builder: (context, scrollController) => Container(
color: Colors.lightBlueAccent,
child: ListView.builder(
controller: scrollController,
itemCount: 20,
itemBuilder: (context, index) => SizedBox(
height: 200,
child: Text('Item $index'),
),
),
),
),
],
),
);
}
Related
I would like my app have a model bottom sheet. The bottom sheet is shown only when user click a button. The bottom sheet first take up around 0.5 or less of the screen which is enough to show popular choices from a listview. User can pick their choice right from here but they also can drag up to view all the choices. The bottom sheet can only be either half or full screen. Once it go full screen, I expect it behave like a scaffold (user can scroll the list view but can not drag down to a bottom sheet anymore). How can I do it in flutter?
When user drag the bottom sheet up, it turn into a scaffold like the screen on the right.
[UPDATED]
Another slide panels, we_slide:
import 'package:we_slide/we_slide.dart';
final _colorScheme = Theme.of(context).colorScheme;
final double _panelMinSize = 70.0;
final double _panelMaxSize = MediaQuery.of(context).size.height / 2;
return Scaffold(
backgroundColor: Colors.black,
body: WeSlide(
panelMinSize: _panelMinSize,
panelMaxSize: _panelMaxSize,
body: Container(
color: _colorScheme.background,
child: Center(child: Text("This is the body 💪")),
),
panel: Container(
color: _colorScheme.primary,
child: Center(child: Text("This is the panel 😊")),
),
panelHeader: Container(
height: _panelMinSize,
color: _colorScheme.secondary,
child: Center(child: Text("Slide to Up ☝️")),
),
),
);
OR
bottom_sheet:
showFlexibleBottomSheet(
minHeight: 0,
initHeight: 0.5,
maxHeight: 1,
context: context,
builder: _buildBottomSheet,
anchors: [0, 0.5, 1],
isSafeArea: true,
);
Widget _buildBottomSheet(
BuildContext context,
ScrollController scrollController,
double bottomSheetOffset,
) {
return Material(
child: Container(
child: ListView(
controller: scrollController,
...
),
),
);
}
[OUTDATED]
Try this package sliding_sheet:
return SheetListenerBuilder(
// buildWhen can be used to only rebuild the widget when needed.
buildWhen: (oldState, newState) => oldState.isAtTop != newState.isAtTop,
builder: (context, state) {
return AnimatedContainer(
elevation: !state.isAtTop ? elevation : 0.0,
duration: const Duration(milliseconds: 400),
child: child,
);
},
);
I am trying to display a ListView Horizontal n times in Stack or Row. But in the end all the ListViews are piled up instead shows one by one from top to bottom.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Prueba para trabajar en el proyecto"),
),
body: Stack(
children: <Widget>[
_crearLista(),
_crearLista(),
_crearLista(),
_crearLista(),
_crearLista(),
//_crearLoading(),
],
),
);
}
Widget _crearLista() {
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Flexible(
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height * 0.25,
decoration: BoxDecoration(),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: _listaNumeros.length,
itemBuilder: (BuildContext context, int index) {
//return image
},
controller: _scrollController,
),
),
)
],
),
);
}
Any suggestions? please. I want to show all the LsitViews.
A Stack widget puts its children above each other (overlapped or piled up) at the left top corner of the stack by default. Unless you position each child using a Positioned or Align widget. To have all the children one below the other (starting from top of the screen towards bottom), you need to use a Column instead of Stack.
Stack children behave like layers, one covers second, etc.
If you want one child exactly below another, there is ListView and Column
As in the image shown above, I want widget 2 to always be at least the height of the remaining space available.
But widget 2 might contain so many ListTiles so that they can not be displayed without scrolling. But scrolling should affect widget 1 and widget 2. What is the best way to implement something like this?
Wrap Widget 2 in an Expanded Widget.
To scroll both Widget 1 and Widget 2, wrap both of them in a SingleChildScrollView Widget.
If you can distinguish between the case with a few and many elements (for example during loading), you can use CustomScrollView with SliverFillRemaining for this:
var _isLoading = true;
#override
Widget build(BuildContext context) {
return CustomScrollView(
slivers: [
_buildWidget1(),
_buildWidget2(),
],
);
}
Widget _buildWidget1() {
return SliverToBoxAdapter(
child: Container(height: 400, color: Colors.blue),
);
}
Widget _buildWidget2() {
if(_isLoading) {
return SliverFillRemaining(
hasScrollBody: false,
child: Center(child: const CircularProgressIndicator()),
);
} else {
return SliverFixedExtentList(
delegate: SliverChildBuilderDelegate(
_buildItem,
childCount: childCount,
),
itemExtent: 56,
);
}
}
A simple way to do that would be to place your widgets in Column and wrap it with a single child scroll view. For the ListView use shrinkWrap as true and physics you can set to NeverScrollableScrollPhysics
Here is an example
SingleChildScrollView(
child: Column(
children: [
Container(
height: MediaQuery.of(context).size.height / 2,
color: Colors.red,
),
ListView.builder(
shrinkWrap:true,
physics:NeverScrollableScrollPhysics(),
itemCount: 100,
itemBuilder: (context, index) => Text("$index"),
),
],
),
);
Hope this helps!
var widgetHeight = MediaQuery.of(context).size.height - fixedSize;
return SingleChildScrollView(
child: Container(
height: widgetHeight,
child: Widget2
)
)
I have a Stack that has some containers and horizontal listView at the very bottom of it.(Intentionally, it's a part of design). Now that I'm trying to scroll through it, it doesn't work because top container make it unreachable sort of. How can I remain the same widget tree but make the bottom listView scrollable?
return Stack(
children: <Widget>[
AnimatedBuilder(
animation: ctrl,
builder: (context, child){
var pos = ctrl.position.pixels;
return Container(
height: pos<h?(pos/h * 250):250,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemBuilder: (ctxt, index){
return Container(
height: pos<h?(pos/h * 250):250,
width: 250,
child: Image(), //Horizontal listview
);
},
),
);
},
),
ListView(
controller: ctrl,
children: <Widget>[
AnimatedBuilder(
animation: ctrl,
builder: (context, child){
var pos = ctrl.position.pixels;
return Container(
height: pos<h?(pos/h * 250):250,
);
},
),
//This is the container I'm talking about, it's just like padding(It's nothing in there)
],
),
],
);
You could put the ListView after the Container in the order inside the Stack, so it will be over the Container and you can interact with it.
Or you could wrap the Container with IgnorePointer, so the gestures won't be intercepted by the Container and you could interact with the ListView.
Inside a scroll view I want to display a widget, then a list, then a widget. Something among the lines of:
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: <Widget>[
Text("Top of ListView"),
ListView.builder(
itemCount: 100,
itemBuilder: (context, index) => Text('Line $index')),
Text("Below of ListView")
],
),
);
}
Where the content of the SingleChildScrollView would be:
Text("Top of ListView"),
Text('Line 0'),
Text('Line 1'),
...
Text('Line 98'),
Text('Line 99'),
Text("Bottom of ListView")
Of course this code doesn't work, since ListView height is undefined. Wrapping the ListView with Expanded isn't the solution since I want Text("Top of ListView") to disappear when I begin to scroll and Text("Bottom of ListView") to appear only when reaching bottom (as if they are the first item and the last item of the list, respectively).
My ideas:
Putting Text("Top of ListView") and Text("Bottom of ListView")
inside the ListView : easy to do for this example, but in real world there won't be only one widget in front of or behind the list. Messy and hard to maintain.
Adding the list items to the Column directly : aka re-implementing ListView.
Wrapping ListView into a Container with a fixed height corresponding to the calculated ListView height: I don't know how to measure childrens.
Any ideas ?
You must use CustomScrollView to achieve this:
CustomScrollView(
slivers: [
SliverList(
delegate: SliverChildListDelegate([Text("Top of ListView")]),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => Text('Line $index')
childCount: 100,
),
),
SliverList(
delegate: SliverChildListDelegate([Text("Below of ListView")]),
),
],
),