Flutter之底部自定义弹框➕CupertinoPicker选择器做二级选择器+界面更新

本文使用showModalBottomSheet作为底部弹框,使用CupertinoPicker充当二级选择器,点击选择城市,弹出弹框选择站点,点击确定,界面更新显示站点名称,效果图如下。

Flutter之底部自定义弹框➕CupertinoPicker选择器做二级选择器+界面更新_第1张图片
siteCity为返回数据后的站点数组,包含一级站点一级二级站点。
site为一级站点数组
newCitys为二级站点数组
index用于保存二级站点的索引值
city用于界面显示站点信息值。
Flutter之底部自定义弹框➕CupertinoPicker选择器做二级选择器+界面更新_第2张图片

代码实现

1.通过Dio网络请求数据,解析数据给siteCity,将siteCity数组下的每一项中的citys数组给newCitys。遍历siteCity,获取一级站点名给site。

void siteCityReturn() {
    HttpRequest()
        .request("https://mock.apifox.cn/m1/2853584-0-default/zhandian",
            method: DioMethod.get)
        .then((value) {
      print(value);
      Area area = Area.fromJson(value);
      setState(() {
        siteCity = area.result;
        newCitys = siteCity[0]["citys"];
        for (dynamic a in siteCity) {
          site.add(a["name"]);
        }
      });
    });
  }

HttpRequest代码如下

import 'package:dio/dio.dart';


/// 请求方法
enum DioMethod {
  get,
  post,
  put,
  delete,
  patch,
  head,
}

class HttpRequest {
  /// 单例模式
  static HttpRequest? _instance;
  factory HttpRequest() => _instance ?? HttpRequest._internal();
  static HttpRequest? get instance => _instance ?? HttpRequest._internal();

  /// 连接超时时间
  static const int connectTimeout = 60 * 1000;

  /// 响应超时时间
  static const int receiveTimeout = 60 * 1000;

  /// Dio实例
  static Dio _dio = Dio();

  /// 初始化
  HttpRequest._internal() {
    // 初始化基本选项
    BaseOptions options = BaseOptions(
        baseUrl: 'http://127.0.0.1:7001/app/',
        connectTimeout: Duration(milliseconds: connectTimeout),
        receiveTimeout: Duration(milliseconds: receiveTimeout));
    _instance = this;
    // 初始化dio
    _dio = Dio(options);
    // 添加拦截器
    _dio.interceptors.add(InterceptorsWrapper(
        onRequest: _onRequest, onResponse: _onResponse, onError: _onError));
  }

  /// 请求拦截器
  void _onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    // 对非open的接口的请求参数全部增加userId
    if (!options.path.contains("open")) {
      options.queryParameters["userId"] = "xxx";
    }
    // 头部添加token
    options.headers["token"] = "xxx";
    // 更多业务需求
    handler.next(options);
    // super.onRequest(options, handler);
  }

  /// 相应拦截器
  void _onResponse(
      Response response, ResponseInterceptorHandler handler) async {
    // 请求成功是对数据做基本处理
    if (response.statusCode == 200) {
      // ....
    } else {
      // ....
    }
    if (response.requestOptions.baseUrl.contains("???????")) {
      // 对某些单独的url返回数据做特殊处理
    }
    handler.next(response);
  }

  /// 错误处理
  void _onError(DioError error, ErrorInterceptorHandler handler) {
    handler.next(error);
  }

  /// 请求类
  Future<T> request<T>(
    String path, {
    DioMethod method = DioMethod.get,
    Map<String, dynamic>? params,
    data,
    CancelToken? cancelToken,
    Options? options,
    ProgressCallback? onSendProgress,
    ProgressCallback? onReceiveProgress,
  }) async {
    const _methodValues = {
      DioMethod.get: 'get',
      DioMethod.post: 'post',
      DioMethod.put: 'put',
      DioMethod.delete: 'delete',
      DioMethod.patch: 'patch',
      DioMethod.head: 'head'
    };
    options ??= Options(method: _methodValues[method]);
    try {
      Response response;
      response = await _dio.request(path,
          data: data,
          queryParameters: params,
          cancelToken: cancelToken,
          options: options,
          onSendProgress: onSendProgress,
          onReceiveProgress: onReceiveProgress);
      return response.data;
    } on DioError catch (e) {
      rethrow;
    }
  }

  /// 开启日志打印
  /// 需要打印日志的接口在接口请求前 DioUtil.instance?.openLog();
  void openLog() {
    _dio.interceptors
        .add(LogInterceptor(responseHeader: false, responseBody: true));
  }
}

2.给选择城市容器框添加点击手势GestureDetector。onTap添加asyncFlutter之底部自定义弹框➕CupertinoPicker选择器做二级选择器+界面更新_第3张图片

3.声明showModalBottomSheet,由于后期更改第一个选择器后的选项后,需要更改第二个选择器的数据,如果弹框的builder的return是Builder,通过setstate()是无法更新弹框中的数据的,原因是弹框是另外一个路由,通过setstate只能更改当前界面数据。此时为登录界面。解决办法为弹框的builder的return返回StatefulBuilder。

  var result = await showModalBottomSheet(
                                context: context,
                                backgroundColor: Color.fromRGBO(1, 1, 1, 0),
                                builder: (context) {
                                  return StatefulBuilder(builder:
                                      (BuildContext context,
                                          StateSetter setState) {
                                    return Container();}
                                    ); });

4.添加选择器1,itemExtent为每一项的高度,现在的选择器样式为有阴影的胶囊状,如需效果图中的横线,需要在selectionOverlay设置样式。_selectionOverlayWidget样式如下:

// 中间分割线
Widget _selectionOverlayWidget() {
  return Padding(
    padding: const EdgeInsets.only(left: 0, right: 0),
    child: Column(
      children: [
        const Divider(
          height: 1,
          color: Colors.black,
        ),
        Expanded(child: Container()),
        const Divider(
          height: 1,
          color: Colors.black,
        ),
      ],
    ),
  );
}

onSelectedItemChanged当选中数据改变时,修改二级选择器中的数组数据。children为选择器中每一项的内容,通过遍历数组返回Text居中显示。

CupertinoPicker(
            itemExtent: 60,
            selectionOverlay:
            _selectionOverlayWidget(),
            onSelectedItemChanged: (position) {
            print(position);
            setState(() {
               newCitys = siteCity[position]["citys"];}); 
             },
            children: [
              for (var site in site)
                 Center(Child:Text(site)),]
             )

5.添加选择器2,如选择器1配置,index为全局变量,保存选择器二的索引,用于点击确定按钮后更改主界面的数据。

CupertinoPicker(
           itemExtent: 60,
           selectionOverlay:
           _selectionOverlayWidget(),
           onSelectedItemChanged:
             (position) {
              index = position; },
           children: [
            for (var city in newCitys)
                   Center(child:Text(city)), 
            ]
         )

6.点击确定按钮改变主界面数据。首先定义onChanged方法,city为字符类型,用于text显示当前选择的站点名字。

  void onChanged(val) {
    setState(() {
      city = val;
    });
  }

在弹框中的确定容器添加手势,onTap方法中调用onChanged方法,将二级选择器中选中的站点名字作为参数,重绘UI,Navigator.pop关闭弹框。

GestureDetector(
       onTap: () {
           onChanged(
               newCitys[index]);
               Navigator.pop(context);
            },

弹框部分完整代码

GestureDetector(
                          onTap: () async {
                            index = 0;
                            newCitys = siteCity[0]["citys"];
                             
                            var result = await showModalBottomSheet(
                                context: context,
                                backgroundColor: Color.fromRGBO(1, 1, 1, 0),
                                builder: (context) {
                                  return StatefulBuilder(builder:
                                      (BuildContext context,
                                          StateSetter setState) {
                                    return Container(
                                      padding: const EdgeInsets.only(
                                          left: 15, right: 15),
                                      height: 350,
                                      decoration: const BoxDecoration(
                                          color: Colors.white,
                                          borderRadius: BorderRadius.only(
                                              topLeft: Radius.circular(15),
                                              topRight: Radius.circular(15))),
                                      child: Column(
                                        children: [
                                          const SizedBox(
                                            height: 12,
                                          ),
                                          const Text(
                                            "选择站点",
                                            style: TextStyle(
                                                fontSize: 16,
                                                fontWeight: FontWeight.w400,
                                                color: Colors.black),
                                          ),
                                          SizedBox(
                                            height: 180,
                                            child: Row(
                                              children: [
                                                Expanded(
                                                    flex: 1,
                                                    child: CupertinoPicker(
                                                        itemExtent: 60,
                                                        selectionOverlay:
                                                            _selectionOverlayWidget(),
                                                        onSelectedItemChanged:
                                                            (position) {
                                                          print(position);
                                                          setState(() {
                                                            newCitys = siteCity[
                                                                    position]
                                                                ["citys"];
                                                          });
                                                        },
                                                        children: [
                                                          for (var site in site)
                                                            Center(
                                                                child:
                                                                    Text(site)),
                                                        ])),
                                                Expanded(
                                                    flex: 1,
                                                    child: CupertinoPicker(
                                                        itemExtent: 60,
                                                        selectionOverlay:
                                                            _selectionOverlayWidget(),
                                                        onSelectedItemChanged:
                                                            (position) {
                                                          index = position;
                                                        },
                                                        children: [
                                                          for (var city
                                                              in newCitys)
                                                            Center(
                                                                child:
                                                                    Text(city)),
                                                        ])),
                                              ],
                                            ),
                                          ),
                                          const SizedBox(
                                            height: 32,
                                          ),
                                          Row(
                                            children: [
                                              Expanded(
                                                  flex: 1,
                                                  child: GestureDetector(
                                                    onTap: () {
                                                      Navigator.pop(context);
                                                    },
                                                    child: Container(
                                                      alignment:
                                                          Alignment.center,
                                                      height: 40,
                                                      decoration: BoxDecoration(
                                                          border: Border.all(
                                                              color:
                                                                  Colors.black,
                                                              width: 1),
                                                          borderRadius:
                                                              const BorderRadius
                                                                      .all(
                                                                  Radius
                                                                      .circular(
                                                                          24))),
                                                      child: const Text(
                                                        "取消",
                                                        style: TextStyle(
                                                            color: Colors.black,
                                                            fontSize: 17,
                                                            fontWeight:
                                                                FontWeight
                                                                    .w500),
                                                      ),
                                                    ),
                                                  )),
                                              const SizedBox(
                                                width: 15,
                                              ),
                                              Expanded(
                                                  flex: 1,
                                                  child: GestureDetector(
                                                    onTap: () {
                                                      onChanged(
                                                          newCitys[index]);
                                                      Navigator.pop(context);
                                                    },
                                                    child: Container(
                                                      alignment:
                                                          Alignment.center,
                                                      height: 40,
                                                      decoration: const BoxDecoration(
                                                          color: Colors.black,
                                                          borderRadius:
                                                              BorderRadius.all(
                                                                  Radius
                                                                      .circular(
                                                                          24))),
                                                      child: const Text(
                                                        "确定",
                                                        style: TextStyle(
                                                            color: Colors.white,
                                                            fontSize: 17,
                                                            fontWeight:
                                                                FontWeight
                                                                    .w500),
                                                      ),
                                                    ),
                                                  )),
                                            ],
                                          )
                                        ],
                                      ),
                                    );
                                  });
                                });
                          },
                          child: Container(
                            height: 40,
                            padding: const EdgeInsets.fromLTRB(12, 0, 12, 0),
                            decoration: const BoxDecoration(
                                color: Color.fromRGBO(247, 249, 250, 1),
                                borderRadius:
                                    BorderRadius.all(Radius.circular(4))),
                            child: Row(
                              crossAxisAlignment: CrossAxisAlignment.center,
                              children: [
                                Text(
                                  city,
                                  style: const TextStyle(
                                      color: Color.fromRGBO(26, 26, 26, 1),
                                      fontSize: 14,
                                      fontWeight: FontWeight.w400),
                                ),
                                const Spacer(),
                                const Icon(Icons.chevron_right)
                              ],
                            ),
                          ),
                        ),

你可能感兴趣的:(flutter)