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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容