美文网首页
如何用Flutter 做一个RangeSlider范围选择选择器

如何用Flutter 做一个RangeSlider范围选择选择器

作者: Hamiltonian | 来源:发表于2022-06-10 18:36 被阅读0次

    GIF效果图加载中【文件较大请稍等】

    由于开发的需要实现一个RangeSlider【选择开始和结尾范围的Slider】。可惜pub.dev没有现成的第三方控件可用。所以就动手自己,实现了一个。


    demo.gif

    最终实现的交互效果【源码放在下一篇文章】


    demo2.gif

    新建文件,命名为【date_range_slider.dart】

    import 'package:flutter/material.dart';
    
    enum SelectedRange { leftA, rightB, none, translation }
    enum AppearanceState { statePointerDown, statePointerUp }
    typedef SliderValueChangedCallback = void Function(
        num beginPercent, num endPercent);
    
    class DateRangeSlider extends StatefulWidget {
      final double sliderHandleWidth;
      final double minimumRangePercentage;
      final SliderValueChangedCallback? valueChangedCallback;
      const DateRangeSlider(
          {Key? key,
          this.sliderHandleWidth = 25,
          this.minimumRangePercentage = 0.2,
          this.valueChangedCallback})
          : super(key: key);
    
      @override
      _DateRangeSliderState createState() => _DateRangeSliderState();
    }
    
    class _DateRangeSliderState extends State<DateRangeSlider> {
      Offset offsetA = Offset(0, 0);
      Offset offsetB = Offset(250, 0);
      SelectedRange selectedRange = SelectedRange.none;
      AppearanceState appearanceState = AppearanceState.statePointerUp;
    
      ///
      bool isFirstLoad = true;
      Color darkBlue = Colors.indigo;
      Color blueAccent = Colors.blueAccent;
    
      @override
      void initState() {
        super.initState();
      }
    
      @override
      Widget build(BuildContext context) {
        return LayoutBuilder(
          builder: (BuildContext context, BoxConstraints constraints) {
            ///最小间距
            double minimumGap =
                constraints.biggest.width * widget.minimumRangePercentage;
    
            ///第一次进入 默认 Range为 50% - 100%
            if (isFirstLoad == true) {
              isFirstLoad = false;
    
              double halfWidth = constraints.biggest.width / 2;
              this.offsetA = Offset(halfWidth - widget.sliderHandleWidth, 0);
              this.offsetB = Offset(
                  constraints.biggest.width - widget.sliderHandleWidth / 2 - 2, 0);
    
              ///GetX 记录百分比
              Future.delayed(Duration(milliseconds: 500), () {
                calculatePercentageRange(constraints);
              });
            }
            return Listener(
              onPointerDown: (PointerDownEvent event) {
                setState(() {
                  appearanceState = AppearanceState.statePointerDown;
                });
              },
              onPointerMove: (PointerMoveEvent event) {
                setState(() {
                  double buttonARightEdge =
                      offsetA.dx + event.delta.dx + widget.sliderHandleWidth / 2;
    
                  double buttonBRightEdge =
                      offsetB.dx + event.delta.dx + widget.sliderHandleWidth / 2;
    
                  ///如果滑动 左边的按钮
                  if (selectedRange == SelectedRange.leftA) {
                    // print('6666666event.delta.dx ${event.delta.dx}');
                    if (buttonARightEdge >= 0 - widget.sliderHandleWidth / 2) {
                      ///如果左边的按钮 往右边 滑,要保证两个按钮之间 最小间距
                      if (event.delta.dx > 0) {
                        if (buttonBRightEdge - buttonARightEdge > minimumGap) {
                          offsetA = Offset(offsetA.dx + event.delta.dx, 0);
                        }
                      } else {
                        double xSet = offsetA.dx + event.delta.dx;
                        if (xSet < -(widget.sliderHandleWidth / 2)) {
                          xSet = -(widget.sliderHandleWidth / 2);
                        }
                        offsetA = Offset(xSet, 0);
                      }
                    }
    
                    ///如果滑动 右边的按钮
                  } else if (selectedRange == SelectedRange.rightB) {
                    if (buttonBRightEdge <= constraints.biggest.width) {
                      ///如果右边的按钮 往左边滑,要保证两个按钮之间 最小间距
                      if (event.delta.dx < 0) {
                        if (buttonBRightEdge - buttonARightEdge > minimumGap) {
                          offsetB = Offset(offsetB.dx + event.delta.dx, 0);
                        }
                      } else {
                        double xSet = offsetB.dx + event.delta.dx;
                        if (xSet > (constraints.biggest.width)) {
                          xSet = (constraints.biggest.width);
                        }
                        offsetB = Offset(xSet, 0);
                      }
                    }
    
                    ///如果是 处理平移 【range 】
                  } else if (selectedRange == SelectedRange.translation) {
                    if (buttonARightEdge >= 0 &&
                        buttonBRightEdge <= constraints.biggest.width) {
                      offsetA = Offset(offsetA.dx + event.delta.dx, 0);
                      offsetB = Offset(offsetB.dx + event.delta.dx, 0);
                    }
                  }
    
                  ///GetX 记录百分比
                  calculatePercentageRange(constraints);
                });
              },
              onPointerUp: (PointerUpEvent event) {
                setState(() {
                  ///恢复默认状态
                  selectedRange = SelectedRange.none;
                  appearanceState = AppearanceState.statePointerUp;
                });
              },
              child: Container(
                height: 55,
                child: Stack(
                  children: [
                    Positioned(
                        top: 0,
                        left: offsetA.dx + widget.sliderHandleWidth / 2,
                        right: constraints.biggest.width -
                            offsetB.dx -
                            2 -
                            widget.sliderHandleWidth / 2,
                        height: 8,
                        child: GestureDetector(
                          onPanStart: (DragStartDetails details) {
                            selectedRange = SelectedRange.translation;
                          },
                          child: AnimatedContainer(
                            duration: Duration(milliseconds: 300),
                            decoration: BoxDecoration(
                                color: appearanceState ==
                                        AppearanceState.statePointerUp
                                    ? darkBlue.withOpacity(0.2)
                                    : darkBlue.withOpacity(0.5),
                                borderRadius: BorderRadius.only(
                                    topLeft: Radius.circular(3),
                                    topRight: Radius.circular(3))),
                            child: const Icon(
                              Icons.view_week_rounded,
                              color: Colors.white,
                              size: 8.0,
                            ),
                          ),
                        )),
                    Positioned(
                      top: 8,
                      left: 0,
                      right: 0,
                      bottom: 0,
                      child: Container(
                        decoration: BoxDecoration(
                            border: Border.all(
                                color: darkBlue.withOpacity(0.2), width: 1.5),
                            borderRadius: BorderRadius.circular(5)),
                        height: 50,
                        child: Stack(
                          children: [
                            Positioned(
                              top: 0,
                              left: offsetA.dx + widget.sliderHandleWidth / 2,
                              right: constraints.biggest.width -
                                  offsetB.dx -
                                  3 -
                                  widget.sliderHandleWidth / 2,
                              bottom: 0,
                              child: GestureDetector(
                                onPanStart: (DragStartDetails details) {
                                  selectedRange = SelectedRange.translation;
                                },
                                child: Container(
                                  color: darkBlue.withOpacity(0.15),
                                ),
                              ),
                            ),
                            Positioned(
                                top: offsetA.dy,
                                left: offsetA.dx,
                                bottom: 0,
                                child: GestureDetector(
                                  onPanStart: (DragStartDetails details) {
                                    selectedRange = SelectedRange.leftA;
                                  },
                                  child: SliderHandleWidget(
                                    width: widget.sliderHandleWidth,
                                  ),
                                )),
                            Positioned(
                                top: offsetB.dy,
                                left: offsetB.dx,
                                bottom: 0,
                                child: GestureDetector(
                                  onPanStart: (DragStartDetails details) {
                                    selectedRange = SelectedRange.rightB;
                                  },
                                  child: SliderHandleWidget(
                                    width: widget.sliderHandleWidth,
                                  ),
                                )),
                          ],
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            );
          },
        );
      }
    
      void calculatePercentageRange(BoxConstraints constraints) {
        // print('_DateRangeSliderState.calculatePercentageRangeoffsetA.dx ${(offsetB.dx + chart_with_time_range.sliderHandleWidth / 2)} ${constraints.biggest.width}');
    
        ///GetX 记录百分比
        double percentageA =
            (offsetA.dx + widget.sliderHandleWidth / 2) / constraints.biggest.width;
        double percentageB =
            (offsetB.dx + widget.sliderHandleWidth / 2) / constraints.biggest.width;
        if (percentageA > 1) {
          percentageA = 1;
        }
        if (percentageA < 0) {
          percentageA = 0;
        }
        if (percentageB > 1) {
          percentageB = 1;
        }
        if (percentageB < 0) {
          percentageB = 0;
        }
    
        if (widget.valueChangedCallback != null) {
          widget.valueChangedCallback!(percentageA, percentageB);
        }
      }
    }
    
    class SliderHandleWidget extends StatefulWidget {
      final double width;
      const SliderHandleWidget({
        this.width = 30,
        Key? key,
      }) : super(key: key);
    
      @override
      State<SliderHandleWidget> createState() => _SliderHandleWidgetState();
    }
    
    class _SliderHandleWidgetState extends State<SliderHandleWidget> {
      Color unselectedColor = Colors.indigo.withOpacity(0.6);
      Color selectedColor = Colors.blueAccent;
      Color currentColor = Colors.white;
      @override
      void initState() {
        // TODO: implement initState
        super.initState();
        currentColor = unselectedColor;
      }
    
      @override
      Widget build(BuildContext context) {
        return LayoutBuilder(
            builder: (BuildContext context, BoxConstraints constraints) {
          return Listener(
            onPointerUp: (PointerUpEvent event) {
              setState(() {
                currentColor = unselectedColor;
              });
            },
            onPointerDown: (PointerDownEvent event) {
              setState(() {
                currentColor = selectedColor;
              });
            },
            child: AnimatedContainer(
              duration: Duration(milliseconds: 500),
              child: Column(
                children: [
                  Container(
                    height: constraints.biggest.height * 0.15,
                    width: 1.5,
                    color: currentColor,
                  ),
                  Container(
                    width: 10,
                    height: constraints.biggest.height * 0.7,
                    decoration: BoxDecoration(
                        border: Border.all(color: currentColor, width: 1.5),
                        borderRadius: BorderRadius.circular(3),
                        color: Colors.white),
                  ),
                  Container(
                    height: constraints.biggest.height * 0.15,
                    width: 1.5,
                    color: currentColor,
                  )
                ],
              ),
              color: Colors.transparent,
              width: widget.width,
              height: 50,
            ),
          );
        });
      }
    }
    
    

    在测试页面调用 【DateRangeSlider】

    import 'package:flutter/material.dart';
    import 'date_range_slider.dart';
    
    class TestPage extends StatefulWidget {
      const TestPage({Key? key}) : super(key: key);
    
      @override
      State<TestPage> createState() => _TestPageState();
    }
    
    class _TestPageState extends State<TestPage> {
      num beginPercentage = 0;
      num endPercentage = 0;
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            body: DefaultTextStyle(
          style: const TextStyle(
              fontSize: 18, fontWeight: FontWeight.bold, color: Colors.red),
          child: Center(
            child: SizedBox(
              height: 120,
              width: MediaQuery.of(context).size.width - 100,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text("Begin: " + beginPercentage.toStringAsFixed(2)),
                  Text("End: " + endPercentage.toStringAsFixed(2)),
                  DateRangeSlider(
                    valueChangedCallback: (a, b) {
                      setState(() {
                        beginPercentage = a * 100;
                        endPercentage = b * 100;
                        print('_TestPageState.build a:$a,b:$b');
                      });
                    },
                  ),
                ],
              ),
            ),
          ),
        ));
      }
    }
    
    

    相关文章

      网友评论

          本文标题:如何用Flutter 做一个RangeSlider范围选择选择器

          本文链接:https://www.haomeiwen.com/subject/zgpbmrtx.html