1、实现效果图
2、工具栏顶部搜索
import 'dart:convert';
import 'package:city_pickers/city_pickers.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_haoke/config.dart';
import 'package:flutter_haoke/pages/home/tab_index/index_recommond_item.dart';
import 'package:flutter_haoke/scopoed_model/city.dart';
import 'package:flutter_haoke/utils/common_toast.dart';
import 'package:flutter_haoke/utils/model/general_type.dart';
import 'package:flutter_haoke/utils/scopoed_mode_helper.dart';
import 'package:flutter_haoke/utils/store.dart';
class SearchBar extends StatefulWidget {
final bool? showLocation; //是否显示位置
final void Function()? goBackCallback; //回退
final String? inputValue; //搜索框值
final String? defaultInputValue; //默认显示值
final void Function()? onCancel; //取消按钮
final bool? showMap; //是否显示地图按钮
final void Function()? onSearch; //点击搜索框触发
// final Function? onSearch;
final ValueChanged<String>? onSearchSubmit; //点击按键回车触发
const SearchBar(
{Key? key,
this.showLocation,
this.goBackCallback,
this.inputValue = '',
this.defaultInputValue = '请输入搜索词',
this.onCancel,
this.showMap,
this.onSearch,
this.onSearchSubmit})
: super(key: key);
_SearchBarState createState() => _SearchBarState();
}
class _SearchBarState extends State<SearchBar> {
String _searchWord = '';
late TextEditingController _controller;
late FocusNode _focus;
_onClean() {
_controller.clear();
setState(() {
_searchWord = '';
});
}
_onChangeLocation() async {
//打开第三方的选择页面
var resultCity = await CityPickers.showCitiesSelector(
context: context, theme: ThemeData(primarySwatch: Colors.green));
//选择之后返回选择的数据
String? cityName = resultCity!.cityName;
if (cityName == null) return;
//检测选中的城市是否在 四个城市中
//查找数组中是否有这个名字 有就返回这个城市model 没有就返回空的类
var city = Config.availableCitys
.firstWhere((city) => cityName.startsWith(city.name), orElse: () {
CommontToast.showToast("该城市暂未开通!");
return GeneralType('', "");
});
//保存选中的城市
_saveCity(city);
}
_saveCity(GeneralType city) async {
if (city.name == null ||city.name =="") return;
//保存到全局
ScopoedModelHelper.getModel<CityModel>(context).city = city;
//保存到本地
var store = await Store.getInstance();
//转化成json字符串格式
var cityString = json.encode(city.toJson());
store.setString(StoreKeys.city, cityString);
}
void initState() {
_focus = FocusNode();
_controller = TextEditingController(text: widget.inputValue);
super.initState();
}
Widget build(BuildContext context) {
//从全局里拿到city
var city = ScopoedModelHelper.getModel<CityModel>(context).city;
if (city.name == null || city.name == "") {
city = Config.availableCitys.first;
//存储下此时的city
_saveCity(city);
}
return Container(
child: Row(
children: [
if (widget.showLocation != null)
Padding(
padding: EdgeInsets.only(right: 10),
child: GestureDetector(
onTap: () {
_onChangeLocation();
},
child: Row(
children: [
Icon(
Icons.room,
color: Colors.green,
size: 15,
),
Text(
city.name,
style: TextStyle(color: Colors.black, fontSize: 14),
)
],
),
),
),
if (widget.goBackCallback != null)
Padding(
padding: EdgeInsets.only(right: 10),
child: GestureDetector(
onTap: widget.goBackCallback,
child: Icon(
Icons.chevron_left,
color: Colors.black,
)),
),
Expanded(
child: Container(
height: 34,
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(17)),
margin: EdgeInsets.only(right: 10),
child: TextField(
focusNode: _focus,
controller: _controller,
style: TextStyle(fontSize: 14),
onChanged: (value) {
setState(() {
_searchWord = value;
});
},
onTap: () {
//判断没有使用搜索功能则失去焦点
if (null == widget.onSearchSubmit) {
_focus.unfocus();
}
if(widget.onSearch !=null)widget.onSearch!();
},
onSubmitted: widget.onSearchSubmit,
textInputAction: TextInputAction.search,
decoration: InputDecoration(
hintText: widget.defaultInputValue,
hintStyle: TextStyle(color: Colors.black, fontSize: 14),
contentPadding: EdgeInsets.only(top: 2, left: -10),
border: InputBorder.none,
icon: Padding(
padding: EdgeInsets.only(top: 4, left: 8),
child: Icon(
Icons.search,
size: 18,
color: Colors.grey,
),
),
suffixIcon: GestureDetector(
onTap: _onClean,
child: Icon(
Icons.clear,
size: 18,
color:
_searchWord == '' ? Colors.grey.shade100 : Colors.grey,
),
)),
),
)),
if (widget.onCancel != null)
Padding(
padding: EdgeInsets.only(right: 10),
child: GestureDetector(
onTap: widget.onCancel,
child: Text(
"取消",
style: TextStyle(color: Colors.black, fontSize: 14),
),
),
),
if (widget.showMap != null)
Image.asset("static/icons/widget_search_bar_map.png")
],
));
}
}
3、工具栏底部搜索
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_haoke/pages/home/tab_search/datalist.dart';
import 'package:flutter_haoke/pages/home/tab_search/filter_bar/data.dart';
import 'package:flutter_haoke/pages/home/tab_search/filter_bar/item.dart';
import 'package:flutter_haoke/scopoed_model/room_filter.dart';
import 'package:flutter_haoke/utils/common_picker/index.dart';
import 'package:flutter_haoke/utils/dio_http.dart';
import 'package:flutter_haoke/utils/model/general_type.dart';
import 'package:flutter_haoke/utils/scopoed_mode_helper.dart';
//上次选择的城市ID 用于切换不同数据请求
var lastCityId;
class FilterBar extends StatefulWidget {
final ValueChanged<FilterBarResult>? onChange;
const FilterBar({Key? key, this.onChange}) : super(key: key);
_FilterBarState createState() => _FilterBarState();
}
class _FilterBarState extends State<FilterBar> {
List<GeneralType> areaList = [];
List<GeneralType> priceList = [];
List<GeneralType> rentTypeList = [];
List<GeneralType> roomTypeList = [];
List<GeneralType> orientedList = [];
List<GeneralType> floorList = [];
bool isAreaActive = false;
bool isRentTypeActive = false;
bool isPriceActive = false;
bool isFilterActive = false;
String areaId = "";
String rentTypeId = "";
String priceId = "";
List<String> moreIds = [];
//选择区域方法
_onChangeArea(context) {
setState(() {
isAreaActive = true;
});
var result = CommonPicker.showPicker(
context: context,
options: areaList.map((item) => item.name).toList(),
value: 0);
result!.then((index) {
setState(() {
areaId = areaList[index].id;
});
_onChange();
}).whenComplete(() {
setState(() {
//动作结束的时候调用
isAreaActive = false;
});
});
}
//选择租金方法
_onChangeRentType(context) {
setState(() {
isRentTypeActive = true;
});
var result = CommonPicker.showPicker(
context: context,
options: rentTypeList.map((item) => item.name).toList(),
value: 0);
result!.then((index) {
setState(() {
rentTypeId = rentTypeList[index].id;
});
_onChange();
}).whenComplete(() {
setState(() {
//动作结束的时候调用
isRentTypeActive = false;
});
});
}
//选择价格方法
_onChangePrice(context) {
setState(() {
isPriceActive = true;
});
var result = CommonPicker.showPicker(
context: context,
options: priceList.map((item) => item.name).toList(),
value: 0);
result!.then((index) {
setState(() {
priceId = priceList[index].id;
});
_onChange();
}).whenComplete(() {
setState(() {
//动作结束的时候调用
isPriceActive = false;
});
});
}
_onChangeFliter(context) {
Scaffold.of(context).openEndDrawer(); //打开右侧的抽屉
}
_onChange() {
//获取选中的数据
var selectIds = ScopoedModelHelper.getModel<FilterBarModel>(context)
.selectedList
.toList();
if (widget.onChange != null) {
widget.onChange!(FilterBarResult(
areaId: areaId,
priceId: priceId,
rentTypeId: rentTypeId,
moreIds: selectIds));
}
}
_getData() async {
//调用网络获取数据
var cityId = ScopoedModelHelper.getAreaId(context);
//给本次选择的数据赋值
lastCityId = cityId;
var url = "/houses/condition?id=$cityId";
var result = await DioHttp.of(context).get(url);
print(result);
if (!this.mounted) {
//判断本页面是否存在 如果已销毁 则不用进行以下赋值操作
return;
}
//接口不通 伪造数据赋值情况如下----------假数据-------
List<GeneralType> areaList = [
GeneralType('区域1', '11'),
GeneralType('区域2', '22'),
];
List<GeneralType> priceList = [
GeneralType('价格1', 'bb'),
GeneralType('价格2', 'aa'),
];
List<GeneralType> rentTypeList = [
GeneralType('出租类型1', 'bb'),
GeneralType('出租类型2', '22'),
];
List<GeneralType> roomTypeList = [
GeneralType('房屋类型1', '11'),
GeneralType('房屋类型2', '22'),
];
List<GeneralType> orientedList = [
GeneralType('方向1', '99'),
GeneralType('方向2', 'cc'),
];
List<GeneralType> floorList = [
GeneralType('楼层1', 'aa'),
GeneralType('楼层2', 'bb'),
];
setState(() {
this.areaList = areaList;
this.priceList = priceList;
this.rentTypeList = rentTypeList;
this.roomTypeList = roomTypeList;
this.orientedList = orientedList;
this.floorList = floorList;
});
//接口不通 伪造数据赋值情况如上----------假数据-------
Map<String, List<GeneralType>> datalist = Map<String, List<GeneralType>>();
datalist["roomTypeList"] = roomTypeList;
datalist["orientedList"] = orientedList;
datalist["floorList"] = floorList;
//传入所有要展示的数据
ScopoedModelHelper.getModel<FilterBarModel>(context).dataList = datalist;
}
void initState() {
//这个方法在这里调用只走一次
Timer.run(_getData);
super.initState();
}
void didChangeDependencies() {
//每次加载都会走这个方法
//判断上次的城市选择和本次是否相同
if (lastCityId != "" &&
lastCityId != ScopoedModelHelper.getAreaId(context)) {
//刷新数据
_getData();
}
_onChange();
super.didChangeDependencies();
}
Widget build(BuildContext context) {
return Container(
height: 41,
decoration: BoxDecoration(
border: Border(bottom: BorderSide(width: 1, color: Colors.grey))),
child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
Item(
title: "区域",
isActive: isAreaActive,
onTap: _onChangeArea,
),
Item(
title: "方式",
isActive: isRentTypeActive,
onTap: _onChangeRentType,
),
Item(
title: "租金",
isActive: isPriceActive,
onTap: _onChangePrice,
),
Item(
title: "筛选",
isActive: isFilterActive,
onTap: _onChangeFliter,
),
]),
);
}
}
3.1、主体结构
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_haoke/pages/home/tab_search/datalist.dart';
import 'package:flutter_haoke/pages/home/tab_search/filter_bar/data.dart';
import 'package:flutter_haoke/pages/home/tab_search/filter_bar/filter_drawer.dart';
import 'package:flutter_haoke/pages/home/tab_search/filter_bar/index.dart';
import 'package:flutter_haoke/scopoed_model/room_filter.dart';
import 'package:flutter_haoke/utils/dio_http.dart';
import 'package:flutter_haoke/utils/model/room_list_item_data.dart';
import 'package:flutter_haoke/utils/scopoed_mode_helper.dart';
import 'package:flutter_haoke/widget/room_list_item_widget.dart';
import 'package:flutter_haoke/widget/search_bar/index.dart';
class TabSearch extends StatefulWidget {
TabSearch({Key? key}) : super(key: key);
_TabSearchState createState() => _TabSearchState();
}
class _TabSearchState extends State<TabSearch> {
//存储网络获取的数据
List<RoomListItemData> list = [];
_onfilterBarChange(FilterBarResult data) async {
//获取所有选中的数据
var cityId =
Uri.decodeQueryComponent(ScopoedModelHelper.getAreaId(context));
var area = Uri.decodeQueryComponent(data.areaId!);
var mode = Uri.decodeQueryComponent(data.rentTypeId!);
var price = Uri.decodeQueryComponent(data.priceId!);
var more = Uri.decodeQueryComponent(data.moreIds!.join(","));
//组装URL参数
String url =
'/houses?cityId=$cityId&area=$area&mode=$mode&price=$price&more=$more';
var result = await DioHttp.of(context).get(url);
// 网络数据返回----真实数据----------
// var resultMap = json.decode(result.toString());
// List<RoomListItemData> datalist = resultMap["data"]["list"]
// .map((json) => RoomListItemData.fromJson(json))
// .toList();
// setState(() {
// list = datalist;
// });
// --------真实数据----------
//以上请求不通 --------模拟数据---------------
const List<RoomListItemData> datas = [
const RoomListItemData(
'0',
'朝阳门南大街 2室1厅 8300元',
"二室/114/东|北/朝阳门南大街",
"https://tva1.sinaimg.cn/large/006y8mN6ly1g6wtu9t1kxj30lo0c7796.jpg",
["近地铁", "集中供暖", "新上", "随时看房"],
1200),
const RoomListItemData(
'144',
'整租 · CBD总部公寓二期 临近国贸 精装修 随时拎包入住',
"一室/110/西/CBD总部公寓二期",
"https://tva1.sinaimg.cn/large/006y8mN6ly1g6wtu5s7gcj30lo0c7myq.jpg",
["近地铁", "随时看房"],
6000,
),
const RoomListItemData(
'155',
'朝阳门南大街 2室1厅 8300元',
"二室/114/东|北/朝阳门南大街",
"https://tva1.sinaimg.cn/large/006y8mN6ly1g6wtu5s7gcj30lo0c7myq.jpg",
["近地铁", "集中供暖", "新上", "随时看房"],
1200,
),
const RoomListItemData(
'166',
'整租 · CBD总部公寓二期 临近国贸 精装修 随时拎包入住',
"一室/110/西/CBD总部公寓二期",
"https://tva1.sinaimg.cn/large/006y8mN6ly1g6wtu9t1kxj30lo0c7796.jpg",
["近地铁", "随时看房"],
6000,
),
];
setState(() {
//刷新数据了-----
print('刷新数据了');
list = datas;
});
//--------模拟数据---------------
}
Widget build(BuildContext context) {
return Scaffold(
endDrawer: FilterDrawer(),
appBar: AppBar(
actions: [Container()],
elevation: 0,
title: SearchBar(
showLocation: true,
showMap: true,
onSearch: () {
Navigator.of(context).pushNamed("search");
},
),
backgroundColor: Colors.white,
),
body: Column(
children: [
Container(
height: 41,
child: FilterBar(
onChange: _onfilterBarChange,
),
),
Expanded(
child: ListView(
children: list.map((e) {
return RoomListItemWidget(e);
}).toList(),
),
)
],
),
);
}
}
3.2、item部分
import 'package:flutter/material.dart';
import 'package:flutter_haoke/config.dart';
import 'package:flutter_haoke/pages/home/tab_search/datalist.dart';
import 'package:flutter_haoke/utils/model/room_list_item_data.dart';
import 'package:flutter_haoke/widget/common_tag.dart';
class RoomListItemWidget extends StatelessWidget {
final RoomListItemData data;
const RoomListItemWidget(
this.data, {
Key? key,
}) : super(key: key);
Widget build(BuildContext context) {
//判断返回的图片地址是否包含http
var imageUri = data.imageUrl.startsWith('http')
? data.imageUrl
: Config.BaseUrl + data.imageUrl;
return GestureDetector(
onTap: () {
Navigator.of(context).pushNamed("roomDetail/${data.id}");
},
child: Container(
padding: EdgeInsets.only(left: 10, right: 10, bottom: 10),
child: Row(
children: [
Image.network(
imageUri,
width: 130,
height: 100,
),
Padding(padding: EdgeInsets.only(left: 10)),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
data.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
),
Text(
data.subTitle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Wrap(
//CommonTag(item)标签代码部分的使用
children: data.tags.map((item) => CommonTag(item)).toList(),
),
Text(
"${data.price}元/月",
style: TextStyle(
color: Colors.orange, fontWeight: FontWeight.w600),
)
],
))
],
),
),
);
}
}
3.3、标签tag部分
import 'package:flutter/material.dart';
class CommonTag extends StatelessWidget {
final String title; //这是外部传递过来的
final Color color; //自己的
final Color backgroundColor;
const CommonTag.origin(
this.title,
{Key? key,
this.color = Colors.black,
this.backgroundColor = Colors.grey})
: super(key: key); //注意外部和自己的写法
// 工厂函数
factory CommonTag(String title) {
switch (title) {
case '近地铁':
return CommonTag.origin(
title, //title子不变,其它值都改变
color: Colors.red,
backgroundColor: Colors.red.shade100,
);
case '集中供暖':
return CommonTag.origin(
title, //title子不变,其它值都改变
color: Colors.blue,
backgroundColor: Colors.blue.withOpacity(.5),
);
case '新上':
return CommonTag.origin(
title, //title子不变,其它值都改变
color: Colors.green,
backgroundColor: Colors.green.shade100,
);
case '随时看房':
return CommonTag.origin(
title, //title子不变,其它值都改变
color: Colors.orange,
backgroundColor: Colors.orange.shade100,
);
default:
return CommonTag.origin( title);
}
}
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(right: 4.0),
padding: EdgeInsets.only(left: 4.0, right: 4.0, top: 4.0, bottom: 2.0),
decoration: BoxDecoration(
color: backgroundColor, //工厂里自定义的背景
borderRadius: BorderRadius.circular(8.0)),
child: Text(
title, //工厂里自定义的文字
style: TextStyle(fontSize: 10.0, color: color //工厂里自定义的字体颜色
),
),
);
}
}