Flutter - 实现 listView 搜索功能
Posted
技术标签:
【中文标题】Flutter - 实现 listView 搜索功能【英文标题】:Flutter - Implementing a listView search feature 【发布时间】:2019-02-13 15:48:29 【问题描述】:我一直在尝试在我的应用中实现一个搜索栏,以便将选定的 listView
项目置于列表顶部。该列表包含相当多的项目,大约 1700 项,因此添加搜索栏是必不可少的。我希望 listView 搜索框出现在顶部 appBar
右侧的 search
图标中。下面是当前视图的图片供参考。
当您单击搜索 iconButton
时,搜索字段应替换 appBar
中的标题。对于用户来说,这是针对加密 listView
的显而易见的,因为我将在搜索视图中添加一个提示来标识这一点。
我没有包括我的所有代码,因为这对于堆栈问题来说会很麻烦,但下面是我的 home_page.dart
文件,而我的底部加密 listView
的其余类可以在 @987654322 找到@。
这就是我的“home_page.dart”的样子;
import 'package:cryptick/cryptoData/crypto_data.dart';
import 'package:cryptick/cryptoData/trending_data.dart';
import 'package:cryptick/modules/crypto_presenter.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'background.dart';
//FOLLOWING DART CODE COPYRIGHT OF 2017 - 2018 SQUARED SOFTWARE LONDON
class HomePage extends StatefulWidget
@override
_HomePageState createState() => new _HomePageState();
class ServerStatusScreen extends StatelessWidget
@override
Widget build(BuildContext context)
return new Scaffold(
appBar: new AppBar(
iconTheme: new IconThemeData(color: Colors.white),
centerTitle: true,
backgroundColor: Colors.black,
title: new Text(
'API Server Status',
textAlign: TextAlign.center,
style: new TextStyle(
color: Colors.white, fontSize: 27.5, fontFamily: 'Kanit'),
),
),
body: new Center(
child: new Column(
children: [
new Divider(color: Colors.white),
new Text(
'News Feed: ',
textAlign: TextAlign.center,
style: new TextStyle(
color: Colors.black,
fontSize: 27.5,
fontFamily: 'Kanit',
),
),
new Divider(),
new Text(
'Crypto Feed: ',
textAlign: TextAlign.center,
style: new TextStyle(
color: Colors.black,
fontSize: 27.5,
fontFamily: 'Kanit',
),
),
new Divider(),
new Wrap(
alignment: WrapAlignment.center,
children: <Widget>[
new Chip(
backgroundColor: Colors.black,
label: new Text(
'© 2017-2018 Squared Software',
style: new TextStyle(
fontSize: 15.0,
fontFamily: 'Poppins',
color: Colors.white,
),
),
),
],
),
],
),
),
);
class MoreInfoScreen extends StatelessWidget
@override
Widget build(BuildContext context)
final ThemeData themeData = Theme.of(context);
final TextStyle aboutTextStyle = themeData.textTheme.body2;
final TextStyle linkStyle =
themeData.textTheme.body2.copyWith(color: themeData.accentColor);
return new Scaffold(
appBar: new AppBar(
iconTheme: new IconThemeData(color: Colors.white),
centerTitle: true,
backgroundColor: Colors.black,
title: new Text(
'More Info',
textAlign: TextAlign.center,
style: new TextStyle(
color: Colors.white, fontSize: 27.5, fontFamily: 'Kanit'),
),
),
body: new Center(
child: new Column(
children: [
new Divider(color: Colors.white),
new ListTile(
title: new Text('Squared Software',
style: new TextStyle(
fontWeight: FontWeight.w500,
fontFamily: 'Poppins',
)
),
leading: new CircleAvatar(
radius: 30.0,
backgroundImage: new AssetImage(
'images/sqinterlock.png'
)
)
),
new Divider(),
new Text('Where do we get our information?',
style: new TextStyle(
color: Colors.black,
fontFamily: 'Poppins',
fontSize: 16.5,
)
),
new Divider(color: Colors.white),
new Text(
"News Feed: bit.ly/2MFpzHX",
style: new TextStyle(
fontFamily: 'Poppins',
fontSize: 16.5,
),
),
new Divider(color: Colors.white),
new Text(
"Crypto Feed: bit.ly/2iIdJht",
style: new TextStyle(
fontFamily: 'Poppins',
fontSize: 16.5,
),
),
new Divider(color: Colors.white),
new Wrap(
alignment: WrapAlignment.center,
children: <Widget>[
new Chip(
backgroundColor: Colors.black,
label: new Text(
'© 2017-2018 Squared Software',
style: new TextStyle(
fontSize: 15.0,
fontFamily: 'Poppins',
color: Colors.white,
),
),
),
],
),
],
),
),
);
class _HomePageState extends State<HomePage> implements CryptoListViewContract
CryptoListPresenter _presenter;
List<Crypto> _currencies;
bool _isLoading;
final List<MaterialColor> _colors = [Colors.blue, Colors.indigo, Colors.red];
_HomePageState()
_presenter = new CryptoListPresenter(this);
@override
void onLoadTrendingComplete(Trending trending)
// TODO:
articlesMap = trending.articles;
for (Map articleMap in articlesMap)
articles.add(Articles.fromMap(articleMap));
if (mounted) setState(() );
@override
void onLoadTrendingError()
// TODO:
List articlesMap = [];
List<Articles> articles = [];
@override
void initState()
super.initState();
_isLoading = true;
_presenter.loadCurrencies();
_presenter.loadTrending();
@override
Widget build(BuildContext context)
return new Scaffold(
appBar: new AppBar(
title: new Text(
"Cryp - Tick Exchange",
style: new TextStyle(
color: Colors.white,
fontFamily: 'Poppins',
fontSize: 22.5,
),
),
iconTheme: new IconThemeData(color: Colors.white),
backgroundColor: const Color(0xFF273A48),
elevation: 0.0,
centerTitle: true,
),
drawer: new Drawer(
child: new ListView(padding: EdgeInsets.zero, children: <Widget>[
new DrawerHeader(
child: new CircleAvatar(
child: new Image.asset('images/ctavatar.png'),
),
decoration: new BoxDecoration(
color: Colors.black,
),
),
new MaterialButton(
child: new Text(
'Server Status',
textAlign: TextAlign.center,
style: new TextStyle(fontSize: 27.5, fontFamily: 'Kanit'),
),
onPressed: ()
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ServerStatusScreen()),
);
),
new Divider(),
new MaterialButton(
child: new Text(
'More Info',
textAlign: TextAlign.center,
style: new TextStyle(fontSize: 27.5, fontFamily: 'Kanit'),
),
onPressed: ()
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MoreInfoScreen()),
);
),
new Divider(),
new Wrap(
alignment: WrapAlignment.center,
children: <Widget>[
new Chip(
backgroundColor: Colors.black,
label: new Text(
'v0.0.1',
style: new TextStyle(
fontSize: 15.0,
fontFamily: 'Poppins',
color: Colors.white,
),
),
),
],
),
]),
),
body: _isLoading
? new Center(child: new CupertinoActivityIndicator(radius: 15.0))
: _allWidget());
Widget _allWidget()
final _width = MediaQuery.of(context).size.width;
final _height = MediaQuery.of(context).size.height;
//CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED
final headerList = new ListView.builder(
itemBuilder: (context, index)
EdgeInsets padding = index == 0
? const EdgeInsets.only(
left: 20.0, right: 10.0, top: 4.0, bottom: 30.0)
: const EdgeInsets.only(
left: 10.0, right: 10.0, top: 4.0, bottom: 30.0);
return new Padding(
padding: padding,
child: new InkWell(
onTap: ()
print('@url');
,
child: new Container(
decoration: new BoxDecoration(
borderRadius: new BorderRadius.circular(10.0),
color: const Color(0xFF273A48),
boxShadow: [
new BoxShadow(
color: Colors.black.withAlpha(70),
offset: const Offset(3.0, 10.0),
blurRadius: 15.0)
],
image: new DecorationImage(
image: new NetworkImage(articles[index].urlToImage),
fit: BoxFit.fitHeight,
),
),
height: 200.0,
width: 275.0,
child: new Stack(
children: <Widget>[
new Align(
alignment: Alignment.bottomCenter,
child: new Container(
padding: new EdgeInsets.only(left: 10.0),
decoration: new BoxDecoration(
color: const Color(0xFF273A48),
borderRadius: new BorderRadius.only(
bottomLeft: new Radius.circular(10.0),
bottomRight: new Radius.circular(10.0)),
),
height: 50.0,
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Expanded(child: new Text(
articles[index].title,
overflow: TextOverflow.ellipsis,
maxLines: 2,
style: new TextStyle(
color: Colors.white,
fontFamily: 'Poppins',
),
),
),
],
)
),
)
],
),
),
),
);
,
scrollDirection: Axis.horizontal,
itemCount: articles.length,
);
final body = new Scaffold(
backgroundColor: Colors.transparent,
body: new Container(
child: new Stack(
children: <Widget>[
new Padding(
padding: new EdgeInsets.only(top: 10.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new Align(
alignment: Alignment.centerLeft,
child: new Padding(
padding: new EdgeInsets.only(
left: 10.0,
),
child: new Text(
"Trending News",
style: new TextStyle(
letterSpacing: 0.8,
fontFamily: 'Kanit',
fontSize: 17.5,
color: Colors.white,
),
)),
),
new Container(
height: 300.0, width: _width, child: headerList),
new Expanded(child: ListView.builder(
itemBuilder: (BuildContext context, int index)
final int i = index;
final Crypto currency = _currencies[i];
final MaterialColor color = _colors[i % _colors.length];
return new ListTile(
title: new Column(
children: <Widget>[
new Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Container(
height: 72.0,
width: 72.0,
decoration: new BoxDecoration(
color: Colors.white,
boxShadow: [
new BoxShadow(
color: Colors.black.withAlpha(80),
offset: const Offset(2.0, 2.0),
blurRadius: 15.0)
],
borderRadius: new BorderRadius.all(
new Radius.circular(35.0)),
image: new DecorationImage(
image: new ExactAssetImage(
"cryptoiconsBlack/" +
currency.symbol.toLowerCase() +
"@2x.png",
),
fit: BoxFit.cover,
)),
),
new SizedBox(
width: 8.0,
),
new Expanded(
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text(
currency.name,
style: new TextStyle(
fontSize: 15.0,
fontFamily: 'Poppins',
color: Colors.black87,
fontWeight: FontWeight.bold),
),
_getSubtitleText(currency.price_usd,
currency.percent_change_1h),
],
)),
],
),
new Divider(),
],
),
);
))
],
),
),
],
),
),
);
return new Container(
decoration: new BoxDecoration(
color: const Color(0xFF273A48),
),
child: new Stack(
children: <Widget>[
new CustomPaint(
size: new Size(_width, _height),
painter: new Background(),
),
body,
],
),
);
// CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED CRYPTO FEED
Widget _getSubtitleText(String priceUSD, String percentageChange)
TextSpan priceTextWidget = new TextSpan(
text: "\$$priceUSD\n",
style: new TextStyle(
color: Colors.black,
fontSize: 14.0,
));
String percentageChangeText = "1 hour: $percentageChange%";
TextSpan percentageChangeTextWidget;
if (double.parse(percentageChange) > 0)
percentageChangeTextWidget = new TextSpan(
text: percentageChangeText,
style: new TextStyle(
color: Colors.green,
fontFamily: 'PoppinsMediumItalic',
));
else
percentageChangeTextWidget = new TextSpan(
text: percentageChangeText,
style: new TextStyle(
color: Colors.red,
fontFamily: 'PoppinsMediumItalic',
));
return new RichText(
text: new TextSpan(
children: [priceTextWidget, percentageChangeTextWidget]));
//Works with cryptoListViewContract implimentation in _MyHomePageState
@override
void onLoadCryptoComplete(List<Crypto> items)
// TODO: implement onLoadCryptoComplete
setState(()
_currencies = items;
_isLoading = false;
);
@override
void onLoadCryptoError()
// TODO: implement onLoadCryptoError
谢谢你的帮助,杰克
【问题讨论】:
你考虑过AppBar.actions吗?如果是这样,期望的效果是什么?也就是说,你想要一个弹出对话框来搜索吗?拥有一个 TextField 而不是一个 Icon 会很酷,您只需在隐藏 AppBar 的其他部分的同时扩展它。 是的,我将在此之后编辑问题以反映此评论,但无论如何,当您单击搜索iconButton
时,搜索字段可以替换 appBar
? 中的标题。对于用户来说,这是针对加密 listView
的显而易见的,因为我将在搜索视图中添加一个提示来标识这一点。谢谢
使用这个插件并随心所欲地使用它:- ***.com/a/61727414/10563627
你可以使用 Flutter 库来实现与 Youtube/Instagram 相同的设计:github.com/pkmangukiya/flutter_search_view_pk
【参考方案1】:
根据您想要的最终体验,可能有很多方法可以实现这一点。一个简单的解决方案是创建activeSearch
状态来切换“搜索应用栏”和“普通应用栏”
这是正常的应用栏:
return AppBar(
title: Text("My App"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () => setState(() => activeSearch = true),
),
],
);
这里是搜索应用栏:
return AppBar(
leading: Icon(Icons.search),
title: TextField(
decoration: InputDecoration(
hintText: "here's a hint",
),
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () => setState(() => activeSearch = false),
)
],
);
注意:如果您不想在搜索处于活动状态时出现前导图标,您可能需要禁用 drawer
和 back button
图标的默认行为:
automaticallyImplyLeading: false
完整示例:
class MyApp extends StatefulWidget
@override
_MyAppState createState() => _MyAppState();
class _MyAppState extends State<MyApp>
bool activeSearch;
@override
void initState()
super.initState();
activeSearch = false;
@override
Widget build(BuildContext context)
return Scaffold(
appBar: _appBar(),
drawer: _drawer(),
);
PreferredSizeWidget _appBar()
if (activeSearch)
return AppBar(
leading: Icon(Icons.search),
title: TextField(
decoration: InputDecoration(
hintText: "here's a hint",
),
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () => setState(() => activeSearch = false),
)
],
);
else
return AppBar(
title: Text("My App"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () => setState(() => activeSearch = true),
),
],
);
Widget _drawer()
return Container();
更新:这里是处理结果的提示
return AppBar(
...
title: TextField(
onChanged: _search,
),
);
而 _search 会是什么:
List<MyResultObject> _results;
void _search(String queryString)
// do some searching and sorting
// then call setState() with the results
// and then in your ListView you can read from results
// (handle empty, default case as well in view)
setState(()
_results = ...
);
List<Widget> _resultWidgets()
if (_results.isEmpty) return _defaultWidgets();
_results.map((r) => _buildRowWidget(s)).toList();
【讨论】:
这看起来很棒,现在就实现它 您是否可以提供或提示我实际对项目进行排序所需的代码?搜索栏非常好,感谢您到目前为止所做的一切。 @Jake,我在最后添加了快速更新,可能会有所帮助 太棒了,感谢您的帮助。我会看一下@Dinesh Balasubramanian 提供的问题并尽快参考。 嘿@Ashton,刚刚注意到当我在搜索字段中键入一个值时弹出屏幕上的ios键盘时,我得到了严重的像素溢出。对此我能做些什么吗?谢谢【参考方案2】:您能否在this 答案中引用一个简单的搜索视图。在该示例中,当用户键入时,列表将被过滤。
【讨论】:
我会先实现来自 Ashton 的问题,然后再添加。感谢您提供更多信息:)以上是关于Flutter - 实现 listView 搜索功能的主要内容,如果未能解决你的问题,请参考以下文章
Flutter:无法将 ListView 添加到示例应用程序
Flutter 错误:'UnmodifiableUint8ListView' 受到限制,无法扩展或实现
Flutter:在 http.get 请求之后构建 ListView
在 Flutter 应用程序的 ListView.builder 中添加滚动