How to add a widget by position on runtime in flutter? - flutter

I am trying to add a button on an image on tap position.
I have managed to get position on tap by using onTapUp's detail parameter.
However, I can't add an icon button when user taps on the image.
Below code shows my sample.
class ArticlesShowcase extends StatelessWidget {
final commentWidgets = List<Widget>();
#override
Widget build(BuildContext context) {
return new GestureDetector(
child: new Center(
child: Image.network(
'https://via.placeholder.com/300x500',
),
),
onTapUp: (detail) {
final snackBar = SnackBar(
content: Text(detail.globalPosition.dx.toString() +
" " +
detail.globalPosition.dy.toString()));
Scaffold.of(context).showSnackBar(snackBar);
new Offset(detail.globalPosition.dx, detail.globalPosition.dy);
var btn = new RaisedButton(
onPressed: () => {},
color: Colors.purple,
child: new Text(
"Book",
style: new TextStyle(color: Colors.white),
),
);
commentWidgets.add(btn);
},
);
}
}
I tried to add the button on the list but no chance.

So , there are a couple of things you are missing.
First you can't update a StatelessWidget state , so you need to use a StatefulWidget.
Second, when using a StatefulWidget , you need to call setState to update the State.
You will also need to use a Stack and Positioned widgets to put the buttons on your specific location.
Your code should end and look like this.
class ArticlesShowcaseState extends State<ArticlesShowcase> {
final commentWidgets = List<Widget>();
void addButton(detail) {
{
final snackBar = SnackBar(
content: Text(
"${detail.globalPosition.dx.toString()} ${detail.globalPosition.dy.toString()}"));
Scaffold.of(context).showSnackBar(snackBar);
var btn = new Positioned(
left: detail.globalPosition.dx,
top: detail.globalPosition.dy,
child: RaisedButton(
onPressed: () => {},
color: Colors.purple,
child: new Text(
"Book",
style: new TextStyle(color: Colors.white),
),
));
setState(() {
commentWidgets.add(btn);
});
}
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
GestureDetector(
child: new Center(
child: Image.network(
'https://via.placeholder.com/300x500',
),
),
onTapUp: (detail) => addButton(detail),
)
] +
commentWidgets,
);
}
}

Related

Flutter: How to replace icon image from the list of pictures to chose from?

For instance: I have a main Icon so when you click on it, it opens a pop-up window with smaller icons/images to select from. So if you select one of the pictures from that pop-up it replaces the main Icon to that specific image.
I have spent hours trying to figure out how to replace icon images but nothing seems to work.
I have created an example (I have used flutter_speed_dial to make expandable buttons but it's not necessary). You can adjust it to your needs:
class _TestState extends State<Test> {
var fabIcon = Icons.expand_less;
var button1Icon = Icons.home;
var button2Icon = Icons.shop;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
floatingActionButton: SpeedDial(
icon: fabIcon,
backgroundColor: Color(0xFF801E48),
visible: true,
curve: Curves.bounceIn,
children: [
// FAB 1
SpeedDialChild(
child: Icon(button1Icon),
backgroundColor: Color(0xFF801E48),
onTap: () {
var temp = fabIcon;
setState(() {
fabIcon = button1Icon;
button1Icon = temp;
});
},
labelStyle: TextStyle(
fontWeight: FontWeight.w500,
color: Colors.white,
fontSize: 16.0),
labelBackgroundColor: Color(0xFF801E48)),
// FAB 2
SpeedDialChild(
child: Icon(button2Icon),
backgroundColor: Color(0xFF801E48),
onTap: () {
var temp = fabIcon;
setState(() {
fabIcon = button2Icon;
button2Icon = temp;
});
},
labelStyle: TextStyle(
fontWeight: FontWeight.w500,
color: Colors.white,
fontSize: 16.0),
labelBackgroundColor: Color(0xFF801E48))
],
),
),
);
}
}
Using showDialog(...) is the solution.
Hope this will help you and others.
you can look at this example:
import 'package:flutter/material.dart';
class IconDialogScreen extends StatefulWidget {
const IconDialogScreen({Key? key}) : super(key: key);
#override
State<IconDialogScreen> createState() => _IconDialogScreenState();
}
class _IconDialogScreenState extends State<IconDialogScreen> {
IconData icon = Icons.abc;
List<IconData> icons = [
Icons.abc,
Icons.person_add,
Icons.person,
Icons.person_remove,
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: TextButton(
onPressed: onIconClicked,
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 20,
children: [Icon(icon, size: 50), const Text("change icon")],
),
),
)
],
),
);
}
void onIconClicked() async {
IconData? _icon = await showDialog<IconData?>(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Select one icon'),
content: Wrap(
spacing: 10,
runSpacing: 10,
children: icons.map<Widget>((e) {
return ElevatedButton(
onPressed: () {
Navigator.of(context).pop(e);
},
child: Icon(e, size: 50));
}).toList(),
),
);
},
);
if (_icon != null) {
setState(() {
icon = _icon;
});
}
}
}

flutter want call parent view upon its child to refresh main view

I have a Stateful widget that has a child buttons and set of stateful containers (visible and invisible).
I want to try here is when i call the specific button, the specific button will refresh all layout and change it on its specifict view by setting visible while the others are not visible.
like this:
button1 = view1;
button2 = view2;
button3 = view3;
if (button1 is pressed){
view1 is visible}
else{
not visible}
upon my code, upon my first view(login button),I've set to go with my main_page like this:
child: MaterialButton(
minWidth: MediaQuery.of(context).size.width,
padding: EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MainPage(change1: true,change2: false,change3: false,)),
);
},
now i was display my main_page view (with a child view that has visibility property).
this is my code on main_page:
class MainPage extends StatefulWidget {
final bool change1 ;
final bool change2;
final bool change3 ;
const MainPage({Key key, this.change1,this.change2,this.change3}) : super(key: key);
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
Container(
constraints: BoxConstraints.expand(
height: 280.0,
),
decoration: BoxDecoration(
image: new DecorationImage(
fit: BoxFit.cover,
colorFilter: new ColorFilter.mode(
Colors.blueAccent, BlendMode.colorBurn),
image: new ExactAssetImage("images/vector.jpg"),
),
),
child: Stack(
children: <Widget>[
Container(
alignment: Alignment.bottomCenter,
child: Row(
children: <Widget>[
Expanded(
child: GestureDetector(
onTap: () {
setState(() {
print("i pressed Official Business");
MainPage(change1: true,change2: false,change3: false);
//TODO: here is my problem, when i call the main_page on its page,
// the value of change1, change2, and chaange3 is not updating
// so that icanot update my view .
});
},
child: Container(
height: 50.0,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Center(
child: Text(
"Official Business",
style: TextStyle(
fontSize: 20.0, color: Colors.white),
),
),
),
),
),
),
Expanded(
child: GestureDetector(
onTap: () {
setState(() {
print("i pressed file an OB");
MainPage(change1: false,change2: true,change3: false);
//TODO: here is my problem, when i call the main_page on its page,
// the value of change1, change2, and chaange3 is not updating
// so that icanot update my view .
});
},
child: Container(
height: 50.0,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Center(
child: Text(
"File an OB",
style: TextStyle(
fontSize: 20.0, color: Colors.white),
),
),
),
),
),
),
],
),
),
],
),
),
//TODO:
new Visibility(
//Called changed and viewOne
visible: widget.change1,
child: OfficialBusiness(),
),
new Visibility(
//Called not changed and viewTwo
visible: widget.change2,
child: FilingOb(),
),
new Visibility(
//Called not changed and viewTwo
visible: widget.change3,
child: ViewOfficialBusiness(),
)
],
),
),
);
}
}
the fillingob/officialbusiness/ViewOfficialBusiness includded was set of stateful layout, i didn't add the code to prevent over views.
sorry, in new on this programming language and i've like to enlighten with these problems i've encountered if my code is possible or not.Also if you need more reference just ping on comment so that i can provide my other codes
bool change1;
bool change2;
bool change3;
#override
initState() {
super.initState();
change1 = widget.change1;
change2 = widget.change2;
change3 = widget.change3;
}
void setChange1() {
setState(() {
change1 = true;
change2 = change3 = false;
});
}
// GestureDetector(onTap: setChange1)
// Visibility(
// visible: change1,
// child: OfficialBusiness(),
// )
Or use enum :
enum MyView {
officialBusiness,
filingOb,
viewOfficialBusiness,
}
MyView current;
// GestureDetector(onTap: () => setState(() { current = MyView.officialBusiness; }))
// Visibility(
// visible: current == MyView.officialBusiness,
// child: OfficialBusiness(),
// )
In Flutter, the standard way to call functions on a parent widget when something occurs in one of its children would be to pass a function from the parent widget to the child, so that the function gets triggered from the child, with data that only the parent widget knows.
In your case, in your first view, you could define a new method like this:
void onChildPressed() {
setState(() {
// Here you change the boolean change1, change2, whatever you want
});
}
And then, in the child view, you have to define a function parameter, such that your child can receive the function as a parameter, and trigger it from wherever you want in your child widget.
class MainPage extends StatefulWidget {
final bool change1 ;
final bool change2;
final bool change3 ;
final void Function() onPressed;
const MainPage({Key key, this.change1,this.change2,this.change3, this.onPressed}) : super(key: key);
#override
_MainPageState createState() => _MainPageState();
}
This way, you can instantiate the MainPage from your first view like this:
MainPage(change1: false,change2: true,change3: false, onPressed: onChildPressed);
And, finally, you can call the onPressed function in your MainPage, so that your child view updates the parent view the way you want.

Adding new ListTile item on click

I have created method and widget, right now my code is working fine because data is static, i want to add new item in ListTile when i press Add Button.
method:
Card listSectionMethod(String title, String subtitle, IconData icon) {
return new Card(
child: ListTile(
title: Text(
title,
style: TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(subtitle),
trailing: Icon(
icon,
color: Colors.blue,
),
),
);
}
Widget:
Widget listSection = Container(
margin: EdgeInsets.only(top: 210),
child: ListView(
children: [
Column(
children: [
listSectionMethod("TITLE 1", "hello i am subtitle one",Icons.forward),
listSectionMethod("TITLE 2", "hello i am subtitle two",Icons.forward),
],
),
],),
);
Button:
FloatingActionButton(
onPressed:()
{
print("clicked"); //i want to add new item from here, when i press on click
},
child:Text("+"),
backgroundColor: Colors.blue,
),
);
look to add a new item you must do the following:
Assuming your screen is a StatefulWidget
class SomePage extends StatefulWidget {
#override
_SomePageState createState() => _SomePageState();
}
class _SomePageState extends State<SomePage> {
var _listSection = List<Widget>();
#override
void initState() {
super.initState();
// Here initialize the list in case it is required
_listSection.add(
listSectionMethod("TITLE 1", "hello i am subtitle one", Icons.forward),
);
_listSection.add(
listSectionMethod("TITLE 2", "hello i am subtitle two", Icons.forward),
);
}
}
Widget:
Widget listSection() {
return Container(
margin: EdgeInsets.only(top: 210),
child: ListView(
children: [
Column(
children: this._listSection, // ----> Add this
),
],
),
);
}
Button:
FloatingActionButton(
onPressed: () {
setState(() {
// Here we add a new item to the list
_listSection.add(
listSectionMethod("New TITLE", "hello from click", Icons.forward),
);
});
},
child: Text("+"),
backgroundColor: Colors.blue,
);
Before I start, you need to make sure you're using a StatefulWidget, not a StatelessWidget so we can take advantage of the setState() method.
First, define a class member variable. You could put it on top of the class, or where ever else as long as it's not inside a function.
List<Widget> _listOfWidgets = [];
Second, let's modify your FAB (Floating Action Button) code:
FloatingActionButton(
onPressed: () {
print("fab clicked");
_addItemToList(); // new method
},
child:Text("+"),
backgroundColor: Colors.blue,
);
Let's now define the new method which will use our class member variable.
_addItemToList() {
List<Widget> tempList = _listOfWidgets; // defining a new temporary list which will be equal to our other list
tempList.add(Text("I'm an added item!"));
setState(() {
_listOfWidgets = tempList; // this will trigger a rebuild of the ENTIRE widget, therefore adding our new item to the list!
});
}
Let's modify your existing code to work with our new code:
Widget listSection = Container(
margin: EdgeInsets.only(top: 210),
child: ListView(
children: [
Column(
children: _listOfWidgets,
),
],
),
);
There you go!

Flutter: How can I select an image icon in a list without all icons in the list being selected?

I have created a List in Flutter using Sliver (Sliver Structure below):
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
//leading: Icon(Icons.arrow_back),
expandedHeight: 150.0,
pinned: true,
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
final item = taskList[index];
return Card()
The Card has a Dismissible Widget encapsulated within which creates a ListTile. The Dismissible works fine and I can swipe to dismiss individual cells in the list.
The issue I am having is related to an IconButton in my ListTile. My aim is that whenever I tap the IconButton it should toggle an Icon Flag on or off for the individual cell, but what happens is that all of the Icon Buttons in the List are toggled. From investigating the Dismissible Widget code I can understand that I need to uniquely identify each cell for this to work, I've tried using a Key to make the cells unique but that hasn't worked. Is anybody able to steer me in the right direction? The code for the IconButton is below, I also attempted to add a key to the ListTile but that didn't work so I removed it.
IconButton(
key: Key(item),
icon: Icon(Icons.flag),
color: (isPriority) ? Colors.red : Colors.grey,
onPressed: _toggleFlag,
)
My toggleFlag code is below, I already have a setState in there, it toggles the flag but the issue is that it toggles all flags in the list when i want to toggle the flag of the cell that it belongs to:
void _toggleFlag() {
setState(() {
if (isPriority) {
isPriority = false;
} else {
isPriority = true;
}
});
}
Create your card like this
return new Users(example: taskList[index]);
then craete widget for card
class Users extends StatefulWidget
{
final String example;
const Users ({Key key, this.example}) : super(key: key);
#override
UserWidgets createState() => UserWidgets();
}
class UserWidgets extends State<Users>
{
#override
Widget build(BuildContext context)
{
return new Container(
child: Card(
child:new IconButton(
icon: Icon(Icons.flag),
color: (isPriority) ? Colors.red : Colors.grey,
onPressed: ()
{
setState(() {
if (isPriority) {
isPriority = false;
} else {
isPriority = true;
}
});
}
,
),
);
}
I have been able to simplify my code to capture all of it and show where the icon button fits in (the icon button is right at the bottom). I'm still getting the issue that if I tap on an icon flag all of the icon flags are then toggled on rather than the specific one that I tapped.
class HomePage extends StatefulWidget {
_HomePageDemoState createState() => _HomePageDemoState();
}
class _HomePageDemoState extends State<HomePage> {
List<String> taskList = [
'The Enchanted Nightingale',
'The Wizard of Oz',
'The BFG'
];
bool isPriority = false;
void _toggleFlag() {
setState(() {
if (isPriority) {
isPriority = false;
} else {
isPriority = true;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
final item = taskList[index];
return Card(
elevation: 8.0,
child: ListTile(
contentPadding: EdgeInsets.all(0.0),
leading: Container(
color: (isPriority) ? Colors.red : Colors.grey,
//padding: EdgeInsets.only(right: 12.0),
padding: EdgeInsets.all(20.0),
child: Text(
'01',
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Colors.white),
),
),
title: Text('$item'), //Text('The Enchanted Nightingale'),
subtitle:
Text('Music by Julie Gable. Lyrics by Sidney Stein.'),
trailing: IconButton(
key: Key(item),
icon: Icon(Icons.flag),
color: (isPriority) ? Colors.red : Colors.grey,
onPressed: _toggleFlag,
),
),
);
},
childCount: taskList.length,
),
),
],
),
);
}
}

Flutter search bar with autocomplete

I'm looking for a search bar in flutter docs but can't find it, is there a widget for the search bar with autocomplete in appbar. For example, I have a search icon on my appbar. When one press it show's the search box, when you type it should show autocomplete from the dropdown with listtile. I managed to implement this but it's not easy to use because I need a dropdown to show suggestion autocomplete, then use the suggestion for a new route if selected.
Here the search action
You can use Stack to achieve the autocomplete dropdown box effect. Example below has 2 Containers - both hold ListView as child objects. One holds search results, other has some random text as content for the body. ListView (search result) is placed inside an Align Object and alignment property is set to Alignment.topCenter. This ensures that List appears at the top, just below the AppBar.
Updated the Post (accepted answer) mentioned in the comments for a complete a demo.
As explained above:
#override
Widget build(BuildContext context) {
return new Scaffold(
key: key,
appBar: buildBar(context),
body: new Stack(
children: <Widget>[
new Container(
height: 300.0,
padding: EdgeInsets.all(10.0),
child: new DefaultTabController(length: 5, child: mainTabView),
),
displaySearchResults(),
],
));
}
Widget displaySearchResults() {
if (_IsSearching) {
return new Align(
alignment: Alignment.topCenter,
//heightFactor: 0.0,
child: searchList());
} else {
return new Align(alignment: Alignment.topCenter, child: new Container());
}
}
Complete demo
class SearchList extends StatefulWidget {
SearchList({Key key, this.name}) : super(key: key);
final String name;
#override
_SearchListState createState() => new _SearchListState();
}
class _SearchListState extends State<SearchList> {
Widget appBarTitle = new Text(
"",
style: new TextStyle(color: Colors.white),
);
Icon actionIcon = new Icon(
Icons.search,
color: Colors.white,
);
final key = new GlobalKey<ScaffoldState>();
final TextEditingController _searchQuery = new TextEditingController();
List<SearchResult> _list;
bool _IsSearching;
String _searchText = "";
String selectedSearchValue = "";
_SearchListState() {
_searchQuery.addListener(() {
if (_searchQuery.text.isEmpty) {
setState(() {
_IsSearching = false;
_searchText = "";
});
} else {
setState(() {
_IsSearching = true;
_searchText = _searchQuery.text;
});
}
});
}
#override
void initState() {
super.initState();
_IsSearching = false;
createSearchResultList();
}
void createSearchResultList() {
_list = <SearchResult>[
new SearchResult(name: 'Google'),
new SearchResult(name: 'IOS'),
new SearchResult(name: 'IOS2'),
new SearchResult(name: 'Android'),
new SearchResult(name: 'Dart'),
new SearchResult(name: 'Flutter'),
new SearchResult(name: 'Python'),
new SearchResult(name: 'React'),
new SearchResult(name: 'Xamarin'),
new SearchResult(name: 'Kotlin'),
new SearchResult(name: 'Java'),
new SearchResult(name: 'RxAndroid'),
];
}
#override
Widget build(BuildContext context) {
return new Scaffold(
key: key,
appBar: buildBar(context),
body: new Stack(
children: <Widget>[
new Container(
height: 300.0,
padding: EdgeInsets.all(10.0),
child: new Container(
child: ListView(
children: <Widget>[
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
new Text("Hello World!"),
],
),
),
),
displaySearchResults(),
],
));
}
Widget displaySearchResults() {
if (_IsSearching) {
return new Align(
alignment: Alignment.topCenter,
child: searchList());
} else {
return new Align(alignment: Alignment.topCenter, child: new Container());
}
}
ListView searchList() {
List<SearchResult> results = _buildSearchList();
return ListView.builder(
itemCount: _buildSearchList().isEmpty == null ? 0 : results.length,
itemBuilder: (context, int index) {
return Container(
decoration: new BoxDecoration(
color: Colors.grey[100],
border: new Border(
bottom: new BorderSide(
color: Colors.grey,
width: 0.5
)
)
),
child: ListTile(
onTap: (){},
title: Text(results.elementAt(index).name,
style: new TextStyle(fontSize: 18.0)),
),
);
},
);
}
List<SearchResult> _buildList() {
return _list.map((result) => new SearchResult(name: result.name)).toList();
}
List<SearchResult> _buildSearchList() {
if (_searchText.isEmpty) {
return _list.map((result) => new SearchResult(name: result.name)).toList();
} else {
List<SearchResult> _searchList = List();
for (int i = 0; i < _list.length; i++) {
SearchResult result = _list.elementAt(i);
if ((result.name).toLowerCase().contains(_searchText.toLowerCase())) {
_searchList.add(result);
}
}
return _searchList
.map((result) => new SearchResult(name: result.name))
.toList();
}
}
Widget buildBar(BuildContext context) {
return new AppBar(
centerTitle: true,
title: appBarTitle,
actions: <Widget>[
new IconButton(
icon: actionIcon,
onPressed: () {
_displayTextField();
},
),
// new IconButton(icon: new Icon(Icons.more), onPressed: _IsSearching ? _showDialog(context, _buildSearchList()) : _showDialog(context,_buildList()))
],
);
}
String selectedPopupRoute = "My Home";
final List<String> popupRoutes = <String>[
"My Home",
"Favorite Room 1",
"Favorite Room 2"
];
void _displayTextField() {
setState(() {
if (this.actionIcon.icon == Icons.search) {
this.actionIcon = new Icon(
Icons.close,
color: Colors.white,
);
this.appBarTitle = new TextField(
autofocus: true,
controller: _searchQuery,
style: new TextStyle(
color: Colors.white,
),
);
_handleSearchStart();
} else {
_handleSearchEnd();
}
});
}
void _handleSearchStart() {
setState(() {
_IsSearching = true;
});
}
void _handleSearchEnd() {
setState(() {
this.actionIcon = new Icon(
Icons.search,
color: Colors.white,
);
this.appBarTitle = new Text(
"",
style: new TextStyle(color: Colors.white),
);
_IsSearching = false;
_searchQuery.clear();
});
}
}
It is actually very simple. you can refer above answers for details. Let's follow these steps:
Create a list of items we want to have in the autofill menu, lets name it autoList
Create one more emptyList named filteredList
Add all the values of autoList to filterList
void initState() {
filteredList.addAll(autoList);
}
Create a custom search bar widget with a TextField in it
we will be getting a 'value' i.e. the text entered from this Textfield: eg. TextFiled(onchange(value){})
Assuming that we have strings in our autoList, write:
filteredList.removeWhere((i) => i.contains(value.toString())==false);
The complete TextField widget will look like:
TextField(
onChanged: (value) {
setState(() {
filteredList.clear(); //for the next time that we search we want the list to be unfilterted
filteredList.addAll(autoList); //getting list to original state
//removing items that do not contain the entered Text
filteredList.removeWhere((i) => i.contains(value.toString())==false);
//following is just a bool parameter to keep track of lists
searched=!searched;
});
},
controller: editingController,
decoration: InputDecoration(
border: InputBorder.none,
labelText: "Search for the filtered list",
prefixIcon: Icon(Icons.search),
),
),
Now, along the search bar, we just have to display filteredList with ListViewBuilder. done :)
this plugin will helpful for you,loader_search_bar
Flutter widget integrating search field feature into app bar, allowing to receive query change callbacks and automatically load new data set into ListView. It replaces standard AppBar widget and needs to be placed underneath Scaffold element in the widget tree to work properly.
Getting started
To start using SearchBar insert it in place of an AppBar element in the Scaffold widget. Regardless of the use case, defaultBar named argument has to be specified, which basically is a widget that will be displayed whenever SearchBar is not in activated state:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: SearchBar(
defaultBar: AppBar(
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: _openDrawer,
),
title: Text('Default app bar title'),
),
...
),
body: _body,
drawer: _drawer,
);
}
Optional attributes
searchHint - hint string being displayed until user inputs any text,
initialQuery - query value displayed for the first time in search field,
iconified - boolean value indicating way of representing non-activated SearchBar:
true if widget should be showed as an action item in defaultBar,
false if widget should be merged with defaultBar (only leading icon of the default widget and search input field are displayed in such case),
autofocus - boolean value determining if search text field should get focus whenever it becomes visible,
autoActive - ,
attrs - SearchBarAttrs class instance allowing to specify part of exact values used during widget building (e.g. search bar colors, text size, border radius),
controller - SearchBarController object that provides a way of interacing with current state of the widget,
searchItem - defining how to build and position search item widget in app bar,
overlayStyle - status bar overlay brightness applied when widget is activated.
Query callbacks
To get notified about user input specify onQueryChanged and/or onQuerySubmitted callback functions that receive current query string as an argument:
appBar: SearchBar(
...
onQueryChanged: (query) => _handleQueryChanged(context, query),
onQuerySubmitted: (query) => _handleQuerySubmitted(context, query),
),
QuerySetLoader
By passing QuerySetLoader object as an argument one can additionally benefit from search results being automatically built as ListView widget whenever search query changes:
appBar: SearchBar(
...
loader: QuerySetLoader<Item>(
querySetCall: _getItemListForQuery,
itemBuilder: _buildItemWidget,
loadOnEachChange: true,
animateChanges: true,
),
),
List<Item> _getItemListForQuery(String query) { ... }
Widget _buildItemWidget(Item item) { ... }
querySetCall - function transforming search query into list of items being then rendered in ListView (required),
itemBuilder - function creating Widget object for received item, called during ListView building for each element of the results set (required),
loadOnEachChange - boolean value indicating whether querySetCall should be triggered on each query change; if false query set is loaded once user submits query,
animateChanges - determines whether ListView's insert and remove operations should be animated.
SearchItem
Specifying this parameter allows to customize how search item should be built and positioned in app bar. It can be either action or menu widget. No matter which of these two is picked, two constructor arguments can be passed:
builder - function receiving current BuildContext and returning Widget for action or PopupMenuItem for menu item,
gravity - can be one of SearchItemGravity values: start, end or exactly. If no arguments are passed, SearchBar will create default item which is search action icon with start gravity.
SearchItem.action
appBar: SearchBar(
// ...
searchItem: SearchItem.action(
builder: (_) => Padding(
padding: EdgeInsets.all(12.0),
child: Icon(
Icons.find_in_page,
color: Colors.indigoAccent,
),
),
gravity: SearchItemGravity.exactly(1),
),
)
SearchItem.menu
appBar: SearchBar(
// ...
searchItem: SearchItem.menu(
builder: (_) => PopupMenuItem(
child: Text("Search 🔍"),
value: "search",
),
gravity: SearchItemGravity.end,
),
)
Also, bear in mind that SearchBar will prevent built item widget from receiving tap events and will begin search action rather than that.
hope it will help you.
import 'package:flutter/material.dart';
class SearchText extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Searchable Text"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () {
showSearch(
context: context,
delegate: DataSearch(),
);
})
],
),
drawer: Drawer(),
);
}
}
class DataSearch extends SearchDelegate<String> {
final cities = ['Ankara', 'İzmir', 'İstanbul', 'Samsun', 'Sakarya'];
var recentCities = ['Ankara'];
#override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
query = "";
})
];
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: AnimatedIcon(
icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation,
),
onPressed: () {
close(context, null);
});
}
#override
Widget buildResults(BuildContext context) {
return Center(
child: Container(
width: 100,
height: 100,
child: Card(
color: Colors.red,
child: Center(child: Text(query)),
),
),
);
}
#override
Widget buildSuggestions(BuildContext context) {
final suggestionList = query.isEmpty
? recentCities
: cities.where((p) => p.startsWith(query)).toList();
return ListView.builder(
itemBuilder: (context, index) => ListTile(
onTap: () {
showResults(context);
},
leading: Icon(Icons.location_city),
title: RichText(
text: TextSpan(
text: suggestionList[index].substring(0, query.length),
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
),
children: [
TextSpan(
text: suggestionList[index].substring(query.length),
),
],
),
),
),
itemCount: suggestionList.length,
);
}
}