Android

聊天页SearchBar

2021-12-18  本文已影响0人  浅墨入画

自定义SearchPage

新建search_page.dart文件,创建顶部搜索栏以及布局搜索页面

<!-- 搜索页 -->
import 'package:flutter/material.dart';

// SearchPage是有状态的
class SearchPage extends StatefulWidget {
  @override
  _SearchPageState createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          SearchBar(),
          // Expanded灵活布局
          Expanded(
              flex: 1,
              child: MediaQuery.removePadding(
                  context: context,
                  removeTop: true,
                  child: ListView.builder(
                    itemCount: 3,
                    itemBuilder: (BuildContext context, int index) {
                      return Text('123$index');
                    },
                  )))
        ],
      ),
    );
  }
}

<!-- 搜索页顶部搜索栏 -->
class SearchBar extends StatefulWidget {
  @override
  _SearchBarState createState() => _SearchBarState();
}

class _SearchBarState extends State<SearchBar> {
  @override
  Widget build(BuildContext context) {
    return Container(
      height: 84,
      color: Colors.red,
    );
  }
}
// 聊天页搜索Cell
class SearchCell extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        print('点击了搜索框!');
        // 跳转搜索页
        Navigator.of(context).push(MaterialPageRoute(
            builder: (BuildContext context) => SearchPage()
        ));
      },
......
跳转搜索页效果

布局SearchBar

SearchBar中的TextField输入框,是Flutter自身渲染引擎的。iOS的图层是通过UIKit绘制上去的,而Flutter中只有FlutterViewController是iOS的,页面上的其他展示内容,都是Flutter渲染引擎绘制上的。

class SearchBar extends StatefulWidget {
  @override
  _SearchBarState createState() => _SearchBarState();
}

class _SearchBarState extends State<SearchBar> {
  @override
  Widget build(BuildContext context) {
    return Container(
      height: 84,
      child: Column(
        children: [
          // 搜索栏上面部分留空
          SizedBox(
            height: 40,
          ),
          Container(
            height: 44,
            child: Row(
              children: [
                // 左侧圆角背景
                Container(
                  width: screenWidth(context) - 40,
                  height: 34,
                  // 搜索图标,清除按钮向内缩进5像素
                  padding: EdgeInsets.only(left: 5, right: 5),
                  margin: EdgeInsets.only(left: 5, right: 5),
                  decoration: BoxDecoration(
                      color: Colors.white,
                      borderRadius: BorderRadius.circular(6.0)),
                  child: Row(
                    children: [
                      Image(
                        image: AssetImage('images/放大镜b.png'),
                        width: 20,
                        color: Colors.grey,
                      ),//放大镜
                      Expanded(
                        child: TextField(
                          autofocus: true, // 一进来搜索框自动聚焦
                          // 光标颜色
                          cursorColor: Colors.green,
                          style: TextStyle(
                            fontSize: 18,
                            color: Colors.black,
                            fontWeight: FontWeight.w300,
                          ),
                          decoration: InputDecoration(
                              contentPadding:
                              EdgeInsets.only(left: 5, bottom: 10),
                              border: InputBorder.none,
                              hintText: '搜索'),
                        ),
                        flex: 1,
                      ), //输入框
                      Icon(
                        Icons.cancel,
                        size: 20,
                        color: Colors.grey,
                      ), //取消按钮
                    ],
                  ),
                ), //搜索框
                Text('取消'), //取消按钮
              ],
            ),
          ),//下面的搜索条
        ],
      ),
    );
  }
}
搜索页SearchBar

SearchBar响应事件

SearchBar右侧取消按钮添加手势,点击返回

GestureDetector(
  onTap: () {
    Navigator.pop(context);
  },
  child: Text('取消'),
), //取消按钮

SearchBar没有输入内容的时候,不应该显示clear按钮,输入内容的时候才显示

class _SearchBarState extends State<SearchBar> {
  // 定义_controller就可以调用clear清空输入框的方法
  final TextEditingController _controller = TextEditingController();

  // 是否显示右侧clear按钮
  bool _showClear = false;

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

......

child: Row(
  children: [
    Image(
      image: AssetImage('images/放大镜b.png'),
      width: 20,
      color: Colors.grey,
    ), //放大镜
    Expanded(
      child: TextField(
      controller: _controller,
       onChanged: _onChange,  //用于回调清除按钮的显示隐藏
       autofocus: true,
       // 一进来搜索框自动聚焦
       // 光标颜色
       cursorColor: Colors.green,
       style: TextStyle(
         fontSize: 18,
         color: Colors.black,
         fontWeight: FontWeight.w300,
       ),
       decoration: InputDecoration(
         contentPadding:
         EdgeInsets.only(left: 5, bottom: 10),
         border: InputBorder.none,
         hintText: '搜索'),
       ),
       flex: 1,
    ), //输入框
    _showClear
      ? GestureDetector(
         onTap: () {
           _controller.clear();
           setState(() {
             // 触发_onChange回调,清空输入框的时候,也隐藏掉clear按钮
              _onChange('');
           });
         },
         child: Icon(
           Icons.cancel,
           size: 20,
           color: Colors.grey,
         ),
        ) //清空按钮
      : Container(),
   ],
),

SearchBar进行搜索的两种思路

下面使用的第二种思路解决

<!-- search_cell.dart文件 -->
class SearchCell extends StatelessWidget {

  final List<Chat> datas;
  const SearchCell({this.datas}); //接收chat_page的数据源
......

<!-- chat_page.dart文件 -->
Widget _itemBuilderForRow(BuildContext context, int index) {
    if (index == 0) {
      // listView第一个Cell创建SearchCell
      return SearchCell(
        datas: _datas,
      );
    }
......
<!-- search_page.dart文件 -->
class SearchPage extends StatefulWidget {

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

@override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTapDown: (TapDownDetails details) {
        Navigator.of(context).push(MaterialPageRoute(
            // 跳转SearchPage的时候把datas带过去
            builder: (BuildContext context) => SearchPage(
                  datas: datas,
                )));
      },
......
<!-- SearchBar类 -->
class SearchBar extends StatefulWidget {
  final ValueChanged<String> onChanged;
  const SearchBar({this.onChanged});
......

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

// SearchPage要把回调传递给SearchBar
<!-- SearchPage类 -->
class _SearchPageState extends State<SearchPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          SearchBar(
            onChanged: (String text) {
              print('我接收到了$text');
            },
          ),
SearchPage接收到搜索框的内容
@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          SearchBar(
            onChanged: (String text) {
              print('我接收到了$text');
              // 调用检索方法
              _searchData(text);
            },
          ),
......

class _SearchPageState extends State<SearchPage> {
  // 搜索出来的数据
  List<Chat> _modals = [];

  void _searchData(String text) {
    _modals.clear();
    if (text.length > 0) {
      for (int i = 0; i < widget.datas.length; i++) {
        // 循环检索
        String name = widget.datas[i].name;
        if (name.contains(text)) {
          _modals.add(widget.datas[i]);
        }
      }
    }
    setState(() {});
  }
<!-- SearchPage类 -->
Expanded(
  flex: 1,
  child: MediaQuery.removePadding(
    context: context,
    removeTop: true,
    child: ListView.builder(
      // shrinkWrap: true,//根据listView的内容大小来展示
      itemCount: _modals.length,
      itemBuilder: (BuildContext context, int index) {
        return Text('${_modals[index].name}');
      },
)))
......
成功检索出数据

SearchPage显示

SearchPage使用ListTile进行展示

<!-- SearchPage类 -->
Expanded(
  flex: 1,
  child: MediaQuery.removePadding(
    removeTop: true,
    context: context,
    child: ListView.builder(
      // shrinkWrap: true, //根据listView的内容大小来展示
      itemCount: _modals.length,
      // SearchPage的展示Cell抽取出来
      itemBuilder: _buildCellForRow()),
  ),
),
......

Function _buildCellForRow() {
    return ((BuildContext context, int index) {
      return ListTile(
        title: Text(_modals[index].name), //名字
        subtitle: Container(
          alignment: Alignment.bottomCenter,
          padding: EdgeInsets.only(
            right: 10,
          ),
          height: 25,
          child: Text(
            _modals[index].message,
            overflow: TextOverflow.ellipsis,
          ),
        ), //聊天信息
        leading: Container(
          width: 44,
          height: 44,
          decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(6.0),
              image: DecorationImage(
                  image: NetworkImage(_modals[index].imageUrl))),
        ), //头像
      );
    });
  }

检索出来的数据色值不同,是一个富文本,要把名字展示抽成富文本方法

// 名字展示使用抽出来的富文本方法
Function _buildCellForRow() {
    return ((BuildContext context, int index) {
      return ListTile(
        title: _title(_modals[index].name), //名字
......

// 定义搜索框输入的内容_searchStr,用于富文本高亮
class _SearchPageState extends State<SearchPage> {
  String _searchStr = '';
  List<Chat> _modals = [];

  void _searchData(String text) {
    _modals.clear(); //每次搜索先清空!
    _searchStr = text;
......

// 富文本相关展示逻辑
TextStyle _normalStyle = TextStyle(
    fontSize: 16,
    color: Colors.black,
  );

  TextStyle _highlightedStyle = TextStyle(
    fontSize: 16,
    color: Colors.green,
  );

  // 富文本方法
  Widget _title(String name) {
    List<TextSpan> spans = [];
    // 把当前cell的name按输入内容截成数组
    // 数组内是空字符的三种情况 1.以输入字符开头 2.以输入字符结尾 3.是两个相邻的输入字符串
    List<String> strs = name.split(_searchStr);
    for (int i = 0; i < strs.length; i++) {
      String str = strs[i]; //拿出字符串
      if (str == '' && i < strs.length - 1) {
        spans.add(TextSpan(text: _searchStr, style: _highlightedStyle));
      } else {
        spans.add(TextSpan(text: str, style: _normalStyle));
        if (i < strs.length - 1) {
          spans.add(TextSpan(text: _searchStr, style: _highlightedStyle));
        }
      }
    }

    return RichText(
      text: TextSpan(children: spans),
    );
  }

Dart练习网站

检索高亮显示

尝试练习:实现模糊搜索,输入内容包含空格,依然能够检索出来...

关于Dart多线程与异步的总结

Dart中的异步
*   Future对象来完成异步操作。
    *   通过工厂构造方法创建Future对象。
    *   参数为Dart的函数
        *   函数的执行代码将被放入事件队列异步执行。
*   async 和 await 。如果Future内部代码希望同步执行,则使用await修饰。被async修饰的函数为异步执行。
*   Future结果处理
    *   Future.then 用来注册一个Future完成时要调用的回调
    *   Future.catchError注册一个回调,来捕捉Future的error
        *   Future.catchError回调只处理原始Future抛出的错误,不能处理回调函数抛出的错误
        *   onError只能处理当前Future的错误
    *   Future.whenComplete 在Future完成之后总是会调用,不管是错误导致的完成还是正常执行完毕。
Dart的事件循环
*   在Dart中,实际上有两种队列:
    *   事件队列(event queue),包含所有的外来事件:I/O、mouse events、drawing events、timers、isolate之间的信息传递。
    *   微任务队列(microtask queue),表示一个短时间内就会完成的异步任务。它的优先级最高,高于event queue,只要队列中还有任务,就可以一直霸占着事件循环。microtask queue添加的任务主要是由 Dart内部产生。
Dart中的多线程
*   Dart是单线程语言,但并不代表它不能并行执行代码。因为它拥有Isolate。
*   Isolate
    *   Isolate可以看成是一个小的进程。
        *   它拥有独立的内存空间,不同Isolate之间通过消息进行通信
        *   它拥有自己的事件循环及队列(MicroTask 和 Event)
    *   Isolate使用
        *   1、创建子线程任务:Isolate.spawn(arg1,arg2);
            *   arg1: 需要在子线程执行的函数
            *   arg2:传递给函数的参数
            *   这样就在另一个线程中执行arg1的代码了。
        *   2、端口通讯
            *   ReceivePort port = ReceivePort()//构造一个端口。
            *   port.listen(arg)//注册一个回调监听
                *   arg为回调函数。参数为消息内容
            *   在子线程中.通过port.send() 发送消息
        *   3、关闭端口,销毁Isolate
            *   注意端口使用完毕需要调用port.close()函数关闭
            *   Isolate使用完毕,需要调用Isolate.kill()函数销毁
*   compute
    *   由于dart中的Isolate比较复杂,数据传输比较麻烦,因此flutter在foundation库中封装了一个轻量级compute操作
    *   使用:compute(func,count)
        *   func:子线程函数!func如果有返回值会直接被compute函数返回出去!
        *   count: 给函数的参数
异步多线程结合
*   Dart中的异步是可以和多线程结合使用的。
*   如果Future中返回子线程的返回值。那么Future的处理是异步的
*   如果Future中没有返回子线程的返回值。那么Future的处理是同步的
*   Future的结果处理会在Future执行完毕立即执行。可以看做是一个任务。
上一篇下一篇

猜你喜欢

热点阅读