This is the 24th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021
Add a search box to the chat screen. So I’m going to add a cell to the ListView, so I’m going to add 1 to the itemCount.
itemCount: _datas.length + 1,
Copy the code
Create a chat package, then drag chat_page in and recreate a search_cell file.
Extract the itemBuilder code from the ListView into a method that returns the SearchCell if index == 0. You need index–, otherwise the index would start at 1.
Next write the search cell interface, first give a height and color, see if it can be displayed.
return Container(
height: 45,
width: 200,
color: Colors.red,
);
Copy the code
Run it and see it shows up.
The search box needs to respond to the click time, so we need to package it with GestureDetector, and then change the color to weChatThemColor, and use Stack inside.
return GestureDetector(
child: Container(
height: 45,
width: 200,
color: weChatThemColor,
padding: EdgeInsets.all(5),
child: Stack(
children: [
],
),
),
);
Copy the code
I’m going to add a white bottom to the stack
Container(decoration: BoxDecoration(color: color.white, borderRadius: Borderradio.circular (6.0),),),// WhiteCopy the code
Then add the image and text inside the Row. To center the Row, set the mainAxisAlignment to mainAxisAlignment. Center and to center the Row, change the stack alignment to align.center.
child: Stack( alignment: Alignment.center, children: [ Container( decoration: BoxDecoration( color: Color.white, borderRadius: borderRadius. Circular (6.0),),),// White Row(mainAxisAlignment: MainAxisAlignment. Center, children: [Image (Image: AssetImage (' images/magnifying glass p. ng), width: 15, color: Color.grey,), Text(' search ',style: TextStyle(fontSize: 15,color: color.grey),],],),Copy the code
This completes the search box page
Next click to go to a new screen, so add onTap and create a file to write the new page SearchPage.
import 'package:flutter/material.dart'; class SearchPage extends StatefulWidget { const SearchPage({Key? key}) : super(key: key); @override _SearchPageState createState() => _SearchPageState(); } class _SearchPageState extends State<SearchPage> { @override Widget build(BuildContext context) { return Scaffold( body: Column( children: [ SearchBar(), Expanded( flex: 1, child: ListView.builder( itemBuilder: itemBuilder, itemCount: 3,), [,], (,); } Widget itemBuilder(BuildContext context, int index) { return Container( child:Text("Column$index"), ); } } class SearchBar extends StatefulWidget { const SearchBar({Key? key}) : super(key: key); @override _SearchBarState createState() => _SearchBarState(); } class _SearchBarState extends State<SearchBar> { @override Widget build(BuildContext context) { return Container( height: 84, color: weChatThemColor, ); }}Copy the code
Then add the page push to the SearchCell’s GestureDetector.
onTap: (){
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) =>
SearchPage()));
},
Copy the code
After running, you get the following interface:
If you find spacing here, use removePadding to remove the spacing between the ListView headers.
child: MediaQuery.removePadding(
removeTop: true,
context: context,
child: ListView.builder(
itemBuilder: itemBuilder,
itemCount: 3,
),
),
Copy the code
Then start writing the SearchBar.
return Container( height: 84, color: weChatThemColor, child: Column( children: [ SizedBox(height: 40,), Container( height: 44, color:Colors.red, child: Row( children: [ Container( width: ScreenWidth (context) - 40, height: 34, color: Colors, yellow,), / / the rounded background Text (" cancel "), / / cancel button],),),,,);Copy the code
After running, see:
Then add rounded corners, spacing, buttons, etc.
return Container( height: 84, color: weChatThemColor, child: Column( children: [ SizedBox( height: 40, ), Container( height: 44, color: Colors.red, child: Row( children: [ Container( width: screenWidth(context) - 50, height: 34, margin: EdgeInsets.only(left: 5, right: 5), padding: EdgeInsets.only(left: 5, right: 5), decoration: BoxDecoration(color: Colors. White, borderRadius: borderRadius. Circular (6.0),), child: The Row (mainAxisAlignment: mainAxisAlignment spaceBetween, children: [Image (Image: AssetImage (' images/magnifying glass p. ng), width: 20, color: Colors, grey), / / a magnifying glass Icon (the Icons. Cancel),),),), / / the rounded background Text (" cancel "), / / cancel button],),),,,);Copy the code
Then you need to add TextField.
return Container( height: 84, color: weChatThemColor, child: Column( children: [ SizedBox( height: 40, ), Container( height: 44, child: Row( children: [ Container( width: screenWidth(context) - 50, height: 34, margin: EdgeInsets.only(left: 5, right: 5), padding: EdgeInsets.only(left: 5, right: 5), decoration: BoxDecoration( color: Colors. White, borderRadius: borderRadius. Circular (6.0),), child: Row(mainAxisAlignment: MainAxisAlignment spaceBetween, children: const [Image (Image: AssetImage (' images/magnifying glass p. ng), width: 20, color: Color.grey,), // Expanded(Child: TextField(cursorColor: color.green, Autofocus: true, style: TextStyle(fontSize: 18.0, color: color.black, fontWeight: fontweight.w300,), decoration: InputDecoration( contentPadding: EdgeInsets.only(left: 5,bottom: 10), border:InputBorder. None, hintText:' search ',),), Icon(Icons. Cancel,size: 20,color: Colors. Grey),],),), / / the rounded background Text (" cancel "), / / cancel button],),),),),);Copy the code
Expanded was wrapped around the ListView because the ListView has no size. When shrinkWrap: true is set, the content of the ListView is displayed according to its size and Expanded is not needed. So let’s do it the same way we did before, and let the ListView fill the bottom part of the screen.
return Scaffold(
body: Column(
children: [
SearchBar(),
ListView.builder(
shrinkWrap: true,
itemBuilder: itemBuilder,
itemCount: 3,
),
],
),
);
Copy the code
Next you handle the click and logical events
Here we add a pop click event to cancel.
GestureDetector(Child: Text(' cancel '), onTap: () {navigator.pop (context); },),Copy the code
Next you need to listen to the search box, and when there is nothing there, then cancel icon is not displayed. Create a TextEditingController variable.
final TextEditingController _textEditingController = TextEditingController();
Copy the code
Add Controller for TextField
controller: _textEditingController,
Copy the code
Then listen for onChanged on TextField
onChanged: _onChanged,
void _onChanged(value) {}
Copy the code
Then create _showClear to see if the cancel button following the TextField is displayed. The default is false.
bool _showClear = false;
Copy the code
The string length is determined in the _onChanged method and the _showClear value is assigned.
void _onChanged(String text) { if (text.length > 0) { setState(() { _showClear = true; }); } else { setState(() { _showClear = false; }); }}Copy the code
Whether to display Icon according to the value of _showClear.
if (_showClear) Icon(
Icons.cancel,
size: 20,
color: Colors.grey,
)
Copy the code
Add a click gesture to cancel Icon to clear the input.
if (_showClear) GestureDetector( child: Icon( Icons.cancel, size: 20, color: Colors.grey, ), onTap: () { _textEditingController.clear(); setState(() { _onChanged(""); }); },)Copy the code
This completes the page section. There is a problem here. When ChatPage clicks into SearchPage, the SearchPage must have data. Does the SearchBar have data? There are two cases: one is yes, the data is sent to the SearchBar and the search results are returned to the SearchPage; the other is no, the SearchBar directly returns the input to the SearchPage through a callback. Add it in SearchCell first
final List<Chat>? datas;
const SearchCell({ this.datas});
Copy the code
Then assign a value to it in the ChatPage, and the data is given to the SearchCell.
if (index == 0) {
return SearchCell(datas: _datas,);
}
Copy the code
It is then passed to SearchPage. In SearchPage add:
final List<Chat>? datas;
const SearchPage({ this.datas});
Copy the code
The data is then passed in where the SearchCell came in
SearchPage(datas: datas,)
Copy the code
Add a callback to the SearchBar:
const SearchBar({Key? key, this.onChanged}) : super(key: key);
final ValueChanged<String>? onChanged;
Copy the code
This callback is then called within _onChanged. If else _showClear = text.length > 0
void _onChanged(String text) { if (widget.onChanged ! = null) { widget.onChanged! (text); } setState(() { _showClear = text.length > 0; }); }Copy the code
At this point the SearchPage can return the call
SearchBar(onChanged: ( String text){},),
Copy the code
Create a _searchData method to handle the text passed by the callback.
SearchBar(onChanged: (String text){
_searchData(text);
},),
void _searchData(String text) {
}
Copy the code
Next, create an array to hold the data that matches the criteria
List<Chat> _models = [];
Copy the code
The _searchData loop retrieves and then adds the data that matches the criteria to _Models.
void _searchData(String text) { _models.clear(); If (text.length > 0) {if (widget.datas! // loop for (int I = 0; i < widget.datas! .length; i++ ) { String? name = widget.datas! [i].name; if ((name ?? "").contains(text)) { _models.add(widget.datas! [i]); } } } } setState(() { }); }Copy the code
Next, display the contents of the ListView according to _Models, changing itemCount to _Models.length.
itemCount: _models.length,
Copy the code
Return in itemBuilder
Widget itemBuilder(BuildContext context, int index) {
return Container(
color: Colors.red,
child: Text("${_models[index].name}"),
);
}
Copy the code
The search will then display models that match the criteria.
The next step is to write the Cell interface, where you can directly copy the style of the previous chat interface.
ListTile( title: Text(_models[index].name ?? ""), subtitle: Container( alignment: Alignment.bottomCenter, padding: EdgeInsets.only(right: 10), height: 25, child: Text( _models[index].message ?? "", overflow: Ellipsis,),), leading: ClipRRect(// Cut to rectangle borderRadius: borderRadius. Circular (5.0), child: Image(image: NetworkImage(_models[index].imageUrl ?? "")), ), );Copy the code
After the operation:
I’m going to highlight the name here, so I’m going to extract the title part.
title: _title(_models[index].name ?? ""),
Text _title(String name) {
return Text(name);
}
Copy the code
We need to know what the name of the search is, so we declare a variable here, okay
String _searchStr = "";
Copy the code
Then assign the value in _searchData.
_searchStr = text
Copy the code
Then declare two TextStyles
TextStyle _normalStyle = TextStyle(fontSize: 16,color: Colors.black);
TextStyle _highLightedStyle = TextStyle(fontSize: 16,color: Colors.green);
Copy the code
And I’m going to do that in the _title method.
idget _title(String name) { List<TextSpan> spans = []; List<String> strs = name.split(_searchStr); for (int i = 0; i < strs.length; i ++) { String str = strs[i]; print(strs); if (str == "" && i < strs.length - 1) { print('here1'); spans.add(TextSpan(text: _searchStr,style:_highLightedStyle)); } else { print('here2'); spans.add(TextSpan(text: str,style:_normalStyle)); if (i < strs.length - 1) { print('here3'); spans.add(TextSpan(text: _searchStr,style:_highLightedStyle)); }}}Copy the code
This completes the search highlighting.