Flutter新人实战—从0开始开发一个DIY活动记录应用(七)实时查询控件实现

版权声明:本文为本人原创文章,未经本人允许不得转载。

hello,大家好,继续我们这个项目的更新,今天我们的任务非常明确:实现实时查询功能
开始之前,我们先看效果演示,苹果的录屏转GIF导致清晰度有点差,大家将就一下。


查询.gif

废话少说,我们直接开始。
首先我对首页UI做了调整,看下图:


image.png

原来设计的时候是左下角有搜索按钮,现在我在标题栏的顶部添加了查询入口,是自己组合实现的一个查询插件,为什么这么做是因为考虑后面底部要加导航栏,所以暂时先把搜索栏放在上面,也是参考了目前很多主流的app。

做任何事之前第一步一定是理清思路,尽量不要想到哪写哪,这样很容易没有条理和逻辑性,代码可能也会混乱。
今天的查询功能全部是自己组合实现的,事后我google了下才知道flutter仓库里其实有好几个查询的插件了,肯定是比我的要好,但是通过自己所学能实现自己要的功能那种成就感是无法言语的。

实现思路:

1、在顶部标题栏实现查询入口
2、查询页面查询栏和查询结果的布局UI
3、实时查询逻辑和结果展示

知道思路那我们一步步来:

一、首页顶部标题栏实现查询控件入口

因为标题栏的查询控件并不是真的直接可以输入信息查询,实际上类似于可以点击的按钮,通过点击跳转至真正的查询页面。
我的实现方法是:GestureDetector+Container实现图上效果,我们在home_page.dart中修改appBar内容,代码如下:
home_page.dart

class HomePage extends StatefulWidget {
  @override
  HomePageState createState() => new HomePageState();
}

class HomePageState extends State<HomePage> {
......
 @override
  Widget build(BuildContext context) {
    //使用默认的tab控制器来实现tab标签和展示内容的联动
    return new DefaultTabController(
      //length代表一共有几个tabbar
      length: 2,
      child: new Scaffold(
        //重绘标题栏,将title名字整个替换成查询入口控件
        appBar: new AppBar(
          title: new GestureDetector(
            onTap: () {
              Navigator.of(context).push(new MaterialPageRoute(
                  fullscreenDialog: true,
                  builder: (context) {
                    return new DiySearch(
                      db: _database,
                    );
                  }));
            },
            child: new Container(
              padding: const EdgeInsets.all(4.0),
              alignment: Alignment.center,
              height: 30.0,
              decoration: new BoxDecoration(
                  color: Colors.grey[300],
                  borderRadius: new BorderRadius.circular(12.0)),
              child: new Row(
                children: <Widget>[
                  new Icon(
                    Icons.search,
                    color: Colors.grey[600],
                  ),
                  new Flexible(
                    child: new Text(
                      '活动名称、联系人',
                      style: Theme.of(context)
                          .textTheme
                          .body1
                          .copyWith(color: Colors.grey[600],fontStyle: FontStyle.italic),
                    ),
                  ),
                ],
              ),
            ),
          ),
......

以上代码通过container包裹查询图标和文本,并在外面嵌套一个手势监测控件,当用户点击的时候就可以导航到真正的查询页面。

二、查询页面实现

在pages目录下新建diy_search.dart文件,存放查询功能实现的代码。
diy_search.dart

import 'dart:io';
import 'package:activity_record/model/diy_project.dart';
import 'package:flutter/material.dart';
import 'dart:async';

class DiySearch extends StatefulWidget {

  @override
  State<StatefulWidget> createState() => new DiySearchState();
}

class DiySearchState extends State<DiySearch> {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        //当leading或action为null的时候,title控件将会占据他们的空间
        automaticallyImplyLeading: false,
        title: new Container(
          padding: const EdgeInsets.all(4.0),
          alignment: Alignment.center,
          height: 30.0,
          decoration: new BoxDecoration(
              color: Colors.grey[200],
              borderRadius: new BorderRadius.circular(12.0)),
          //一个横向布局,包括查询图标和文本输入框,用于获取用户查询对象
          child: new Row(
            children: <Widget>[
              new Icon(
                Icons.search,
                color: Colors.grey[600],
              ),
              //Flexible控件会填满剩余空间
              new Flexible(
                child: new TextField(
                  controller: _searchTextController,
                  autofocus: true,
                  decoration: InputDecoration.collapsed(
                      hintText: '活动名称、联系人',
                      hintStyle: Theme.of(context).textTheme.body1.copyWith(
                          color: Colors.grey[600],
                          fontStyle: FontStyle.italic)),
                  //因为我们要实现实时查询,所以这里使用onChanged回调函数,实时根据输入的内容进行查询并反馈
                  onChanged: (value) {},
                ),
              ),
              //这里是输入框后面的全部清空按钮实现
              new InkWell(
                child: Container(
                    decoration: new BoxDecoration(shape: BoxShape.circle,color: Colors.grey[400]),
                    child: new Icon(
                      Icons.clear,
                      color: Colors.white,
                      size: 19.0,
                    )),
                onTap: () {},
              )
            ],
          ),
        ),
        //最后就是取消按钮,点击后退出查询页面,返回首页
        actions: <Widget>[
          new FlatButton(
            padding: const EdgeInsets.all(0.0),
            child: new Text(
              '取消',
              style: Theme.of(context)
                  .textTheme
                  .body1
                  .copyWith(color: Color(0xff813066)),
            ),
            onPressed: () {
              Navigator.of(context).pop();
            },
          )
        ],
      ),
      body: new Text('查错了不关我的事')
    );
  }
}

以上代码应该不难看懂,我基本在每个实现控件的地方做了注释。
顶部标题栏是放的一个文本输入框,用于获取用户查询对象。后面跟一个取消退出查询页面的按钮。
底下内容区域用于显示查询的结果。
运行下程序,点击首页标题栏的查询框就会跳转到如下页面了:


image.png

三、实时查询逻辑和结果展示

说到底查询搜索肯定是和数据库打交道嘛,这一步的实现思路也是三步走:

1、获取用户输入
2、根据用户输入进行数据库查询并返回查询结果
3、将返回的结果按照你想展现的形式展示出来

1、用户输入

因为是实现实时查询,所以使用textField的onChanged(value){}回调函数就会实时获取用户的输入,在这个回调函数里我们放上查询数据库的方法就会实时查询数据库了。

2、查询数据库并返回结果

查询数据库方法如下:
首先去数据库类文件中添加根据用户输入查询的方法
data_base.dart

//根据内容查询数据库name和Contact字段
  Future searchDiy(String value) async {
    Database database = await db;
    var result = database
        .rawQuery("select * from $tableName where $columnName like '%$value%' or $columnContact like '%$value%'");
    return result;
  }

然后回到diy_search.dart添加查询数据库并获取数据的方法

  
 //首先我们定义一个查询结果的数组,用于存放查询结果
  List<DiyProject> _searchResult = [];
  //通过输入的字符串查询数据库
  _searchDiy(String value) async {
    var result = await widget.db.searchDiy(value);
    //每次查询前先清空查询结果的数组,不然查询结果的数组里会包含以前查询的结果哦
    _searchResult.clear();
    //当返回内容部位空说明查询到内容,我们通过forEach函数将内容添加到结果数组里。
    setState(
      () {
        if (result != null) {
          result.forEach((value) {
            _searchResult.add(DiyProject.fromMap(value));
          });
        }
      },
    );
  }

将该方法添加到TextField的onChanged回调函数中

                 onChanged: (value) {
                    if (value != '') {
                      _searchDiy(value);
                    }
                    setState(() {
                      if (value == '') {
                        _searchResult.clear();
                      }
                    });
                  },

注意事项:

以上代码中要判断value值不为''的时候才进行查询,如果是''那么还要清空查询结果,因为否则你会发现当你输入内容查询后,当你取消输入的文字,底下会显示所有的数据库数据,这是因为这是onChanged实时监听回调函数,你在取消文字的时候系统也在一直监听查询,当你全部取消那么系统还是会继续查询数据库,所以会查询出所有结果并展现在底下,所以这个地方大家要注意。

3、将返回的结果按照你想展现的形式展示出来

这一步实际上就是按照你想展示的形式进行自定义布局了,你想展现详细卡片信息也好,显示一行行的listTile也好都可以,这一部分内容就自己实现一下吧,我这边是我显示的实现方式,我还是贴一下:

//如果查询结果是0那么就显示_text = 没有查询到任何结果,否则就显示我们定义好的布局
body: _searchResult.length == 0
          ? new Center(
              child: new Text(_text, style: new TextStyle(fontSize: 18.0)),
            )
          : _searchResultListView(_searchResult),
//每条查询结果的显示布局
  Widget _searchResultListTile(DiyProject diyProject) {
    return new Container(
      color: Colors.white,
      padding: const EdgeInsets.all(8.0),
      height: 100.0,
      child: new Row(
        children: <Widget>[
          new ClipRRect(
            borderRadius: BorderRadius.circular(4.0),
            child: new Image.file(
              File(diyProject.imagePath),
              fit: BoxFit.cover,
              width: 100.0,
            ),
          ),
          new Expanded(
            flex: 2,
            child: new Container(
              margin: const EdgeInsets.only(left: 10.0),
              padding: const EdgeInsets.all(4.0),
              child: new Column(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  new Text(
                    diyProject.name,
                    style: new TextStyle(fontWeight: FontWeight.bold),
                  ),
                  new Text(diyProject.date),
                  new Row(
                    children: <Widget>[
                      new Text('${diyProject.nums.toString()}元'),
                      new SizedBox(
                        width: 10.0,
                      ),
                      new Text('${diyProject.singlePrcie.toString()}份')
                    ],
                  ),
                ],
              ),
            ),
          ),
          new Container(
            padding: const EdgeInsets.all(4.0),
            child: new Column(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              crossAxisAlignment: CrossAxisAlignment.end,
              children: <Widget>[
                new Text(diyProject.place),
                new Text(diyProject.contact),
              ],
            ),
          )
        ],
      ),
    );
  }
  //整个查询结果
  Widget _searchResultListView(List list) {
    return new Container(
      color: Colors.grey[200],
      child: new ListView.builder(
        itemCount: _searchResult.length * 2,
        itemBuilder: (BuildContext context, index) {
          if (index.isOdd) {
            return new Divider(
              height: 0.0,
            );
          }
          index = index ~/ 2;
          return _searchResultListTile(list[index]);
        },
      ),
    );
  }

这部分实现实际上见仁见智,按照自己想要展现的内容来做就好,也是对之前我们说的控件布局的又一次练手。
整体效果就是开头的演示动画那样的。

这个是我自己实现的查询功能,没有直接使用仓库里的查询插件,所以各方面肯定不完善,后面我会去看看仓库里的查询插件,学习他们的好的地方,不断提升啦。

最后附上项目源码地址:https://gitee.com/xusujun33/activity_record_jia.git

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容