Flutter —— 搜索框

Flutter —— 搜索框

这是我参与11月更文应战的第24天,活动详情检查:2021最终一次更文应战

在谈天界面增加一个查找框。那么就在ListView里边增加一个cell,那么就需求itemCount里边加1。

  itemCount: _datas.length + 1,

创立一个chat package,然后将chat_page拖进来并且从头创立一个search_cell文件。

Flutter —— 搜索框

将ListView里边的itemBuilder的代码抽取成一个办法,然后在里边判别假如index == 0 则回来 SearchCell。这儿后边需求index–,否则index就会从1开端。

Flutter —— 搜索框

接下来写search cell 的界面,先赋予一个高度和色彩,来看是否能显现。

 return Container(
      height: 45,
      width:  200,
      color: Colors.red,
    );

运转后看到显现了出来。

Flutter —— 搜索框

查找框需求响应点击时间,所以这儿需求用GestureDetector包一下,然后色彩改为weChatThemColor,里边用Stack来写。

return GestureDetector(
      child: Container(
      height: 45,
      width:  200,
      color: weChatThemColor,
       padding: EdgeInsets.all(5),
        child: Stack(
          children: [
          ],
        ),
    ),
    );

在stack里边先增加一个白底

 Container(
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(6.0),
              ),
            ),//白底

然后增加Row里边放图片和文字,为了让其居中设置row的mainAxisAlignment为MainAxisAlignment.center,而让row居中则将stack的alignment改为Alignment.center。

 child: Stack(
          alignment: Alignment.center,
          children: [
            Container(
              decoration: BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.circular(6.0),
              ),
            ),//白底
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Image(image: AssetImage('images/放大镜b.png'),width: 15,color: Colors.grey,),
                Text('   查找',style: TextStyle(fontSize: 15,color: Colors.grey),),
            ],),
          ],
        ),

这样查找框页面就完成了

Flutter —— 搜索框

接下来点进去需求到一个新的界面,那么就增加onTap,然后创立一个文件来写新页面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,
    );
  }
}

然后在SearchCell的GestureDetector里边增加页面push。

   onTap: (){
        Navigator.of(context).push(MaterialPageRoute(
            builder: (BuildContext context) =>
                SearchPage()));
      },

运转后得到下面的界面:

Flutter —— 搜索框

发现这儿有距离,那么就运用removePadding将ListView头部距离去掉。

    child: MediaQuery.removePadding(
              removeTop: true,
              context: context,
              child: ListView.builder(
                itemBuilder: itemBuilder,
                itemCount: 3,
              ),
            ),

然后开端编写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,
                ),//圆角布景
                Text('撤销'),//撤销按钮
              ],
            ),
          ),
        ],
      ),
    );

运转后看到:

Flutter —— 搜索框

然后在增加圆角,距离以及按钮等。

    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: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      Image(
                        image: AssetImage('images/放大镜b.png'),
                        width: 20,
                        color: Colors.grey,
                      ), //放大镜
                      Icon(Icons.cancel),
                    ],
                  ),
                ), //圆角布景
                Text('撤销'), //撤销按钮
              ],
            ),
          ),
        ],
      ),
    );

然后还需求增加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/放大镜b.png'),
                        width: 20,
                        color: Colors.grey,
                      ), //放大镜
                      Expanded(
                        child: TextField(
                          cursorColor: Colors.green,
                          autofocus: true,
                          style: TextStyle(
                            fontSize: 18.0,
                            color: Colors.black,
                            fontWeight: FontWeight.w300,
                          ),
                          decoration: InputDecoration(
                            contentPadding: EdgeInsets.only(left: 5,bottom: 10),
                            border:InputBorder.none,
                            hintText:'查找',
                          ),
                        ),
                      ),
                      Icon(Icons.cancel,size: 20,color: Colors.grey,),
                    ],
                  ),
                ), //圆角布景
                Text('撤销'), //撤销按钮
              ],
            ),
          ),
        ],
      ),
    );

之前用Expanded包着ListView是因为ListView没有巨细,当咱们设置了 shrinkWrap: true之后,那么就会依据ListView的内容巨细来展示,就不需求Expanded了。咱们这儿还是用之前的写法,让ListView占满屏幕下面部分。

   return Scaffold(
      body: Column(
        children: [
          SearchBar(),
          ListView.builder(
            shrinkWrap: true,
            itemBuilder: itemBuilder,
            itemCount: 3,
          ),
        ],
      ),
    );

接下来处理点击和逻辑事情

这儿先为撤销增加一个pop的点击事情。

  GestureDetector(
                  child: Text('撤销'),
                  onTap: () {
                    Navigator.pop(context);
                  },
                ),

接下来需求监听查找框,当没有任何东西的时分,那么就不显现cancel icon。创立一个TextEditingController变量

  final TextEditingController _textEditingController = TextEditingController();

为TextField增加Controller

  controller: _textEditingController,

然后监听TextField的onChanged

   onChanged: _onChanged,
  void _onChanged(value) {}

然后创立_showClear来判别是否显现TextField后边的撤销按钮,默认为false。

 bool _showClear = false; 

在_onChanged办法里边进行字符串长度的判别以及_showClear的赋值

  void _onChanged(String text) {
    if (text.length > 0) {
      setState(() {
        _showClear = true;
      });
    } else {
      setState(() {
        _showClear = false;
      });
    }
  }

依据_showClear的值觉得是否显现Icon。

   if (_showClear) Icon(
                              Icons.cancel,
                              size: 20,
                              color: Colors.grey,
                            )

为 cancel Icon增加点击手势清空输入内容。

   if (_showClear) GestureDetector(
                        child: Icon(
                          Icons.cancel,
                          size: 20,
                          color: Colors.grey,
                        ),
                        onTap: () {
                          _textEditingController.clear();
                          setState(() {
                            _onChanged("");
                          });
                        },
                      )

这样页面部分就完成了。这儿有个问题,ChatPage点击进去SearchPage的时分那么SearchPage一定要有数据,那么SearchBar 是否有数据呢?这儿有两种状况,一种是有,将数据传给SearchBar,查找完之后将成果回来给SearchPage,第二种是没有,SearchBar直接经过回调将输入内容给SearchPage。 先在SearchCell增加

  final List<Chat>? datas;
  const SearchCell({ this.datas});

然后在ChatPage为其赋值,这样数据就给了SearchCell。

   if (index == 0) {
      return SearchCell(datas: _datas,);
    }

然后再传给SearchPage。在SearchPage增加:

  final List<Chat>? datas;
  const SearchPage({ this.datas});

然后在SearchCell进来的地方将data传进来

SearchPage(datas: datas,)

在SearchBar里边增加一个回调:

  const SearchBar({Key? key, this.onChanged}) : super(key: key);
  final ValueChanged<String>? onChanged;

然后在_onChanged里边调用这个回调。这儿if else 判别太长了,能够直接运用 _showClear = text.length > 0进行设值。

  void _onChanged(String text) {
    if (widget.onChanged != null) {
      widget.onChanged!(text);
    }
    setState(() {
      _showClear = text.length > 0;
    });
  }

这个时分SearchPage就能够传回调了

 SearchBar(onChanged: ( String text){},),

创立一个 _searchData办法来处理回调传过来的文字。

  SearchBar(onChanged: (String text){
            _searchData(text);
          },),
  void _searchData(String text) {
}

接下来创立一个数组来装契合条件的数据

List<Chat> _models = [];

_searchData里边循环检索然后增加契合条件的数据到_models。

  void _searchData(String text) {
    _models.clear(); // 每次查找先状况
    if (text.length > 0 ) {
      if (widget.datas != null){   // 循环检索
        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(() {
    });
  }

接下来依据_models显现ListView的内容,将itemCount改为_models.length。

  itemCount: _models.length,

在itemBuilder里边回来

  Widget itemBuilder(BuildContext context, int index) {
    return Container(
      color: Colors.red,
      child: Text("${_models[index].name}"),
    );
  }

这样查找就能够显现契合条件的Model了。

Flutter —— 搜索框

接下来需求来编写Cell的界面,这儿能够直接把之前谈天界面的款式仿制过来。

ListTile(
      title: Text(_models[index].name ?? ""),
      subtitle: Container(
        alignment: Alignment.bottomCenter,
        padding: EdgeInsets.only(right: 10),
        height: 25,
        child: Text(
          _models[index].message ?? "",
          overflow: TextOverflow.ellipsis,
        ),
      ),
      leading: ClipRRect(
        //剪裁为圆角矩形
        borderRadius: BorderRadius.circular(5.0),
        child: Image(image: NetworkImage(_models[index].imageUrl ?? "")),
      ),
    );

运转后:

Flutter —— 搜索框

这儿需求对姓名做高亮处理,那么就把title部分提取出来做处理。

    title: _title(_models[index].name ?? ""),
  Text _title(String name) {
    return Text(name);
  }

咱们要知道查找的姓名是什么,所以这儿声明一个变量

 String _searchStr = "";

然后在_searchData里边赋值。

_searchStr = text

然后在声明2个TextStyle

  TextStyle _normalStyle = TextStyle(fontSize: 16,color: Colors.black);
  TextStyle _highLightedStyle = TextStyle(fontSize: 16,color: Colors.green);

然后再_title办法里边处理。

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));
        }
      }
    }

这样查找高亮就完成了。

Flutter —— 搜索框