Related
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,
),
),
),
);
i am working with PageView but if i swipe to change it will change the page But if i put onPageChanged to setState for me to get to current index it will not change the page.
here is my code
int _indicatorsPages = 0;
final PageController controller =
PageController(initialPage: 0);
change(int page) {
setState(() {
_indicatorsPages = page;
}); }
code from build
Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
leading: Row(
children: [
SizedBox(
width: 10,
),
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => SettingsFMC(),
),
);
},
child: Container(
height: 40,
width: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.5),
spreadRadius: 1, blurRadius: 2,
offset: Offset(0, 3), // i change position of shadow
),
],
),
child: Center(
child: Icon(
FontAwesomeIcons.slidersH,
size: 20,
color: Colors.black,
)),
),
),
],
),
elevation: 0,
backgroundColor: Colors.transparent,
actions: [
GestureDetector(
onTap: () async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Actitvities(),
),
);
final doc = await firestore
.collection('feeds')
.doc(auth.currentUser.uid)
.collection('feedsItems')
.get();
if (doc.docs.isNotEmpty) {
firestore
.collection('feeds')
.doc(auth.currentUser.uid)
.collection('feedsItems')
.get()
.then((value) {
value.docs.forEach((doc) {
doc.reference.update({'seen': true});
});
});
}
},
child: Container(
height: 40,
width: 40,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.5),
spreadRadius: 1, blurRadius: 2,
offset: Offset(0, 3), // i change position of shadow
),
],
),
child: StreamBuilder(
stream: firestore
.collection('feeds')
.doc(auth.currentUser.uid)
.collection('feedsItems')
.where('seen', isEqualTo: false)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: Icon(
Icons.notifications_none_outlined,
size: 20,
color: Colors.black,
));
}
if (snapshot.data.docs.isEmpty) {
return Center(
child: Icon(
Icons.notifications_none_outlined,
size: 20,
color: Colors.black,
));
}
return Badge(
animationType: BadgeAnimationType.scale,
badgeContent: Text('${snapshot.data.docs.length}'),
position: BadgePosition.topStart(),
showBadge: true,
child: Center(
child: Icon(
Icons.notifications_none_outlined,
size: 20,
color: Colors.black,
)),
);
}),
),
),
SizedBox(
width: 10,
),
],
),
extendBodyBehindAppBar: true,
body: userProfileLoading
? Center(
child: CircularProgressIndicator(),
)
: Stack(
children: [
Container(
height: MediaQuery.of(context).size.height / 1.6,
child: StreamBuilder(
stream: firestore
.collection('Image')
.doc(widget.viewId)
.collection('Photos')
.orderBy('timestap', descending: false)
.snapshots(),
builder: (context, AsyncSnapshot snapshot) {
int idx = 0;
List<Widget> list = [];
if (snapshot.connectionState ==
ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
if (!snapshot.hasData) {
return Center(
child: Text("No image Found, Add images"),
);
} else {
if (snapshot.hasError) {
return Center(child: Text("fetch error"));
} else {
for (int i = 0;
i < snapshot.data.docs.length;
i++) {
// print('the lent of the document is $idx');
list.add(
FullScreenWidget(
child: Hero(
tag: "customTag",
child: Image.network(
snapshot.data.docs[idx]
.data()['picture'],
fit: BoxFit.cover,
),
),
),
);
idx++;
}
return Stack(
children: [
PageView(
key: _key,
scrollDirection: Axis.horizontal,
controller: controller,
onPageChanged: change,
// onImageChange: (pre, current) {
// print('this current : $current');
// setState(() {
// indicatorsPages = current;
// });
// },
// boxFit: BoxFit.cover,
// autoplay: false,
// animationCurve: Curves.fastOutSlowIn,
// animationDuration:
// Duration(milliseconds: 1000),
// dotIncreasedColor: Colors.orange,
// dotBgColor: Colors.transparent,
// dotPosition: DotPosition.bottomCenter,
// dotVerticalPadding:
// MediaQuery.of(context).size.height / 15,
//showIndicator: false,
// indicatorBgPadding: 7.0,
children: list,
),
Positioned(
right:
MediaQuery.of(context).size.width / 2,
bottom: 75,
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(20),
color: Colors.white),
child: indicat.DotsIndicator(
dotsCount: list.length,
position: _indicatorsPages.toDouble(),
decorator: DotsDecorator(
color:
Colors.grey, // Inactive color
activeColor: Colors.black,
),
)),
)
],
);
}
}
}),
),
You are wrong. When you use pageView, it will call onPageChnaged function after page changed. If you want to change page programmatically, you should use pageView.animateToPage() function.
import 'package:flutter/material.dart';
class OnBoarding extends StatefulWidget {
#override
_OnBoardingState createState() => _OnBoardingState();
}
class _OnBoardingState extends State<OnBoarding> {
PageController controller;
int currentIndex = 0;
#override
void initState() {
controller = PageController();
super.initState();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blueAccent[200],
body: Stack(
children: [
PageView(
onPageChanged: onchahged,
controller: controller,
children: [
Container(
child: Image.asset('assets/fierceninja.png'),
),
Container(
child: Image.asset('assets/ninja.png'),
),
Container(
child: Image.asset('assets/ninjahead.jpg'),
),
],
),
],
),
);
}
onchahged(int index) {
setState(() {
currentIndex = index;
});
}
}
Here's a complete example.
in flutter after showing dialog i want to change height of that, for example make full screen dialog height, my implemented code don't work correctly and after show again i have full screen. how can i change height of the dialog real time?
void main() => runApp(MaterialApp(home: DialogCustomHeight(),));
class DialogCustomHeight extends StatefulWidget {
#override
State<StatefulWidget> createState() => DialogCustomHeightState();
}
class DialogCustomHeightState extends State<DialogCustomHeight> {
bool fullScreenDialog = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
color: Colors.white,
child: Text(
'show dialog'
),
onPressed: () => _showDialog(),
),
),
);
}
_showDialog() {
bool _fromTop = true;
return showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: Duration(milliseconds: 200),
context: context,
pageBuilder: (context, anim1, anim2) {
return Align(
alignment: _fromTop ? Alignment.topCenter : Alignment.bottomCenter,
child: Container(
height: fullScreenDialog ? MediaQuery.of(context).size.height : MediaQuery.of(context).size.height * 0.500,
child: SizedBox.expand(
child: ClipRRect(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(10.0),
bottomLeft: Radius.circular(10.0),
),
child: Container(
color:Colors.green,
child: Center(
child: RaisedButton(
color: Colors.white,
child: Text(
'change height'
),
onPressed: () {
setState(() {
fullScreenDialog = !fullScreenDialog;
});
},
),
),
))),
),
);
},
transitionBuilder: (context, anim1, anim2, child) {
return FadeTransition(
opacity: new CurvedAnimation(parent: anim1, curve: Curves.easeOut),
child: SlideTransition(
position: Tween(begin: Offset(0, _fromTop ? -0.1 : 0.1), end: Offset(0, 0)).animate(anim1),
child: child,
),
);
},
);
}
}
Output (From top):
Output (From bottom)
void main() => runApp(MaterialApp(home: DialogCustomHeight()));
class DialogCustomHeight extends StatefulWidget {
#override
State<StatefulWidget> createState() => DialogCustomHeightState();
}
class DialogCustomHeightState extends State<DialogCustomHeight> {
bool fullScreenDialog = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
color: Colors.white,
child: Text('show dialog'),
onPressed: () => _showDialog(),
),
),
);
}
_showDialog() {
bool _fromTop = false;
return showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: Duration(milliseconds: 200),
context: context,
pageBuilder: (context, anim1, anim2) {
return MyDialog(fromTop: _fromTop, fullScreen: fullScreenDialog);
},
transitionBuilder: (context, anim1, anim2, child) {
return FadeTransition(
opacity: new CurvedAnimation(parent: anim1, curve: Curves.easeOut),
child: SlideTransition(
position: Tween(begin: Offset(0, _fromTop ? -0.1 : 0.1), end: Offset(0, 0)).animate(anim1),
child: child,
),
);
},
);
}
}
class MyDialog extends StatefulWidget {
final bool fromTop;
final bool fullScreen;
const MyDialog({Key key, this.fromTop, this.fullScreen}) : super(key: key);
#override
_MyDialogState createState() => _MyDialogState();
}
class _MyDialogState extends State<MyDialog> {
bool _fromTop, _fullScreen;
#override
void initState() {
super.initState();
_fromTop = widget.fromTop;
_fullScreen = widget.fullScreen;
}
#override
Widget build(BuildContext context) {
return Align(
alignment: _fromTop ? Alignment.topCenter : Alignment.bottomCenter,
child: Container(
height: _fullScreen ? MediaQuery.of(context).size.height : MediaQuery.of(context).size.height * 0.500,
child: SizedBox.expand(
child: ClipRRect(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(10.0),
bottomLeft: Radius.circular(10.0),
),
child: Container(
color: Colors.green,
child: Center(
child: RaisedButton(
color: Colors.white,
child: Text('change height'),
onPressed: () {
setState(() {
_fullScreen = !_fullScreen;
});
},
),
),
),
),
),
),
);
}
}
Here we solved changing the dialog height, now how can I animate this changing height of dialog.
Full source code:
void main() => runApp(MaterialApp(home: DialogCustomHeight()));
class DialogCustomHeight extends StatefulWidget {
#override
State<StatefulWidget> createState() => DialogCustomHeightState();
}
class DialogCustomHeightState extends State<DialogCustomHeight> {
bool fullScreenDialog = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
color: Colors.white,
child: Text('show dialog'),
onPressed: () => _showDialog(),
),
),
);
}
_showDialog() {
bool _fromTop = false;
return showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: Duration(milliseconds: 200),
context: context,
pageBuilder: (context, anim1, anim2) {
return MyDialog(fromTop: _fromTop, fullScreen: fullScreenDialog);
},
transitionBuilder: (context, anim1, anim2, child) {
return FadeTransition(
opacity: new CurvedAnimation(parent: anim1, curve: Curves.easeOut),
child: SlideTransition(
position: Tween(begin: Offset(0, _fromTop ? -0.1 : 0.1), end: Offset(0, 0)).animate(anim1),
child: child,
),
);
},
);
}
}
class MyDialog extends StatefulWidget {
final bool fromTop;
final bool fullScreen;
const MyDialog({Key key, this.fromTop, this.fullScreen}) : super(key: key);
#override
_MyDialogState createState() => _MyDialogState();
}
class _MyDialogState extends State<MyDialog> {
bool _fromTop, _fullScreen;
#override
void initState() {
super.initState();
_fromTop = widget.fromTop;
_fullScreen = widget.fullScreen;
}
#override
Widget build(BuildContext context) {
return Align(
alignment: _fromTop ? Alignment.topCenter : Alignment.bottomCenter,
child: Container(
height: _fullScreen ? MediaQuery.of(context).size.height : MediaQuery.of(context).size.height * 0.500,
child: SizedBox.expand(
child: ClipRRect(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(10.0),
bottomLeft: Radius.circular(10.0),
),
child: Container(
color: Colors.green,
child: Center(
child: RaisedButton(
color: Colors.white,
child: Text('change height'),
onPressed: () {
setState(() {
_fullScreen = !_fullScreen;
});
},
),
),
),
),
),
),
);
}
}
All you need to do is use AnimatedContainer and give it a duration. That's all
#override
Widget build(BuildContext context) {
return Align(
alignment: _fromTop ? Alignment.topCenter : Alignment.bottomCenter,
child: AnimatedContainer(
duration: Duration(seconds: 1),
height: _fullScreen ? MediaQuery.of(context).size.height : MediaQuery.of(context).size.height * 0.5,
child: SizedBox.expand(
child: ClipRRect(
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(20.0),
bottomLeft: Radius.circular(20.0),
),
child: Container(
color: Colors.teal,
child: Center(
child: RaisedButton(
child: Text('change height'),
onPressed: () {
setState(() {
_fullScreen = !_fullScreen;
});
},
),
),
),
),
),
),
);
}
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),
],
),
);