版权声明:本文为本人原创文章,未经本人允许不得转载。
hello,大家好,继续我们这个项目的更新,今天我们的任务非常明确:实现实时查询功能
开始之前,我们先看效果演示,苹果的录屏转GIF导致清晰度有点差,大家将就一下。
废话少说,我们直接开始。
首先我对首页UI做了调整,看下图:
原来设计的时候是左下角有搜索按钮,现在我在标题栏的顶部添加了查询入口,是自己组合实现的一个查询插件,为什么这么做是因为考虑后面底部要加导航栏,所以暂时先把搜索栏放在上面,也是参考了目前很多主流的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('查错了不关我的事')
);
}
}
以上代码应该不难看懂,我基本在每个实现控件的地方做了注释。
顶部标题栏是放的一个文本输入框,用于获取用户查询对象。后面跟一个取消退出查询页面的按钮。
底下内容区域用于显示查询的结果。
运行下程序,点击首页标题栏的查询框就会跳转到如下页面了:
三、实时查询逻辑和结果展示
说到底查询搜索肯定是和数据库打交道嘛,这一步的实现思路也是三步走:
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