What I would like to do
I want to make it possible for the user to drag the blue area up with their finger.
Problem
cant drag up draggable
Architecture
NestedScrollView (
headerSliver:SliverAppBar(...)
body: Stack(
childern: [
Expanded(
child: SliverGrid(),
),
Draggable(),
],
),
),
What I checked
I confirmed that the touch works on the draggable. I made an InkWell with a builder and confirmed that the onPressed function works.
I tried with CustomScrollView as well, but it still didn't work.
code
NestedScrollView
NestedScrollView(
headerSliverBuilder: (context, innerBoxScrolled) {
return [
SliverAppBar(
leading: null,
elevation: 0,
expandedHeight: 160,
pinned: false,
flexibleSpace: TopNavBar(
height: 100,
),
bottom: AppBar(
leading: null,
toolbarHeight: 100,
elevation: 0,
actions: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AdTopTabBar(),
FilterBar(),
],
),
],
),
),
];
},
body: Stack(
children: [
Expanded(
child: HomeAdCollection(),
),
MyAdDraggable(),
],
),
),
Draggable
class MyAdDraggable extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DraggableScrollableSheet(
key: key,
initialChildSize: 0.05,
minChildSize: 0.05,
maxChildSize: 0.7,
expand: true,
builder: (context, scrollcontroller) {
return InkWell(
onTap: () {
print('ff');
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
color: Colors.blue[100],
),
),
);
},
);
}
}
There must be at least one child in the DraggableScrollableSheet that uses scrolling. Otherwise, you can't drag it up.
When I put ListView as a child in DraggableScrollableSheet, it works well!
Related
I'm begginning Flutter and I'm searching to scroll ListView over a Container like below
You can use DraggableScrollableSheet
DraggableScrollableSheet(
builder: (context, scrollController) {
return SingleChildScrollView(
controller: scrollController,
child: Column(
children: [],
),
);
},
)
You can follow the doc example
A complete widget
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
openDraggable(context) {
showModalBottomSheet(
isScrollControlled: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(22),
topRight: Radius.circular(22),
)),
context: context,
builder: (context) => DraggableScrollableSheet(
maxChildSize: 1,
initialChildSize: .4,
expand: false,
builder: (context, scrollController) {
return SingleChildScrollView(
controller: scrollController,
child: Column(
children: [
for (int i = 0; i < 32; i++)
ListTile(
title: Text("item"),
),
],
),
);
},
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
openDraggable(context);
},
child: Text("open"),
)
],
),
),
);
}
}
Don't use DraggableScrollableSheet. instead of that widget use the sliver widget, this widget is amazing,
you can use this article and this
CustomScrollView(
slivers: <Widget>[
// appbar of search page
SliverAppBar(
// icon in top left
leading: _icon(context),
// backgroun of appbar
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
// style of app bar
pinned: true,
floating: true,
snap: true,
expandedHeight: 130,
// obtional filter for product
flexibleSpace: FlexibleSpaceBar(
background: Padding(
padding: const EdgeInsets.only(top: 80.0),
child: Container(
alignment: Alignment.center,
margin: const EdgeInsets.all(8.0),
padding: const EdgeInsets.symmetric(horizontal: 10.0),
decoration: BoxDecoration(
borderRadius: borderRadius_30,
color: Theme.of(context).primaryColorLight),
child: TextField(
onChanged: (value) {
// you can sort data for user in this section
},
style: TextStyle(
color: Theme.of(context).textSelectionColor,
),
decoration: InputDecoration(
border: InputBorder.none,
// styel of labeltext in textfield
labelStyle: TextStyle(
color: Colors.cyan,
),
labelText: Strings.search,
),
),
),
),
),
),
// body
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return SearchBody(index);
},
childCount: fakeData.length,
),
// number of object
)
],
),
You can use the sliver package, something like this.
I want to have a modal bottom sheet that I can scroll through as normal without closing the modal bottom sheet. Then, when I'm at the top of my list, I want to be able to close the Modal Bottom Sheet with a scroll gesture.
I would like to add the YouTube style modal bottom sheet shown in the video to my app, but unfortunately I don't know how to do it.
This is the link to the video that shows what I mean: https://drive.google.com/file/d/1977Ptq4Sox6WKGQkiTbCH_zydn8Mu05o/view?usp=sharing
This is inside my Profile Screen:
#override
Widget build(BuildContext context) {
return Material(
color: Colors.transparent,
child: Container(
height: MediaQuery.of(context).size.height * 0.95,
margin: EdgeInsets.only(bottom: 5, left: 10, right: 10),
decoration: BoxDecoration(
border: Border.all(color: Colors.black, width: 1.5),
borderRadius: BorderRadius.circular(25),
color: Colors.white,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(23),
child: DraggableScrollableSheet(
initialChildSize: 1,
minChildSize: 0.5,
maxChildSize: 1,
expand: false,
builder: (BuildContext context, ScrollController _scrollController) {
return SingleChildScrollView(
controller: _scrollController,
child: Column(
children: [
Container(
height: 590,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(width: 1.5, color:
Colors.black),
),
color: Colors.white,
),
child: Stack(
children: [
InteractiveViewer(
transformationController:
_zoomProfilePictureController,
minScale: 1,
maxScale: 3,
onInteractionEnd: (ScaleEndDetails endDetails) {
setState(() {
_zoomProfilePictureController.value =
Matrix4.identity();
});
},
child: Swiper(
physics: NeverScrollableScrollPhysics(),
itemBuilder:
(BuildContext context, int imageIndex) {
return Hero(
tag: "Profile",
child: CachedNetworkImage(
imageUrl: widget.user.imageUrl[imageIndex],
fit: BoxFit.cover,
useOldImageOnUrlChange: true,
placeholder: (context, url) => Center(
child: CircularProgressIndicator(
strokeWidth: 3,
valueColor: AlwaysStoppedAnimation<Color>(
primaryColor.withOpacity(0.7),
),
),
),
errorWidget: (context, url, error) => Icon(
Icons.error_outline,
size: 30,
color: Colors.red,
),
),
);
},
itemCount: widget.user.imageUrl.length,
pagination: SwiperPagination(
alignment: Alignment.topCenter,
margin: EdgeInsets.all(0),
builder: DotSwiperPaginationBuilder(
color: Colors.white.withOpacity(0.7),
size: 8,
activeColor: widget.user.imageUrl.length > 1
? primaryColor
: Colors.transparent,
activeSize: 10,
),
),
control: widget.user.imageUrl.length > 1
? SwiperControl(
padding: const EdgeInsets.only(top: 250),
color: Colors.transparent,
disableColor: Colors.transparent,
size: 228,
)
: null,
loop: false,
),
),
This is how I call the BottomSheet:
ListTile(
onTap: () => showModalBottomSheet(
backgroundColor: Colors.transparent,
context: context,
builder: (context) {
return ProfileInformationHomeScreen(
secondUser,
widget.currentUser,
);
},
),
it's DraggableScrollableSheet.
create a widget for bottomSheet.
#override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
child: DraggableScrollableSheet(
///* it will be always visible 95% of screen,
///* if we drag down at 50% it will close the sheet
initialChildSize: 0.95,
minChildSize: 0.5,
maxChildSize: .95,
expand: false,
builder: (BuildContext context, c) {
return Container(
color: Colors.white,
child: ListView(
controller: c,
children: [
/// your widgets
call a method to showModalBottomSheet.
_showBottomSheet(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => DestinationBottomSheetWidget(),
);
}
To close the buttomSheet you can drag or use a button inside DraggableScrollableSheet builder and use Navigator.of(context).pop().
I do like this and add item of ListView
/// heading
Padding(
padding: EdgeInsets.only(
left: 20,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Icon(
Icons.close,
color: Colors.grey,
),
),
Text(
"Title",
textAlign: TextAlign.center,
),
///* this empty widget will handle the spaces
Icon(
Icons.close,
color: Colors.grey.withOpacity(0.0),
),
],
),
),
for more flutter doc youtube
I want to create bottom sheet like Instagram post sharing page
everything work fine but I need to start flexibleSpace animation before DraggableScrollableSheet at the maxChildSize property and finished at maxChildSize
Stack(
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
Navigator.of(cotext).pop()
},
child: Container(
width: context.width,
height: context.height,
),
),
SizedBox.fromSize(
size: Size.fromHeight(context.height),
child: DraggableScrollableSheet(
minChildSize: 0.5,
maxChildSize: 1,
initialChildSize: 0.5,
builder: (context, scroll) {
return SafeArea(
child: Container(
color: Colors.white,
child: CustomScrollView(
controller: scroll,
slivers: [
SliverAppBar(
pinned: true,
title:title,
),
SliverList(
delegate:
SliverChildBuilderDelegate((context, index) {
return Object()
}, childCount: 100))
],
),
),
);
},
),
),
],
);
Here's the widget I'm using in the appBar;
appBar: AppBar(actions: <Widget>[myAppBarIcon()],)
And the code for the widget itself;
Widget myAppBarIcon() {
return ValueListenableBuilder(
builder: (BuildContext context, int newNotificationCounterValue,
Widget child) {
return newNotificationCounterValue == 0? Container(): Stack(
children: [
Icon(
Icons.notifications,
color: Colors.white,
size: 30,
),
Container(
width: 30,
height: 30,
child: Container(
width: 15,
height: 15,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Color(0xffc32c37),
border: Border.all(color: Colors.white, width: 1)),
child: Padding(
padding: const EdgeInsets.all(0.0),
child: Text(
newNotificationCounterValue.toString(),
),
),
),
),
],
);
},
valueListenable: notificationCounterValueNotifer,
);
}
But I would now like to use the widget in a floating action button instead of the appBar. So here's what I tried;
Scaffold(
body: FoldableSidebarBuilder(
drawerBackgroundColor: Colors.black45,
drawer: CustomDrawer(closeDrawer: (){
setState(() {
drawerStatus = FSBStatus.FSB_CLOSE;
});
},),
screenContents: mainStage(),
status: drawerStatus,
),
floatingActionButton: Stack(
children: <Widget>[
Positioned(
bottom:140,
left: 17,
child: FloatingActionButton(
mini: true,
heroTag: null,
backgroundColor: Colors.black12,
child: myAppBarIcon()
),
),
],
),
),
But although no error comes up, myAppBarIcon isn't appearing in the build. Where did I go wrong?
Turns out it does work. I didn't think so at first till I realized I needed to receive an FCM notification before it appeared. This line was the reason.;
return newNotificationCounterValue == 0? Container()
So I'm going to put an empty notification icon in the container.
I have a ListView where I want to implement a nice way to delete list items using a bottom sheet with actions on. Initially I went down the path of simply calling showBottomSheet() in the onLongPress event handler for my list items, which would successfully open a bottom sheet with my action buttons on. However, this would automatically add a back button to the AppBar which is not what I want.
I then went down the route of trying out animations, such as SlideTransition and AnimatedPositioned:
class FoldersListWidget extends StatefulWidget {
#override
_FoldersListWidgetState createState() => _FoldersListWidgetState();
}
class _FoldersListWidgetState extends State<FoldersListWidget>
with SingleTickerProviderStateMixin {
double _bottomPosition = -70;
#override
Widget build(BuildContext context) {
return Stack(
children: [
FutureBuilder<List<FolderModel>>(
future: Provider.of<FoldersProvider>(context).getFolders(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, i) {
final folder = snapshot.data[i];
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15),
),
margin: EdgeInsets.symmetric(vertical: 5, horizontal: 10),
elevation: 1,
child: ListTile(
title: Text(folder.folderName),
leading: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
width: 50,
child: Consumer<FoldersProvider>(
builder:
(BuildContext context, value, Widget child) {
return value.deleteFolderMode
? CircularCheckBox(
value: false,
onChanged: (value) {},
)
: Icon(
Icons.folder,
color: Theme.of(context).accentColor,
);
},
),
),
],
),
subtitle: folder.numberOfLists != 1
? Text('${folder.numberOfLists} items')
: Text('${folder.numberOfLists} item'),
onTap: () {},
onLongPress: () {
Provider.of<FoldersProvider>(context, listen: false)
.toggleDeleteFolderMode(true); // removes fab from screen
setState(() {
_bottomPosition = 0;
});
},
),
);
},
);
},
),
AnimatedPositioned(
bottom: _bottomPosition,
duration: Duration(milliseconds: 100),
child: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(25),
topRight: Radius.circular(25),
),
child: Container(
height: 70,
width: MediaQuery.of(context).size.width,
color: Theme.of(context).colorScheme.surface,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Expanded(
child: IconAboveTextButton(
icon: Icon(Icons.cancel),
text: 'Cancel',
textColour: Colors.black,
opacity: 0.65,
onTap: () => setState(() {
_bottomPosition = -70;
}),
),
),
VerticalDivider(
color: Colors.black26,
),
Expanded(
child: IconAboveTextButton(
icon: Icon(Icons.delete),
text: 'Delete',
textColour: Colors.black,
opacity: 0.65,
),
),
],
),
),
),
),
],
);
}
}
This slides the bottom Container on and off the screen but my issue is that it covers the last list item:
Could anyone suggest a better way of doing this or simply a way to adjust the height of the ListView so that when the Container slides up, the ListView also slides up.
Wrap ListView.builder inside a container and set its bottom padding as (70+16)
70 (height of bottom sheet), 16 (some default padding to make it look better if you like).
return Container(
padding: EdgetInset.ony(bottom: (70+16)),
child:ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, i) {
.....
.....