Flutter CupertinoPicker组件的二次封装

这篇博客介绍了如何针对Flutter的CupertinoPicker组件进行二次封装,以实现单位和值分开显示的效果。作者提供了一个名为SinglePickerWidget的自定义组件,通过Positioned widget定位单位,实现了在滑动时单位跟随的效果。博客包含了组件的实现思路、代码示例以及使用方法,便于其他开发者参考和应用。
摘要由CSDN通过智能技术生成

Flutter CupertinoPicker组件的二次封装

前言

今年大部分时间都放在了Flutter开发上,开发中自己也封装了些组件,最近有时间可以整理一下。分享给大家同时也再次整理当时的思路。

SinglePickerWidget

SinglePickerWidget是我封装的组件之一,主要是为了实现UI设计的picker效果,效果图如下,需要单位和值分开:
UI设计

正常的Flutter CupertinoPicker组件是没办法实现的:
CupertinoPicker组件

所以对CupertinoPicker组件进行了二次封装。

功能实现思路

首先说下我的实现思路,是把单位用Position组件包裹,然后进行定位到选中的一行位置。这样滑动单位的区域也会滑动SinglePickerWidget组件。

代码

由于是为了实现UI设计需求,所以组件没有做的很灵活,具体使用时还需要更改部分代码。

import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

///
/// Author: chengzan
/// Date: 2020-6-8
/// Describe: 单个选择picker
///

class SinglePickerWidget extends StatefulWidget {
  final List<Map> values;
  final value;
  final double itemHeight;
  final double height;
  final double width;
  final String unit;
  final Function onChanged;
  final Color backgroundColor;

  const SinglePickerWidget(
      {Key key,
      @required this.values,
      @required this.value,
      @required this.onChanged,
      this.unit,
      this.itemHeight = 37.5,
      this.backgroundColor = const Color(0xffffffff),
      this.height = 150.0,
      this.width = 150.0})
      : super(key: key);
  @override
  _SinglePickerWidgetState createState() => _SinglePickerWidgetState();
}

class _SinglePickerWidgetState extends State<SinglePickerWidget> {
  int _selectedColorIndex = 0;
  FixedExtentScrollController scrollController;
  var values;
  var value;

  //设置防抖周期为300毫秒
  Duration durationTime = Duration(milliseconds: 300);
  Timer timer;

  @override
  void initState() {
    super.initState();
    values = widget.values;
    value = widget.value;
    getDefaultValue();
    scrollController =
        FixedExtentScrollController(initialItem: _selectedColorIndex);
  }

  @override
  void dispose() {
    super.dispose();
    scrollController.dispose();
    timer?.cancel();
  }

  // 获取默认选择值
  getDefaultValue() {
    // 查找要选择的默认值
    for (var i = 0; i < values.length; i++) {
      if (values[i]["value"] == value) {
        setState(() {
          _selectedColorIndex = i;
        });
        break;
      }
    }
  }

  // 触发值改变
  void _changed(index) {
    timer?.cancel();
    timer = new Timer(durationTime, () {
      // 触发回调函数
      widget.onChanged(values[index]["value"]);
    });
  }

  Widget _buildColorPicker(BuildContext context) {
    return Container(
      height: widget.height,
      color: Colors.white,
      child: Stack(
        alignment: Alignment.center,
        children: [
          widget.unit != null
              ? Positioned(
                  top: widget.height / 2 - (widget.itemHeight / 2),
                  left: widget.width / 2 + 18.0,
                  child: Container(
                    alignment: Alignment.center,
                    height: widget.itemHeight,
                    child: Text(
                      widget.unit,
                      style: TextStyle(
                        color: Color(0xff333333),
                        fontSize: 16.0,
                        height: 1.5,
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                  ),
                )
              : Offstage(
                  offstage: true,
                ),
          CupertinoPicker(
            magnification: 1.0, // 整体放大率
            scrollController:
                scrollController, // 用于读取和控制当前项的FixedxtentScrollController
            itemExtent: widget.itemHeight, // 所有子节点 统一高度
            useMagnifier: true, // 是否使用放大效果
            backgroundColor: Colors.transparent,
            onSelectedItemChanged: (int index) {
              // 当正中间选项改变时的回调
              if (mounted) {
                print('index--------------$index');
                _changed(index);
              }
            },
            children: List<Widget>.generate(values.length, (int index) {
              return Container(
                alignment: Alignment.center,
                height: widget.itemHeight,
                child: Text(
                  values[index]["label"],
                  style: TextStyle(
                    color: Color(0xff333333),
                    fontSize: 21.0,
                    height: 1.2,
                    fontWeight: FontWeight.w500,
                  ),
                ),
              );
            }),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: widget.height,
      width: widget.width,
      child: CupertinoPageScaffold(
        child: Container(
          child: ListView(
            padding: EdgeInsets.all(0),
            children: <Widget>[
              _buildColorPicker(context),
            ],
          ),
        ),
      ),
    );
  }
}

使用实例

// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Map> values = [
    {"label": "1", "value": 1},
    {"label": "2", "value": 2},
    {"label": "3", "value": 3},
    {"label": "4", "value": 4},
    {"label": "5", "value": 5}
  ];

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Container(
      constraints: BoxConstraints(maxWidth: 160),
      alignment: Alignment.centerLeft,
      child: SinglePickerWidget(
        values: values,
        value: 2,
        width: 150,
        itemHeight: 50,
        height: 250,
        unit: 'm',
        onChanged: (val) {
          print('val----$val');
        },
      ),
    )
      ],
    );
  }
}

///
/// Author: chengzan
/// Date: 2020-6-8
/// Describe: 单个选择picker
///

class SinglePickerWidget extends StatefulWidget {
  final List<Map> values;
  final value;
  final double itemHeight;
  final double height;
  final double width;
  final String unit;
  final Function onChanged;
  final Color backgroundColor;

  const SinglePickerWidget(
      {Key key,
      @required this.values,
      @required this.value,
      @required this.onChanged,
      this.unit,
      this.itemHeight = 37.5,
      this.backgroundColor = const Color(0xffffffff),
      this.height = 150.0,
      this.width = 150.0})
      : super(key: key);
  @override
  _SinglePickerWidgetState createState() => _SinglePickerWidgetState();
}

class _SinglePickerWidgetState extends State<SinglePickerWidget> {
  int _selectedColorIndex = 0;
  FixedExtentScrollController scrollController;
  var values;
  var value;

  //设置防抖周期为300毫秒
  Duration durationTime = Duration(milliseconds: 300);
  Timer timer;

  @override
  void initState() {
    super.initState();
    values = widget.values;
    value = widget.value;
    getDefaultValue();
    scrollController =
        FixedExtentScrollController(initialItem: _selectedColorIndex);
  }

  @override
  void dispose() {
    super.dispose();
    scrollController.dispose();
    timer?.cancel();
  }

  // 获取默认选择值
  getDefaultValue() {
    // 查找要选择的默认值
    for (var i = 0; i < values.length; i++) {
      if (values[i]["value"] == value) {
        setState(() {
          _selectedColorIndex = i;
        });
        break;
      }
    }
  }

  // 触发值改变
  void _changed(index) {
    timer?.cancel();
    timer = new Timer(durationTime, () {
      // 回调函数
      widget.onChanged(values[index]["value"]);
    });
  }

  Widget _buildColorPicker(BuildContext context) {
    return Container(
      height: widget.height,
      color: Colors.white,
      child: Stack(
        alignment: Alignment.center,
        children: [
          widget.unit != null
              ? Positioned(
                  top: widget.height / 2 - (widget.itemHeight / 2),
                  left: widget.width / 2 + 18.0,
                  child: Container(
                    alignment: Alignment.center,
                    height: widget.itemHeight,
                    child: Text(
                      widget.unit,
                      style: TextStyle(
                        color: Color(0xff333333),
                        fontSize: 16.0,
                        height: 1.5,
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                  ),
                )
              : Offstage(
                  offstage: true,
                ),
          CupertinoPicker(
            magnification: 1.0, // 整体放大率
            scrollController:
                scrollController, // 用于读取和控制当前项的FixedxtentScrollController
            itemExtent: widget.itemHeight, // 所以子节点 统一高度
            useMagnifier: true, // 是否使用放大效果
            backgroundColor: Colors.transparent,
            onSelectedItemChanged: (int index) {
              // 当正中间选项改变时的回调
              if (mounted) {
                print('index--------------$index');
                _changed(index);
              }
            },
            children: List<Widget>.generate(values.length, (int index) {
              return Container(
                alignment: Alignment.center,
                height: widget.itemHeight,
                child: Text(
                  values[index]["label"],
                  style: TextStyle(
                    color: Color(0xff333333),
                    fontSize: 21.0,
                    height: 1.2,
                    fontWeight: FontWeight.w500,
                  ),
                ),
              );
            }),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: widget.height,
      width: widget.width,
      child: CupertinoPageScaffold(
        child: Container(
          child: ListView(
            padding: EdgeInsets.all(0),
            children: <Widget>[
              _buildColorPicker(context),
            ],
          ),
        ),
      ),
    );
  }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个使用CupertinoPicker实现二级联动选择器的示例代码,可以在底部弹出: ```dart showModalBottomSheet( context: context, builder: (BuildContext builder) { return Container( height: 250.0, child: Column( children: <Widget>[ Container( height: 200.0, child: CupertinoPicker( itemExtent: 30.0, onSelectedItemChanged: (int index) { //更新选择项时的回调函数 }, children: List<Widget>.generate(2, (int groupIndex) { return Column( children: <Widget>[ Container( height: 30.0, child: Text( 'Group ${groupIndex}', style: TextStyle(fontSize: 16.0), ), ), Expanded( child: CupertinoPicker( itemExtent: 30.0, onSelectedItemChanged: (int index) { //更新选择项时的回调函数 }, children: List<Widget>.generate(5, (int index) { return Center( child: Text('Item ${index}'), ); }), ), ), ], ); }), ), ), Container( height: 50.0, color: Colors.white, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ FlatButton( onPressed: () { //取消按钮的回调函数 Navigator.pop(context); }, child: Text( 'Cancel', style: TextStyle(color: Colors.red), ), ), FlatButton( onPressed: () { //确定按钮的回调函数 Navigator.pop(context); }, child: Text( 'Done', style: TextStyle(color: Colors.blue), ), ), ], ), ), ], ), ); }, ); ``` 在这个示例中,使用了两个CupertinoPicker来实现二级联动选择器。外层CupertinoPicker用于选择一级选项,内层CupertinoPicker用于选择二级选项。每个CupertinoPicker都使用了generate方法来动态生成选择项的widget。在选择项改变时,可以在onSelectedItemChanged回调函数中进行相应的处理。底部的取消和确定按钮可以使用FlatButton来实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值