Is there a way of making beautiful dropdown menu in flutter? - flutter

I want to make beautiful dropdown menu like this. I already tried making it with containers, but it's taking very long time. Is there any package or a way of configuring default dropdownmenu and items?

You could use a Container() Widget with Boxdecoration and as a child the DropdownButton() Widget.
Use DropdownButtonHideUnderline() as a Parent to hide the default Underline.
Sample Code:
Container(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
height: 40.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30.0),
color: Colors.yellow,
),
child: DropdownButtonHideUnderline(
child: DropdownButton() // your Dropdown Widget here
),
);

try this:
import 'package:flutter/material.dart';
void main() => runApp(ExampleApp());
class ExampleApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: PopMenu(),
);
}
}
class PopMenu extends StatefulWidget {
#override
_PopMenuState createState() => _PopMenuState();
}
class _PopMenuState extends State<PopMenu> {
List<String> _menuList = ['menu 1', 'menu 2', 'menu 3'];
GlobalKey _key = LabeledGlobalKey("button_icon");
OverlayEntry _overlayEntry;
Offset _buttonPosition;
bool _isMenuOpen = false;
void _findButton() {
RenderBox renderBox = _key.currentContext.findRenderObject();
_buttonPosition = renderBox.localToGlobal(Offset.zero);
}
void _openMenu() {
_findButton();
_overlayEntry = _overlayEntryBuilder();
Overlay.of(context).insert(_overlayEntry);
_isMenuOpen = !_isMenuOpen;
}
void _closeMenu() {
_overlayEntry.remove();
_isMenuOpen = !_isMenuOpen;
}
OverlayEntry _overlayEntryBuilder() {
return OverlayEntry(
builder: (context) {
return Positioned(
top: _buttonPosition.dy + 70,
left: _buttonPosition.dx,
width: 300,
child: _popMenu(),
);
},
);
}
Widget _popMenu() {
return Material(
child: Container(
width: 300,
height: 300,
decoration: BoxDecoration(
color: Color(0xFFF67C0B9),
borderRadius: BorderRadius.circular(4),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: List.generate(
_menuList.length,
(index) {
return GestureDetector(
onTap: () {},
child: Container(
alignment: Alignment.center,
width: 300,
height: 100,
child: Text(_menuList[index]),
),
);
},
),
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
key: _key,
width: 300,
height: 50,
decoration: BoxDecoration(
color: Color(0xFFF5C6373),
borderRadius: BorderRadius.circular(25),
),
child: Row(
children: [
Expanded(
child: Center(child: Text('menu 1')),
),
IconButton(
icon: Icon(Icons.arrow_downward),
color: Colors.white,
onPressed: () {
_isMenuOpen ? _closeMenu() : _openMenu();
},
),
],
),
),
),
);
}
}

Related

How can I make separate color and button stateless and stateful classes for this code in flutter?

I would like to make an extra file 'extra.dart' where I would like to add my top container and the four TextButtonWidgets.
1 ColorWidget, 4 TextButtonWidgets with each one having colorwidget as their child.
I would name the top container widget ColorWidget, and reuse that widget in the text button widgets as their child.
!! I need to set color as a required named parameter in ColorWidget(), but there it always goes wrong for me.
So in extra.dart should be all widgets, and in main.dart just the basic layout where I call all widgets from extra.dart.
Don't worry about the switchlisttiles. If you'd like to add them, they mean create border, and make container bigger in size, all applied to top container.
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
import 'extra.dart';
// ONLY USE STATEFUL WIDGETS IF CONTENT ON SCREEN HAS TO CHANGE DYNAMICALLY!!
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final String title = 'Test';
final String name = 'John Doe';
#override
//
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Home(),
),
);
}
}
// var colorChange = true;
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
Color color = Colors.amber;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Container color")),
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Container(
decoration: BoxDecoration(
color: color,
borderRadius: const BorderRadius.all(Radius.circular(10))),
height: 100,
width: 100,
),
),
const SizedBox(
height: 20,
),
Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: TextButton(
onPressed: () {
setState(() {
color = Colors.amber;
});
},
child: Container(
decoration: const BoxDecoration(
color: Colors.amber,
borderRadius:
BorderRadius.all(Radius.circular(10))),
height: 100,
width: 100,
),
),
),
Center(
child: TextButton(
onPressed: () {
setState(() {
color = Colors.blue;
});
},
child: Container(
decoration: const BoxDecoration(
color: Colors.blue,
borderRadius:
BorderRadius.all(Radius.circular(10))),
height: 100,
width: 100,
),
),
)
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: TextButton(
onPressed: () {
setState(() {
color = Colors.red;
});
},
child: Container(
decoration: const BoxDecoration(
color: Colors.red,
borderRadius:
BorderRadius.all(Radius.circular(10))),
height: 100,
width: 100,
),
),
),
Center(
child: TextButton(
onPressed: () {
setState(() {
color = Colors.green;
});
},
child: Container(
decoration: const BoxDecoration(
color: Colors.green,
borderRadius:
BorderRadius.all(Radius.circular(10))),
height: 100,
width: 100,
),
),
)
],
),
)
],
)
],
),
);
}
}

How to create this type of Bottom Navigation?

I am trying to make a Bottom Navigation Bar that looks exactly like this. Since I'm just a beginner to learn flutter, I am having a lot of problems one of which is not able to find the icons so I decided to use other similarly available icons. Now I just confused with my own code.
This is what I want:
this is how my Bottom Navigation Bar looks:
This is my code:
Scaffold(bottomNavigationBar:
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(
height: 50,
width: MediaQuery.of(context).size.width / 5,
decoration: BoxDecoration(color:Color(0xfffed307)),
child: Column(
children: [
Icon(Icons.store_mall_directory_outlined),
Text('My Page')
],
),
),
Container(
height: 50,
width: MediaQuery.of(context).size.width / 5,
decoration: BoxDecoration(color: Color(0xfffed307)),
child: Column(
children: [Icon(Icons.apps), Text('Election')],
),
),
Container(
height: 50,
width: MediaQuery.of(context).size.width / 5,
decoration: BoxDecoration(color: Color(0xfffed307)),
child: Image.asset('images/scan_icon.png'),
),
Container(
height: 50,
width: MediaQuery.of(context).size.width / 5,
decoration: BoxDecoration(color: Color(0xfffed307)),
child: Column(
children: [Icon(Icons.apps), Text('Add Election')],
),
),
Expanded(
child: Container(
height: 50,
width: MediaQuery.of(context).size.width / 5,
decoration: BoxDecoration(color: Color(0xfffed307)),
child: Column(
children: [Icon(Icons.groups_outlined), Text('Customer')],
),
),
),
],
)
,);
You can use floatingActionButton for the scan icon and use floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
With Flutter, to make this kind of UI is easy peasy)) Use Positioned Widget inside Stack Widget if you really wanna make this UI using bottomNavigationBar property of Scaffold Widget.
Result UI
Copy and paste the code below to see the effect:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyScreen(),
);
}
}
class MyScreen extends StatefulWidget {
const MyScreen({Key? key}) : super(key: key);
#override
_MyScreenState createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> {
late List<Widget> _screens;
int currentIndex = 0;
#override
void initState() {
super.initState();
_screens = [
TestScreen(title: '1st Screen'),
TestScreen(title: '2nd Screen'),
TestScreen(title: '3rd Screen'),
TestScreen(title: '4th Screen'),
TestScreen(title: '5th Screen'),
];
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: currentIndex,
children: _screens,
),
bottomNavigationBar: BottomBar(
selectedIndex: currentIndex,
children: [
BottomBarItem(icon: Icons.home),
BottomBarItem(icon: Icons.search),
BottomBarItem(icon: Icons.favorite),
BottomBarItem(icon: Icons.person),
],
onMainPressed: () {
setState(() {
currentIndex = 4;
});
},
onPressed: (index) {
setState(() {
currentIndex = index;
});
},
),
);
}
}
class BottomBarItem {
BottomBarItem({required this.icon});
IconData icon;
}
class BottomBar extends StatelessWidget {
final List<BottomBarItem> children;
final Function? onPressed;
final Function? onMainPressed;
final selectedIndex;
BottomBar({
this.children = const [],
this.onPressed,
this.onMainPressed,
this.selectedIndex,
});
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Container(
color: Colors.grey[100],
child: SafeArea(
bottom: true,
child: Container(
height: 60,
decoration: BoxDecoration(
color: Colors.grey[100],
boxShadow: [
BoxShadow(
color: Colors.grey,
blurRadius: 8.0,
offset: Offset(
0.0, // horizontal, move right 10
-6.0, // vertical, move down 10
),
),
],
),
child: Stack(
clipBehavior: Clip.none,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: children.map<Widget>(
(item) {
int index = children.indexOf(item);
bool isSelected = selectedIndex == index;
return Expanded(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
onPressed!(index);
},
child: Padding(
padding: EdgeInsets.zero,
child: Icon(
item.icon,
size: isSelected ? 35 : 30,
color: isSelected ? Colors.blue : Colors.grey,
),
),
),
),
);
},
).toList()
..insert(2, SizedBox(width: 80)),
),
Positioned(
top: -14,
width: size.width,
child: Center(
child: Container(
height: 60,
width: 60,
child: ClipOval(
child: Material(
color: selectedIndex == 4 ? Colors.blue : Colors.grey,
child: InkWell(
onTap: () {
onMainPressed!();
},
child: Center(
child: Icon(
Icons.adb,
size: 27,
color: Colors.white,
),
),
),
),
),
decoration: BoxDecoration(
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.grey,
blurRadius: 6.0,
),
],
),
),
),
),
],
),
),
),
);
}
}
class TestScreen extends StatelessWidget {
final String title;
const TestScreen({required this.title, Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: Text(title),
)),
);
}
}

Is there a way to show a slider under the number stepper widget?

Is there a way to show a slider under the number stepper widget?
Depending upon the activeStep in the number stepper the slider should be placed under the activeStep.
Any suggestions?
I'm attaching an image of the desired result.
#override
Widget build(BuildContext context) {
screenWidth = MediaQuery.of(context).size.width;
questionsProgressBarWidth = screenWidth - 80.0;
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(
height: 20,
),
TutorialTestTopBar(screenWidth: screenWidth),
SizedBox(
height: 10,
),
Padding(
padding: const EdgeInsets.all(10.0),
child: quizQuestionWidget,
),
Spacer(),
],
),
),
);
}
Widget get quizQuestionWidget {
if (quizQuestion == null) {
return const Center(child: CircularProgressIndicator());
}
questions = quizQuestion.questions;
upperBound = questions.length;
for (int i = 1; i <= questions.length; i++) {
numbers.add(i);
}
return SizedBox(
height: MediaQuery.of(context).size.height * 0.85,
child: Column(
children: [
NumberStepper(
stepColor: Colors.white,
activeStepColor: Colors.green,
// activeStepBorderColor: Colors.green,
stepRadius: 15,
direction: Axis.horizontal,
lineColor: Colors.white,
numbers: numbers,
activeStep: activeStep,
onStepReached: (index) {
setState(() {
activeStep = index;
});
},
),
//NEED THE SLIDER HERE
Expanded(
child: PageView.builder(
controller: pageController,
onPageChanged: (value) {
setState(() {
pageChanged = value;
});
},
itemBuilder: (context, index) {
return buildContent(questions[index], index, upperBound);
},
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
previousButton(),
nextButton(),
],
),
],
),
);
}
You can define a custom border for each item, then update the Color property based on the current question being answered. Full example code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
home: SomeScreen(),
);
}
}
class SomeScreen extends StatefulWidget {
#override
_SomeScreenState createState() => _SomeScreenState();
}
class _SomeScreenState extends State<SomeScreen> {
int _currentQuestion = 0;
List<int> _answered = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
padding: EdgeInsets.all(20),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List<Widget>.generate(
5,
(index) => _buildItem(index),
),
),
Container(
child: FlatButton(
color: Colors.orange,
onPressed: () {
setState(() {
if (!_answered.contains(_currentQuestion))
_answered.add(_currentQuestion);
if (_currentQuestion < 5) {
_currentQuestion += 1;
}
});
},
child: Text('Answer'),
),
)
],
),
),
),
);
}
Column _buildItem(int index) {
return Column(
children: [
Container(
child: CircleAvatar(
backgroundColor:
_answered.contains(index) ? Colors.green : Colors.transparent,
child: Text(
'${index + 1}',
style: TextStyle(color: Colors.black),
)),
),
Container(
height: 10,
width: 40,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: _currentQuestion == index
? Colors.orange
: Colors.transparent),
)
],
);
}
}
Result:
try this:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
List<int> _steps = [1, 2, 3, 4, 5];
#override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: _numberStep(),
),
SizedBox(height: 10),
Stack(children: [
Container(
margin: EdgeInsets.symmetric(horizontal: 50),
width: MediaQuery.of(context).size.width,
height: 20,
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: _sliderStep(),
),
]),
],
),
);
}
List<Widget> _numberStep() {
List<Widget> _stepList = [];
_steps.forEach((step) {
_stepList.add(
Container(
alignment: Alignment.center,
width: 20,
height: 20,
decoration: BoxDecoration(
color: step < 3? Colors.green: Colors.transparent,
shape: BoxShape.circle,
),
child: Text(step.toString()),
),
);
});
return _stepList;
}
List<Widget> _sliderStep() {
List<Widget> _sliderList = [];
_steps.forEach((step) {
_sliderList.add(
Container(
width: 40,
height: 20,
decoration: BoxDecoration(
color: step == 3? Colors.orange: Colors.transparent,
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
),
),
);
});
return _sliderList;
}
}

FLUTTER : How to make container size of children

By definition, a container with children grows enough to show them. In this example that I am developing, I am not able to make the container fit the size of the children, I have to hardcode the Weight and Height, both, otherwise the container disappear (is the one with a red background, I put the whole code so you can copy-paste but it is only that one that I can not control the behaviour).
import 'package:flutter/material.dart';
import 'package:littlebusiness/logic/Category.dart';
import 'package:hive/hive.dart';
class FormCategoryPage extends StatefulWidget {
#override
_FormCategoryPageState createState() => _FormCategoryPageState();
}
class _FormCategoryPageState extends State<FormCategoryPage> {
final _formKey = GlobalKey<FormState>();
List<RadioModel> sampleData = new List<RadioModel>();
#override
void initState() {
// TODO: implement initState
super.initState();
sampleData.add(new RadioModel(true, 'A', 0xffe6194B));
sampleData.add(new RadioModel(false, 'B', 0xfff58231));
sampleData.add(new RadioModel(false, 'C', 0xffffe119));
sampleData.add(new RadioModel(false, 'D', 0xffbfef45));
sampleData.add(new RadioModel(true, 'A', 0xffe6194B));
sampleData.add(new RadioModel(false, 'B', 0xfff58231));
}
String _name;
Color _color;
String _selectedValue;
void addCategory(Category cat) {
Hive.box('categories').add(cat);
}
void getColor(String value) {
_selectedValue = value;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('PERFORMANCE'),
),
body: Center(
child: Form(
key: _formKey,
child: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
TextFormField(
decoration: const InputDecoration(
// hintText: 'Enter your email',
labelText: 'Name',
),
onSaved: (value) => _name = value,
validator: (value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
height: 100,
width: 320,
color: Colors.red,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: sampleData.length,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
setState(() {
sampleData.forEach(
(element) => element.isSelected = false);
sampleData[index].isSelected = true;
});
},
child: RadioItem(sampleData[index]),
);
},
),
),
],
),
RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
side: BorderSide(color: Colors.red)),
child: Text('Add New Contact'),
color: Colors.teal,
textColor: Colors.white,
onPressed: () {
_formKey.currentState.save();
// final newContact = Contact(_name, int.parse(_age));
// addContact(newContact);
},
),
],
),
),
),
),
);
}
}
class RadioItem extends StatelessWidget {
final RadioModel _item;
RadioItem(this._item);
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(15.0),
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
height: 35.0,
width: 35.0,
alignment: Alignment.center,
child: Container(
height: 25.0,
width: 25.0,
decoration: BoxDecoration(
color: Color(_item.colorCode),
borderRadius:
const BorderRadius.all(const Radius.circular(15)),
)),
decoration: BoxDecoration(
color: Colors.transparent,
border: Border.all(
width: 3.0,
color: _item.isSelected
? Color(_item.colorCode)
: Colors.transparent),
borderRadius: const BorderRadius.all(const Radius.circular(25)),
),
),
Container(margin: EdgeInsets.only(left: 20.0))
],
),
);
}
}
class RadioModel {
bool isSelected;
final String buttonText;
final int colorCode;
RadioModel(this.isSelected, this.buttonText, this.colorCode);
}
This is the actuar result:
Anyone knows why it is happening that? I am lost, and giving a with of double.infinity does not work...
Thanks!
instead of using fixed values on wisth and height , you can use relative values to device by using
MediaQuery.of(context).size.width,
MediaQuery.of(context).size.height
you can also use them like
MediaQuery.of(context).size.width * 0.5
which means 50% of the device screen
hope it will help
You have to specified both width and height because your Listview is a child of a Column and a Row
You can replace your Listview by a Row of RadioItem in a SingleChildScrollView
Container(
color: Colors.red,
child: Builder(
builder: (context) {
var childs = <Widget>[];
for (var item in sampleData) {
childs.add(RadioItem(item));
}
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: childs,
),
);
},
),
),

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