Flutter - 使用来自 SQFlite 的数据即时过滤(搜索)ListView
Flutter - instant filter (search) ListView with data from SQFlite
我有一个 Listview
和一个用作搜索栏的 Textfield
(我还应该提到,每个都在不同的 class 中)
我正在尝试在用户输入时立即过滤列表视图
我在正确的方向上取得了进展,但问题是我正在寻找与此示例类似的东西 mList.contains(userSearch)
我目前拥有的并不完全满足要求,它会搜索,但只有当用户停止输入时,它才会检测文本字段上的实时变化。
从SQFlite数据库获取数据:
//getting full list
Future<List<Word>> getAllWords() async {
final db = await database;
var response = await db.query(TABLE_WORDS);
List<Word> list = response.map((c) => Word.fromMap(c)).toList();
return list;
}
//getting search results
Future<List<Word>> searchResults(String userSearch) async {
final db = await database;
var response = await db.query(TABLE_WORDS, where: '$COL_ENGLISH_WORD = ? OR $COL_GERMAN_WORD = ?', whereArgs: [userSearch, userSearch]);
List<Word> list = response.map((c) => Word.fromMap(c)).toList();
return list;
}
现在显示列表和搜索栏的页面:
String userSearchInput = "";
TextEditingController _searchInputController = TextEditingController();
class FullList extends StatefulWidget {
@override
_FullListState createState() => _FullListState();
}
class _FullListState extends State<FullList> {
@override
void initState() {
search(String userInput){
setState(() {
userInput = _searchInputController.text;
if(userInput.isEmpty){
return;
}else{
userSearchInput = userInput;
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
primary: true,
title: InkWell(
splashColor: gradientStart,
child: TextField(
autofocus: false,
enableInteractiveSelection: false,
controller: _searchInputController,
onChanged: search,
decoration: InputDecoration(hintText: "Search"),
),
),
),
body: Container(
height: MediaQuery.of(context).size.height,
child: SafeArea(
child: ListPage(),
),
),
);
}
}
//The class that has the listview
class ListPage extends StatefulWidget {
@override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
DatabaseHelper databaseHelper;
@override
void initState() {
databaseHelper = DatabaseHelper.databaseHelper;
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder<List<Word>>(
future: _searchInputController.text.isEmpty ? databaseHelper.getAllWords() : databaseHelper.searchResults(userSearchInput),
builder: (BuildContext context, AsyncSnapshot<List<Word>> snapshot){
if(snapshot.hasData){
return ListView.builder(
physics: const AlwaysScrollableScrollPhysics (),
shrinkWrap: true,
reverse: false,
controller: _scrollController,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {/*take action*/},
child: AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 300),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: listChild(snapshot.data[index].eng, snapshot.data[index].ger),
),
),
),
);
},
);
}
return Center(
child: CircularProgressIndicator()
);
}
),
);
}
}
问题:如何更改此设置以便能够在用户键入时立即过滤列表?
我认为问题出在您的 initState() 上。你应该将它从 initState 移开。另外,你可以这样解决;
首先,定义2个List,
List<Word> list = List<Word>();
List<Word> filteredList = List<Word>();
然后当您从快照中获取数据时,将您获取的列表分配给您定义的列表。像这样;
if(snapshot.hasData){
if(!doItJustOnce){ //You should define a bool like (bool doItJustOnce = false;) on your state.
list = snapshot.data;
filteredList = list;
doItJustOnce = !doItJustOnce; //this line helps to do just once.
}
return ListView.builder(
...
然后,添加您的列表
itemCount: filteredList.length,
还有这个;
FadeInAnimation(
child: listChild(filteredList[index].eng, filteredList[index].nor),
)
最后,你应该像这样制作一个空白;
void _filterList(value) {
setState(() {
filteredList = list.where((text) => text.fileName.toLowerCase().contains(value.toLowerCase())).toList();
});
}
并添加您的 TextField 的 onChanged;
onChanged: (value) {
_filterList(value);
},
如果您重置列表,只需写下这些行;
setState(() {
filteredList = list;
});
你可以试试你的代码;
import 'package:flutter/material.dart';
class FullList extends StatefulWidget {
@override
_FullListState createState() => _FullListState();
}
class _FullListState extends State<FullList> {
String userSearchInput = "";
TextEditingController _searchInputController = TextEditingController();
DatabaseHelper databaseHelper;
List<Word> list = List<Word>();
List<Word> filteredList = List<Word>();
bool doItJustOnce = false;
void _filterList(value) {
setState(() {
filteredList = list
.where((text) => text.eng.toLowerCase().contains(value.toLowerCase()))
.toList(); // I don't understand your Word list.
});
}
@override
void initState() {
databaseHelper = DatabaseHelper.databaseHelper;
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
primary: true,
title: InkWell(
splashColor: gradientStart,
child: TextField(
autofocus: false,
enableInteractiveSelection: false,
controller: _searchInputController,
onChanged: (value) {
_filterList(value);
},
decoration: InputDecoration(hintText: "Search"),
),
),
),
body: Container(
height: MediaQuery.of(context).size.height,
child: SafeArea(
child: Container(
child: FutureBuilder<List<Word>>(
future: databaseHelper.getAllWords(),
builder: (BuildContext context, AsyncSnapshot<List<Word>> snapshot) {
if (snapshot.hasData) {
if (!doItJustOnce) {
//You should define a bool like (bool doItJustOnce = false;) on your state.
list = snapshot.data;
filteredList = list;
doItJustOnce = !doItJustOnce; //this line helps to do just once.
}
return ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
shrinkWrap: true,
reverse: false,
controller: _scrollController,
itemCount: filteredList.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
/*take action*/
},
child: AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 300),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: listChild(filteredList[index].eng, filteredList[index].ger),
),
),
),
);
},
);
}
return Center(child: CircularProgressIndicator());
}),
),
),
),
);
}
}
我有一个 Listview
和一个用作搜索栏的 Textfield
(我还应该提到,每个都在不同的 class 中)
我正在尝试在用户输入时立即过滤列表视图
我在正确的方向上取得了进展,但问题是我正在寻找与此示例类似的东西 mList.contains(userSearch)
我目前拥有的并不完全满足要求,它会搜索,但只有当用户停止输入时,它才会检测文本字段上的实时变化。
从SQFlite数据库获取数据:
//getting full list
Future<List<Word>> getAllWords() async {
final db = await database;
var response = await db.query(TABLE_WORDS);
List<Word> list = response.map((c) => Word.fromMap(c)).toList();
return list;
}
//getting search results
Future<List<Word>> searchResults(String userSearch) async {
final db = await database;
var response = await db.query(TABLE_WORDS, where: '$COL_ENGLISH_WORD = ? OR $COL_GERMAN_WORD = ?', whereArgs: [userSearch, userSearch]);
List<Word> list = response.map((c) => Word.fromMap(c)).toList();
return list;
}
现在显示列表和搜索栏的页面:
String userSearchInput = "";
TextEditingController _searchInputController = TextEditingController();
class FullList extends StatefulWidget {
@override
_FullListState createState() => _FullListState();
}
class _FullListState extends State<FullList> {
@override
void initState() {
search(String userInput){
setState(() {
userInput = _searchInputController.text;
if(userInput.isEmpty){
return;
}else{
userSearchInput = userInput;
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
primary: true,
title: InkWell(
splashColor: gradientStart,
child: TextField(
autofocus: false,
enableInteractiveSelection: false,
controller: _searchInputController,
onChanged: search,
decoration: InputDecoration(hintText: "Search"),
),
),
),
body: Container(
height: MediaQuery.of(context).size.height,
child: SafeArea(
child: ListPage(),
),
),
);
}
}
//The class that has the listview
class ListPage extends StatefulWidget {
@override
_ListPageState createState() => _ListPageState();
}
class _ListPageState extends State<ListPage> {
DatabaseHelper databaseHelper;
@override
void initState() {
databaseHelper = DatabaseHelper.databaseHelper;
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder<List<Word>>(
future: _searchInputController.text.isEmpty ? databaseHelper.getAllWords() : databaseHelper.searchResults(userSearchInput),
builder: (BuildContext context, AsyncSnapshot<List<Word>> snapshot){
if(snapshot.hasData){
return ListView.builder(
physics: const AlwaysScrollableScrollPhysics (),
shrinkWrap: true,
reverse: false,
controller: _scrollController,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {/*take action*/},
child: AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 300),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: listChild(snapshot.data[index].eng, snapshot.data[index].ger),
),
),
),
);
},
);
}
return Center(
child: CircularProgressIndicator()
);
}
),
);
}
}
问题:如何更改此设置以便能够在用户键入时立即过滤列表?
我认为问题出在您的 initState() 上。你应该将它从 initState 移开。另外,你可以这样解决; 首先,定义2个List,
List<Word> list = List<Word>();
List<Word> filteredList = List<Word>();
然后当您从快照中获取数据时,将您获取的列表分配给您定义的列表。像这样;
if(snapshot.hasData){
if(!doItJustOnce){ //You should define a bool like (bool doItJustOnce = false;) on your state.
list = snapshot.data;
filteredList = list;
doItJustOnce = !doItJustOnce; //this line helps to do just once.
}
return ListView.builder(
...
然后,添加您的列表
itemCount: filteredList.length,
还有这个;
FadeInAnimation(
child: listChild(filteredList[index].eng, filteredList[index].nor),
)
最后,你应该像这样制作一个空白;
void _filterList(value) {
setState(() {
filteredList = list.where((text) => text.fileName.toLowerCase().contains(value.toLowerCase())).toList();
});
}
并添加您的 TextField 的 onChanged;
onChanged: (value) {
_filterList(value);
},
如果您重置列表,只需写下这些行;
setState(() {
filteredList = list;
});
你可以试试你的代码;
import 'package:flutter/material.dart';
class FullList extends StatefulWidget {
@override
_FullListState createState() => _FullListState();
}
class _FullListState extends State<FullList> {
String userSearchInput = "";
TextEditingController _searchInputController = TextEditingController();
DatabaseHelper databaseHelper;
List<Word> list = List<Word>();
List<Word> filteredList = List<Word>();
bool doItJustOnce = false;
void _filterList(value) {
setState(() {
filteredList = list
.where((text) => text.eng.toLowerCase().contains(value.toLowerCase()))
.toList(); // I don't understand your Word list.
});
}
@override
void initState() {
databaseHelper = DatabaseHelper.databaseHelper;
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
primary: true,
title: InkWell(
splashColor: gradientStart,
child: TextField(
autofocus: false,
enableInteractiveSelection: false,
controller: _searchInputController,
onChanged: (value) {
_filterList(value);
},
decoration: InputDecoration(hintText: "Search"),
),
),
),
body: Container(
height: MediaQuery.of(context).size.height,
child: SafeArea(
child: Container(
child: FutureBuilder<List<Word>>(
future: databaseHelper.getAllWords(),
builder: (BuildContext context, AsyncSnapshot<List<Word>> snapshot) {
if (snapshot.hasData) {
if (!doItJustOnce) {
//You should define a bool like (bool doItJustOnce = false;) on your state.
list = snapshot.data;
filteredList = list;
doItJustOnce = !doItJustOnce; //this line helps to do just once.
}
return ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
shrinkWrap: true,
reverse: false,
controller: _scrollController,
itemCount: filteredList.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
/*take action*/
},
child: AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 300),
child: SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: listChild(filteredList[index].eng, filteredList[index].ger),
),
),
),
);
},
);
}
return Center(child: CircularProgressIndicator());
}),
),
),
),
);
}
}