GestureDetector Not working in Listview Builder Flutter - flutter

This is the Animation Class where MoviesListView Widget Calls When i apply Animation on listViewBuilder GestureDetector not get Call
import 'package:autoscroll/MoviesListView.dart';
import 'package:autoscroll/data.dart';
import 'package:flutter/material.dart';
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
ScrollController _scrollController1 = ScrollController();
ScrollController _scrollController2 = ScrollController();
ScrollController _scrollController3 = ScrollController();
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
double minScrollExtent1 = _scrollController1.position.minScrollExtent;
double maxScrollExtent1 = _scrollController1.position.maxScrollExtent;
double minScrollExtent2 = _scrollController2.position.minScrollExtent;
double maxScrollExtent2 = _scrollController2.position.maxScrollExtent;
double minScrollExtent3 = _scrollController3.position.minScrollExtent;
double maxScrollExtent3 = _scrollController3.position.maxScrollExtent;
//
animateToMaxMin(maxScrollExtent1, minScrollExtent1, maxScrollExtent1, 1,
_scrollController1);
animateToMaxMin(maxScrollExtent2, minScrollExtent2, maxScrollExtent2, 15,
_scrollController2);
animateToMaxMin(maxScrollExtent3, minScrollExtent3, maxScrollExtent3, 20,
_scrollController3);
});
}
animateToMaxMin(double max, double min, double direction, int seconds,
ScrollController scrollController) {
scrollController
.animateTo(direction,
duration: Duration(seconds: seconds), curve: Curves.linear)
.then((value) {
direction = direction == max ? min : max;
animateToMaxMin(max, min, direction, seconds, scrollController);
});
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: Colors.white,
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
children: [
MoviesListView(
scrollController: _scrollController1,
images: movies1,
),
MoviesListView(
scrollController: _scrollController2,
images: movies2,
),
MoviesListView(
scrollController: _scrollController3,
images: movies3,
),
],
),
Text(
'30 days for free',
style: TextStyle(
fontSize: 35,
fontWeight: FontWeight.bold,
),
),
Material(
elevation: 0,
color: Color(0xfff2c94c),
borderRadius: BorderRadius.circular(20),
child: MaterialButton(
onPressed: () {},
minWidth: 340,
height: 60,
child: Text(
'Continue',
style: TextStyle(
color: Colors.white,
fontSize: 25,
fontWeight: FontWeight.bold,
),
),
),
)
],
),
),
);
}
}
Gesture Detector is not working when i apply animation on List view Builder it didn't let the Gesture Detector work how can I resolve this issue
import 'package:flutter/material.dart';
class MoviesListView extends StatelessWidget {
final ScrollController scrollController;
final List images;
const MoviesListView({Key key, this.scrollController, this.images})
: super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: 120,
child: ListView.builder(
controller: scrollController,
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemCount: images.length,
itemBuilder: (context, index) {
return Container(
margin: EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
),
child: GestureDetector(
onTap: (){
print('Func Called');
},
child: ClipRRect(
borderRadius: BorderRadius.circular(25),
child: Image.asset(
'assets/${images[index]}',
width: 150,
fit: BoxFit.cover,
),
),
),
);
}),
);
}
}
Without Animation Gesture detector working perfectly

Try wrapping outer Container with GestureDetector()
return GestureDetector(
onTap: () {
print('Func Called');
},
child: Container(
margin: EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(25),
child: Image.asset(
'assets/${images[index]}',
width: 150,
fit: BoxFit.cover,
),
),
),
);

Related

Hero widget transition conflict with other animations

I'm trying to achieve the Hero & shake upright animation result attached gif here.
This is the result I got so far.
Seems like the Hero widget conflicts with the animation I've applied. It seems to be working for the 200 & 300 card. But when 100 is tapped, it seems to be working differently. Attached below are the code for the demo above.
Tried using WidgetsBinding.instance.addPostFrameCallback and SchedulerBinding.instance.addPersistentFrameCallback.
Is there any way to get the expected result instead of using the code I've used?
dummy_data.dart
class _DummyData {
final IconData icons;
final Color colors;
final String backText;
const _DummyData(this.icons, this.colors, this.backText);
}
const List<_DummyData> _datas = [
_DummyData(Icons.abc, Colors.blue, '100'),
_DummyData(Icons.alarm, Colors.red, '200'),
_DummyData(Icons.shop, Colors.green, '300'),
];
playground_list.dart
class PlayGroundList extends StatelessWidget {
const PlayGroundList({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: CustomScrollView(
slivers: [
const SliverToBoxAdapter(child: SizedBox(height: 50)),
SliverList(
delegate: SliverChildBuilderDelegate(
childCount: _datas.length,
(context, index) => PlayGroundCardWidget(dummy: _datas[index]),
),
),
],
),
);
}
}
playground_card_widget.dart
class PlayGroundCardWidget extends StatelessWidget {
final _DummyData dummy;
const PlayGroundCardWidget({super.key, required this.dummy});
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 5.w, vertical: 1.5.h),
height: 350,
child: GestureDetector(
onTap: () => Navigator.push(
context,
PageRouteBuilder(
transitionsBuilder: (context, animation, _, child) =>
FadeTransition(
opacity: Tween(
begin: 0.0,
end: 1.0,
).chain(CurveTween(curve: Curves.ease)).animate(animation),
child: child,
),
pageBuilder: (context, _, __) => PlayGroundDetail(dummy: dummy),
),
),
child: Stack(
alignment: Alignment.center,
children: [
Positioned.fill(
child: Hero(
tag: dummy.colors.value,
child: Material(color: dummy.colors),
),
),
Align(
alignment: Alignment.topCenter,
child: Hero(
tag: dummy.backText,
child: Material(
color: Colors.transparent,
child: Text(
dummy.backText,
textAlign: TextAlign.center,
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 140.0,
),
),
),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: 3.h),
Expanded(
flex: 12,
child: Hero(
tag: dummy.icons,
child: Icon(dummy.icons, size: 60.0),
),
),
],
),
],
),
),
);
}
}
playground_detail.dart
const _shakeDuration = Duration(milliseconds: 900);
class PlayGroundDetail extends StatefulWidget {
final _DummyData dummy;
const PlayGroundDetail({super.key, required this.dummy});
#override
State<PlayGroundDetail> createState() => _PlayGroundDetailState();
}
class _PlayGroundDetailState extends State<PlayGroundDetail>
with TickerProviderStateMixin {
late final PageController _pageController;
#override
void initState() {
super.initState();
_pageController = PageController();
}
#override
void dispose() {
super.dispose();
_pageController.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
CustomScrollView(
slivers: [
const SliverToBoxAdapter(child: SizedBox(height: 50)),
SliverToBoxAdapter(
child: SizedBox(
height: 350,
child: Stack(
children: [
Positioned.fill(
child: Hero(
tag: widget.dummy.colors.value,
child: Material(color: widget.dummy.colors),
),
),
Align(
alignment: Alignment.topCenter,
child: Hero(
tag: widget.dummy.backText,
child: Material(
color: Colors.transparent,
child: ShakeTransitionWidget(
axis: Axis.vertical,
duration: _shakeDuration,
offset: 30,
child: Text(
widget.dummy.backText,
textAlign: TextAlign.center,
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
fontSize: 140.0,
),
),
),
),
),
),
Center(
child: Hero(
tag: widget.dummy.icons,
child: ShakeTransitionWidget(
axis: Axis.vertical,
offset: 5,
duration: _shakeDuration,
child: Icon(widget.dummy.icons, size: 60.0),
),
),
),
],
),
),
),
],
),
],
),
);
}
}
shake_transition_widget.dart
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
class ShakeTransitionWidget extends StatefulWidget {
final Widget child;
final Duration duration;
final double offset;
final Axis axis;
const ShakeTransitionWidget({
super.key,
required this.child,
required this.duration,
required this.offset,
required this.axis,
});
#override
State<ShakeTransitionWidget> createState() => _ShakeTransitionWidgetState();
}
class _ShakeTransitionWidgetState extends State<ShakeTransitionWidget>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation _animation;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: widget.duration,
);
_animation = Tween(
begin: 1.0,
end: 0.0,
).chain(CurveTween(curve: Curves.elasticOut)).animate(_controller);
// Tried this.
// WidgetsBinding.instance.addPostFrameCallback((_) => _controller.forward());
if (SchedulerBinding.instance.schedulerPhase ==
SchedulerPhase.persistentCallbacks) {
_controller.forward();
}
SchedulerBinding.instance.addPersistentFrameCallback((timeStamp) {});
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (ctx, _) => Transform.translate(
offset: widget.axis == Axis.horizontal
? Offset(_animation.value * widget.offset, 0.0)
: Offset(0.0, _animation.value * widget.offset),
child: widget.child,
),
);
}
}
Wrap your Hero widget with ShakeTransitionWidget instead of the other way around:
// ... Hero is the child, not the parent
ShakeTransitionWidget(
axis: Axis.vertical,
duration: _shakeDuration,
offset: 30,
child: Hero(
tag: widget.dummy.backText,
// ...
Now inside the initState() of _ShakeTransitionWidgetState simply call _controller.forward() without any SchedulerBinding or WidgetsBinding.
As a rule of thumb, usually the Hero and its children should not change from page to page. Instead add modifications to the parent widgets.
See gif.

There is a problem that is rebuild when I click TextField() in Flutter

My code includes FutureBuilder(), which get data from Firestore, and its child widgets include GridView.builder and TextField widgets etc.
When I click on a TexField(focus), the codes in FutureBuilder are rebuild.
The following is the test code for this.
Can you tell me the cause and solution of this problem?
class TestRoom extends StatefulWidget {
TestRoom({Key? key}) : super(key: key);
#override
State<TestRoom> createState() => _TestRoomState();
}
class _TestRoomState extends State<TestRoom> {
List<RoomModel> _roomModels = [];
TextEditingController _textEditingController = TextEditingController();
bool _isTablet = false;
#override
void dispose() {
_textEditingController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
if (size.width >= 800) {
_isTablet = true;
} else {
_isTablet = false;
}
return Scaffold(
appBar: AppBar(),
body: Padding(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Test",
style: TextStyle(
fontSize: 35,
fontWeight: FontWeight.bold,
),
),
SizedBox(
height: 20,
),
FutureBuilder(
future: _getAllRoom(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
return Expanded(
child: TestList1(
isTablet: _isTablet,
roomModels: _roomModels,
isListStyle1: true,
));
},
),
],
),
),
);
}
// get user's models from firestore
Future _getAllRoom() async {
_roomModels.clear();
_roomModels.addAll(await RoomService().getAllRoomModel("userName"));
}
}
//
//
class TestList1 extends StatefulWidget {
final isTablet;
final List<RoomModel> roomModels;
final bool isListStyle1;
TestList1({
Key? key,
required this.isTablet,
required this.roomModels,
required this.isListStyle1,
}) : super(key: key);
#override
State<TestList1> createState() => _TestList1State();
}
class _TestList1State extends State<TestList1> {
TextEditingController _textEditingController = TextEditingController();
double _paddingSize = 40.0;
#override
void dispose() {
// _textEditingController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return GridView.builder(
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: widget.isTablet ? 6 : 3,
childAspectRatio: 1 / 1.5,
mainAxisSpacing: _paddingSize,
crossAxisSpacing: 10,
),
itemBuilder: (context, index) {
return _buildMyRooms(widget.roomModels[index], index);
},
itemCount: widget.roomModels.length,
);
}
Widget _buildMyRooms(RoomModel roomModel, int index) {
return Column(
children: [
InkWell(
onTap: () {},
child: Container(
width: 120,
height: 168,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Colors.blue,
width: 2.0,
),
borderRadius: const BorderRadius.all(
Radius.circular(10),
),
),
),
),
SizedBox(
height: sm_padding,
),
PopupMenuButton<int>(
color: Colors.grey[100],
itemBuilder: (context) => _fileMenuItemLust(roomModel),
onSelected: _onSeletedFileMenu,
child: Column(
children: [
Container(
width: 130,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Flexible(
child: Text(
roomModel.roomName,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: const TextStyle(
color: Colors.blue,
fontSize: 15,
),
),
),
const Icon(
Icons.arrow_drop_down_outlined,
color: Colors.blue,
),
],
),
),
],
),
)
],
);
}
// 파일 메뉴 아이템
List<PopupMenuEntry<int>> _fileMenuItemLust(RoomModel roomModel) {
_textEditingController.text = roomModel.roomName;
return [
// 파일명
PopupMenuItem(
enabled: false,
// TODO textfield
child: TextField(
controller: _textEditingController,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
maxLines: 1,
decoration: InputDecoration(
border: _textFieldBorder(),
enabledBorder: _textFieldBorder(),
disabledBorder: _textFieldBorder(),
focusedBorder: _textFieldBorder(),
focusColor: Colors.white60,
filled: true,
fillColor: Colors.grey.withOpacity(0.3),
isDense: true, // padding 조절을 위해 추가
contentPadding: EdgeInsets.all(sm_padding),
)),
),
const PopupMenuDivider(),
PopupMenuItem(
value: 0,
child: Row(
children: [
const Icon(
Icons.copy,
),
SizedBox(
width: sm_padding,
),
const Text(
"Menu Item1",
),
],
),
),
];
}
void _onSeletedFileMenu(value) {}
OutlineInputBorder _textFieldBorder() {
return OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
borderSide: BorderSide(
color: Colors.grey.withOpacity(0.3),
width: 1,
),
);
}
}
// ======================
I modified the code based on Yeasin Sheikh's answer.
The existing problem has been solved, but a new problem has arisen.
I'm going to update the Firestore using onSubmitted() of TextField and then update my list using stream builder and future builder.
This is a modified part of the code above to solve an existing problem
class _TestRoomState extends State<TestRoom> {
late final myFuture = _getAllRoom(); // new
FutureBuilder(
future: myFuture, //_getAllRoom(), // change
The problem of constantly rebuilding the text field according to the focus has disappeared, but there is a problem that getAllRoom() is called only at the first time and cannot be called afterwards, so the new room list cannot be updated.
Here future: _getAllRoom() calls the api on every state changes.
Create a state variable for future
late final myFuture = _getAllRoom();
#override
Widget build(BuildContext context) {
And use
FutureBuilder(
future:myFuture ,
You can check Fixing a common FutureBuilder and StreamBuilder problem

Streambuilder did not update Flutter

STREAMBUILDER
Did not update.
I already tried to work with initState.
On another page the data changes and when you go back to this HOMEPAGE the Streambuilder did not update.
I would be really happy, when you can give me an advice.
I think thats right to use a stateful widget isn't it.
........................................................................................................................................................................skldladlkjsdlkjslkdjsdklsdjlkasjdlajdljasldjjlkadjaskdjlkasjdlkjasdjlsajdlaskjdasdlkasjkdasjkdlkasjdskajdlsajdlsajdlkasjlkJLADJLKASJDJalksjjfaskldjfkdfjdslkfjlksdafjlködsfjdjflkdsjfjljfldskjfldskflksfljsdfj
class Homepage extends StatefulWidget {
#override
HomepageState createState() => _HomepageState();
}
class _HomepageState extends State<Homepage> {
UserLevelStandGrossKlein lus = UserLevelStandGrossKlein();
SharedPreferences prefs;
UserLevelStand usl = UserLevelStand();
String name = 'Tom';
#override
void initState() {
super.initState();
}
#override
void dispose() {
lus.startWork();
usl.startWork();
super.dispose();
}
#override
Widget build(BuildContext context) {
setState(() {
lus.startWork();
usl.startWork();
});
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Column(
children: [
Row(children: [
Padding(
padding: const EdgeInsets.fromLTRB(30, 60, 0, 0),
child: RichText(
text: TextSpan(
style: TextStyle(
fontSize: 25,
color: Colors.black
),
children: <TextSpan>[
TextSpan(text: 'Guten Tag,', style: TextStyle(fontWeight: FontWeight.normal)),
TextSpan(text:'\n'+ name, style: TextStyle(fontWeight: FontWeight.bold)),
],
),
), ),
Spacer(),
GestureDetector(
onTap: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => Profil()));
},
child: Padding(
padding: const EdgeInsets.fromLTRB(0, 60, 40, 0),
child: Image.asset("assets/pic.jpg",
width: 80,),
),
)
]
),
StreamBuilder(
initialData:User(1, 'Lädt'),
stream: usl.stream(),
builder: (BuildContext context, AsyncSnapshot snapchot){
User lus = snapchot.data;
String stand2 = lus.userD <=25 ? 'Beginner': 'Fortgeschritten';
return
Padding(
padding: const EdgeInsets.fromLTRB(30, 0, 0, 0),
child: Stack(
alignment: Alignment.topLeft,
children: [Container(
height: 6,
width: 150,
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.all(Radius.circular(9)),
Container(color: Colors.black,),
AnimatedContainer(
duration: Duration(seconds: 5),
width: lus.userD,
height: 6,
decoration:
BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.all(Radius.circular(9)))
)],
),
);
}

how to add flexible height to showGeneralDialog on Flutter?

I added padding for transparent outside. But fixed height. How to change it?
padding: EdgeInsets.fromLTRB(20, 50, 20, 50),
Is it possible to remove above this line and flexible(center)?
I am expected like this flexible height alert. click here
onPressed: () {
showGeneralDialog(
context: context,
barrierColor: Palette.black.withOpacity(.3),
barrierDismissible: true,
transitionDuration: Duration(milliseconds: 400),
pageBuilder: (_, __, ___) {
return ChangePropertyPage(
propertyModel: propertyModel);
},
);
},
change Property Page
class ChangePropertyPage extends StatelessWidget {
final List<PropertyModel> propertyModel;
const ChangePropertyPage({Key key, this.propertyModel}) : super(key: key);
#override
Widget build(BuildContext context) {
final double width = CustomMediaQuery.width(context);
return Padding(
padding: EdgeInsets.fromLTRB(20, 50, 20, 50),
child: Material(
borderRadius: BorderRadius.all(Radius.circular(10)),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
PropertyListTileWidget(
mainTitle: 'USER\'S Name', subTitle: 'USER\'S Email'),
VerticalSpacing(height: 10),
CustomLine(
height: 1,
width: (width - 40) - 20,
color: Palette.black.withOpacity(.2),
),
Expanded(
child: ListView.builder(
itemCount: propertyModel.length,//now length is 1
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: ()async{
},
child: PropertyListTileWidget(
mainTitle: '${propertyModel[index].propertyName}',
subTitle: '${propertyModel[index].ownerUId}'),
);
}),
)
],
),
),
),
);
}
}
if you are expecting this:
then
full code:
import 'package:flutter/material.dart';
class CustomDialogBox extends StatefulWidget {
#override
_CustomDialogBoxState createState() => _CustomDialogBoxState();
}
class _CustomDialogBoxState extends State<CustomDialogBox> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Custom Dialog Box"),
centerTitle: true,
),
body:Center(
child:FlatButton(
color: Colors.blue,
onPressed: (){
showDialog(
context: (context),
child: ShowCustomDialogBox()
);
},
child: Text("Show Dialog")
)
) ,
);
}
}
class ShowCustomDialogBox extends StatefulWidget {
#override
State<StatefulWidget> createState() => ShowCustomDialogBoxState();
}
class ShowCustomDialogBoxState extends State<ShowCustomDialogBox>with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> scaleAnimation;
#override
void initState() {
super.initState();
controller = AnimationController(vsync: this, duration: Duration(milliseconds: 450));
scaleAnimation =CurvedAnimation(parent: controller, curve: Curves.decelerate);
controller.addListener(() {
setState(() {});
});
controller.forward();
}
#override
Widget build(BuildContext context) {
return Center(
child: Material(
color: Colors.transparent,
child: ScaleTransition(
scale: scaleAnimation,
child: Container(
margin: EdgeInsets.all(20.0),
padding: EdgeInsets.all(8.0),
height: MediaQuery.of(context).size.height/2.5, //Change height of dialog box.
width: MediaQuery.of(context).size.width,
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0))),
child: Column(
children: <Widget>[
Expanded(
flex: 4,
child: ListView.builder(
itemCount: 10,
itemBuilder: (context, index){
return Column(
// mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text((index+1).toString(),style: TextStyle(color:Colors.blue,fontSize:40),),
Divider()
],
);
}
)
),
Padding(
padding: const EdgeInsets.only(
left: 20.0, right: 10.0, top: 0.0,),
child: ButtonTheme(
height: 35.0,
minWidth: MediaQuery.of(context).size.width/3.5,
child: RaisedButton(
color: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0)),
splashColor: Colors.white.withAlpha(40),
child: Text(
'Next',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 13.0),
),
onPressed: () {
setState(() {
Navigator.pop(context);
});
},
)
)
),
],
)
),
),
),
);
}
}

Standard Bottom Sheet in Flutter

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),
],
),
);