Flutter搜索界面插件flutter_searchbox_ui的使用
Flutter搜索界面插件flutter_searchbox_ui的使用
flutter_searchbox_ui
提供了用于Elasticsearch和Appbase.io的UI组件,支持多种类型的查询。
目前,我们支持 [RangeInput]
和 [ReactiveGoogleMap]
组件。
安装
要安装 flutter_searchbox_ui
,请按以下步骤操作:
-
添加依赖
在你的项目
pubspec.yaml
文件中添加以下依赖:dependencies: flutter_searchbox: ^3.1.0 searchbase: ^3.4.0-beta flutter_searchbox_ui: 1.0.16-alpha
-
获取依赖
你可以在命令行中运行以下命令来获取这些包:
$ flutter pub get
-
使用
[ReactiveGoogleMap]
如果你要使用
[ReactiveGoogleMap]
,请参阅此处的安装指南: google_maps_flutter
基本用法
[ReactiveGoogleMap]
示例与 [RangeInput]
import 'package:flutter/material.dart';
import 'package:searchbase/searchbase.dart';
import 'package:flutter/services.dart';
import 'package:flutter/foundation.dart';
import 'dart:async';
import 'dart:ui';
import 'package:flutter_searchbox/flutter_searchbox.dart';
import 'package:flutter_searchbox_ui/flutter_searchbox_ui.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:dart_geohash/dart_geohash.dart';
import 'package:google_maps_cluster_manager/google_maps_cluster_manager.dart';
import 'dart:io';
void main() {
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.presentError(details);
if (kReleaseMode) exit(1);
};
runApp(FlutterSearchBoxUIApp());
}
class FlutterSearchBoxUIApp extends StatelessWidget {
// 避免在构建方法中创建 searchbase 实例
// 以保持热重载时的状态
final searchbaseInstance = SearchBase(
'earthquakes',
'https://appbase-demo-ansible-abxiydt-arc.searchbase.io',
'a03a1cb71321:75b6603d-9456-4a5a-af6b-a487b309eb61',
appbaseConfig: AppbaseSettings(
recordAnalytics: true,
// 使用唯一的用户ID来个性化最近的搜索
userId: 'jon@appbase.io'));
FlutterSearchBoxUIApp({Key? key}) : super(key: key);
// 构建聚合图标的方法
Future<BitmapDescriptor> _getMarkerBitmap(int size, {String? text}) async {
if (kIsWeb) size = (size / 2).floor();
final PictureRecorder pictureRecorder = PictureRecorder();
final Canvas canvas = Canvas(pictureRecorder);
final Paint paint1 = Paint()..color = Colors.orange;
final Paint paint2 = Paint()..color = Colors.white;
canvas.drawCircle(Offset(size / 2, size / 2), size / 2.0, paint1);
canvas.drawCircle(Offset(size / 2, size / 2), size / 2.2, paint2);
canvas.drawCircle(Offset(size / 2, size / 2), size / 2.8, paint1);
if (text != null) {
TextPainter painter = TextPainter(textDirection: TextDirection.ltr);
painter.text = TextSpan(
text: text,
style: TextStyle(
fontSize: size / 3,
color: Colors.white,
fontWeight: FontWeight.normal),
);
painter.layout();
painter.paint(
canvas,
Offset(size / 2 - painter.width / 2, size / 2 - painter.height / 2),
);
}
final img = await pictureRecorder.endRecording().toImage(size, size);
final data = await img.toByteData(format: ImageByteFormat.png) as ByteData;
return BitmapDescriptor.fromBytes(data.buffer.asUint8List());
}
[@override](/user/override)
Widget build(BuildContext context) {
// SearchBaseProvider 应该包裹你的 MaterialApp 或 WidgetsApp。这将确保所有路由都能访问到存储。
return SearchBaseProvider(
// 将 searchbase 实例传递给 SearchBaseProvider。任何祖先 SearchWidgetConnector 小部件都将找到并使用此值作为 SearchController。
searchbase: searchbaseInstance,
child: MaterialApp(
title: "SearchBox Demo",
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Scaffold(
appBar: AppBar(
// 一个过滤器,根据震级更新地震数据
title: RangeInput(
id: 'range-selector',
beforeValueChange: (dynamic value) async {
if (value is Map<String, dynamic>) {
final Map<String, dynamic> mapValue = value;
if (mapValue['start'] == 0 && mapValue['end'] == null) {
return Future.error(value);
}
}
print('beforeValueChange $value');
return value;
},
buildTitle: () {
return const Text(
"Filter by Magnitude",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
color: Colors.amber,
),
);
},
buildRangeLabel: () {
return const Text(
"to",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
color: Colors.blue,
),
);
},
dataField: 'magnitude',
range: const RangeType(
start: ['other', 4, 5, 6, 7],
end: 10,
),
rangeLabels: RangeLabelsType(
start: (value) {
return value == 'other'
? 'Custom Other'
: (value == 'no_limit' ? 'No Limit' : '$value');
},
end: (value) {
return value == 'other'
? 'Custom Other'
: (value == 'no_limit' ? 'No Limit' : '$value');
},
),
validateRange: (start, end) {
if (start < end) {
return true;
}
return false;
},
buildErrorMessage: (start, end) {
return Text(
'Custom error $start > $end',
style: const TextStyle(
fontSize: 15.0,
color: Colors.yellowAccent,
),
);
},
inputStyle: const TextStyle(
fontSize: 18,
height: 1,
color: Colors.deepPurple,
),
dropdownStyle: const TextStyle(
fontSize: 18,
height: 1,
color: Colors.deepPurpleAccent,
),
customContainer: (showError, childWidget) {
return Container(
padding: const EdgeInsets.only(left: 6.0, right: 1.0),
height: 50,
decoration: BoxDecoration(
border: Border.all(
color: showError ? Colors.orangeAccent : Colors.black,
width: 1.5,
),
borderRadius: BorderRadius.circular(3),
),
child: childWidget,
);
},
closeIcon: () {
return const Text(
"X",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
color: Colors.blueAccent,
),
);
},
dropdownIcon: (showError) {
return Icon(
Icons.arrow_drop_down,
color: showError ? Colors.red : Colors.black,
);
},
),
toolbarHeight: 120,
backgroundColor: Colors.white.withOpacity(.9),
),
bottomNavigationBar: Padding(
padding: EdgeInsets.all(20.0),
// SelectedFilters: 一个跟踪所有活动过滤器的小部件
child: SelectedFilters(
subscribeTo: const ['range-selector'],
filterLabel: (id, value) {
if (id == 'range-selector') {
return 'Range: $value';
}
return '$id: $value';
},
showClearAll: true,
clearAllLabel: "Vanish All",
onClearAll: () {
// 这里可以执行一些操作
print('Clear all called');
},
onClear: (id, value) {
// 这里可以执行一些操作
print('Filter $id with value: ${value.toString()} cleared');
},
resetToDefault: true,
defaultValues: const {
"range-selector": {'start': 5, 'end': 10}
},
// hideDefaultValues: false,
// 取消注释以下属性以渲染自定义的 SelectedFilters 小部件 UI
// buildFilters: (options) {
// List<Widget> widgets = [];
// options.selectedValues.forEach((id, filterValue) {
// widgets.add(
// Chip(
// label: Text(
// ' $id --- ${options.getValueAsString(filterValue)}'),
// onDeleted: () {
// options.clearValue(id);
// },
// ),
// );
// });
// return Wrap(
// spacing: 16.0,
// crossAxisAlignment: WrapCrossAlignment.start,
// // 相邻芯片之间的间距
// children: widgets,
// );
// },
),
),
body: ReactiveGoogleMap(
id: 'map-widget',
// 当震级发生变化时更新标记
react: const {
"and": "range-selector",
},
// 初始地图中心
initialCameraPosition: const CameraPosition(
target: LatLng(37.42796133580664, -122.085749655962),
zoom: 4,
),
// 启用标记聚类
showMarkerClusters: true,
// 构建聚类标记
// 我们在这里根据集群中的项目数量显示 [Marker] 图标和文本。
buildClusterMarker: (Cluster<Place> cluster) async {
return Marker(
markerId: MarkerId(cluster.getId()),
position: cluster.location,
icon: await _getMarkerBitmap(cluster.isMultiple ? 125 : 75,
text: cluster.isMultiple
? cluster.count.toString()
: cluster.items.first.source?["magnitude"]),
);
},
// 当 `showMarkerClusters` 设置为 `false` 时构建标记
// buildMarker: (Place place) {
// return Marker(
// markerId: MarkerId(place.id), position: place.position);
// },
// 数据库字段映射到地理点。
dataField: 'location',
// Elasticsearch hits 的大小
// 我们将 `size` 设置为零,因为我们正在使用聚合来构建标记。
size: 0,
// Elasticsearch 聚合的大小
aggregationSize: 50,
// 获取初始结果
triggerQueryOnInit: false,
// 当地图边界变化时更新标记
searchAsMove: true,
// [可选] 使用默认查询来使用 Elasticsearch `geohash_grid` 查询。
defaultQuery: (SearchController controller) {
return {
"aggregations": {
"location": {
"geohash_grid": {"field": "location", "precision": 3},
"aggs": {
"top_earthquakes": {
"top_hits": {
"_source": {
"includes": ["magnitude"]
},
"size": 1
}
}
}
},
}
};
},
// [可选] 从聚合数据计算标记
calculateMarkers: (SearchController controller) {
List<Place> places = [];
for (var bucket
in controller.aggregationData?.raw?["buckets"] ?? []) {
try {
var locationDecode = GeoHash(bucket["key"]);
var source = bucket["top_earthquakes"]?["hits"]?["hits"]?[0]
?["_source"];
places.add(
Place(
id: bucket["key"],
position: LatLng(locationDecode.latitude(),
locationDecode.longitude()),
source: source),
);
} catch (e) {}
}
return places;
},
),
),
),
);
}
}
更多关于Flutter搜索界面插件flutter_searchbox_ui的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter搜索界面插件flutter_searchbox_ui的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何在Flutter项目中使用flutter_searchbox_ui
插件来创建搜索界面的示例代码。这个插件提供了一个简洁的搜索界面,并且易于集成。
首先,确保你已经在pubspec.yaml
文件中添加了flutter_searchbox_ui
依赖:
dependencies:
flutter:
sdk: flutter
flutter_searchbox_ui: ^latest_version # 请替换为最新版本号
然后,运行flutter pub get
来安装依赖。
接下来,在你的Flutter应用中,你可以按照以下步骤使用flutter_searchbox_ui
插件:
import 'package:flutter/material.dart';
import 'package:flutter_searchbox_ui/flutter_searchbox_ui.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter SearchBox UI Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SearchScreen(),
);
}
}
class SearchScreen extends StatefulWidget {
@override
_SearchScreenState createState() => _SearchScreenState();
}
class _SearchScreenState extends State<SearchScreen> {
final List<String> items = List.generate(100, (index) => "Item $index");
String? searchQuery;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SearchBox UI Demo'),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: SearchBoxUI(
searchQuery: searchQuery,
onChanged: (query) {
setState(() {
searchQuery = query;
});
},
onSearchPressed: (query) {
// 处理搜索逻辑
print("Searching for: $query");
},
clearIconPressed: () {
setState(() {
searchQuery = null;
});
},
searchResults: searchQuery == null || searchQuery!.isEmpty
? items
: items
.where((item) =>
item.toLowerCase().contains(searchQuery!.toLowerCase()))
.toList(),
itemBuilder: (context, index) {
final item = searchResults![index];
return ListTile(
title: Text(item),
);
},
noResultsFoundWidget: Center(
child: Text('No Results Found'),
),
),
),
);
}
}
在这个示例中,我们做了以下事情:
- 引入依赖:在文件顶部导入了
flutter_searchbox_ui
包。 - 创建主应用:在
MyApp
类中,设置了应用的主题和主页。 - 创建搜索界面:在
SearchScreen
类中,创建了一个包含搜索框的界面。 - 处理搜索逻辑:使用
SearchBoxUI
组件,并实现了onChanged
、onSearchPressed
和clearIconPressed
回调来处理用户输入、搜索操作和清除操作。 - 显示搜索结果:根据用户的搜索查询,过滤并显示搜索结果。如果没有找到结果,显示一个“No Results Found”的提示。
请注意,这个示例假设你已经有一个包含100个项目的列表,并且根据用户的搜索查询动态地过滤这些项目。你可以根据自己的需求调整这个逻辑。
此外,flutter_searchbox_ui
插件可能提供了更多的配置选项和自定义功能,你可以查阅其官方文档以获取更多信息。