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.