当前位置: 首页 > news >正文

Flutter项目之底部搜索功能实现

目录:

    • 1、实现效果图
    • 2、工具栏顶部搜索
    • 3、工具栏顶部搜索跳转页

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,
        ),
      ]),
    );
  }
}

待完善。。。。。。
https://www.bilibili.com/video/BV1fAPveaExf?spm_id_from=333.788.videopod.episodes&vd_source=1e6f6d5c0620893f1c319968434126de&p=36

相关文章:

  • 【NumPy】1. 前言安装
  • Javaweb后端AOP记录操作日志
  • (041)05-01-自考数据结构(20331)树与二叉树大题总结
  • spring IOC AOP
  • 基于 RK3588 的 YOLO 多线程推理多级硬件加速引擎框架设计(代码框架和实现细节)
  • 【商城实战(96)】打造商城监控利器Prometheus与Grafana
  • AI预测3D新模型百十个定位预测+胆码预测+杀和尾+杀和值2025年3月31日第38弹
  • K个一组翻转链表--囊括半数链表题的思想
  • Android学习之计算器app(java + 详细注释 + 源码)
  • el-switch的before-change的使用方法
  • 解决wsl2下CentOS 7 的 yum 仓库无法连接问题
  • Vue舞台剧
  • AWS Lambda:无服务器架构如何重塑云计算,解锁企业敏捷开发与成本革命
  • 目标检测YOLO实战应用案例100讲-基于孤立森林算法的高光谱遥感图像异常目标检测(续)
  • Go语言从零构建SQL数据库引擎 - 开篇
  • 【C++游戏引擎开发】《线性代数》(4):矩阵求逆的LU分解实现(SIMD实现计算加速)
  • 某地81栋危房自动化监测试点项目
  • 深度学习图像分类数据集—五种艺术画风格分类
  • 开源AI大模型赋能的S2B2C商业生态重构研究——基于智能名片系统的体验认知与KOC背书机制
  • 单调自增的数字 斐波那契数列 爬楼梯
  • 邢台网站建设报价/线上推广策划方案
  • 做网站过程/临沂seo推广
  • 微网站有什么用/珠海网站seo
  • 东莞营销商城网站建设/开发app需要多少资金
  • 国内永久免费服务器/如何优化标题关键词
  • 中国做的比较好的网站/武汉seo优化排名公司