防止颤动中触碰/结束时拖动中的小跳跃

Jos*_*ane 5 slider touch drag flutter

我已经构建了一个自定义滑块,并一直使用GestureDetectoronHorizontalDragUpdate报告拖动更改、更新 UI 和value.

然而,当用户抬起手指时,有时可能会出现一个小的、无意的跳跃/拖动,足以调整滑块上的值并降低准确性。我怎样才能阻止这种情况发生?

我考虑过添加一个小的延迟,以防止拖动在短时间内没有移动并评估primaryDelta,但不确定这是否适合目的,或者是否有更常规的常见做法来防止这种情况。

--

我正在使用的现有拖动逻辑的示例。初始拖动数据来自onHorizontalDragUpdate_buildThumb. 重建滑块时,会计算轨道大小和拇指位置LayoutBuilder,然后根据拇指位置计算该值。

    double valueForPosition({required double min, required double max}) {
    double posIncrements = ((max) / (_divisions));
    double posIncrement = (_thumbPosX / (posIncrements));
    double incrementVal =
        (increment) * (posIncrement + widget.minimumValue).round() +
            (widget.minimumValue - widget.minimumValue.truncate());
    return incrementVal.clamp(widget.minimumValue, widget.maximumValue);
  }

  double thumbPositionForValue({required double min, required double max}) {
    return (max / (widget.maximumValue - widget.minimumValue - 1)) *
        (value - widget.minimumValue - 1);
  }

  double trackWidthForValue({
    required double min,
    required double max,
    required double thumbPosition,
  }) {
    return (thumbPosition + (_thumbTouchZoneWidth / 2))
        .clamp(min, max)
        .toDouble();
  }

  bool isDragging = false;
  bool isSnapping = false;
  Widget _buildSlider() {
    return SizedBox(
      height: _contentHeight,
      child: LayoutBuilder(
        builder: (context, constraints) {
          double minThumbPosX = -(_thumbTouchZoneWidth - _thumbWidth) / 2;
          double maxThumbPosX =
              constraints.maxWidth - (_thumbTouchZoneWidth / 2);
          if (isDragging) {
            _thumbPosX = _thumbPosX.clamp(minThumbPosX, maxThumbPosX);
            value = valueForPosition(min: minThumbPosX, max: maxThumbPosX);
            WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
              widget.onChanged(value);
            });
          } else {
            _thumbPosX = thumbPositionForValue(
              min: minThumbPosX,
              max: maxThumbPosX,
            );
          }

          double minTrackWidth = 0;
          double maxTrackWidth = constraints.maxWidth;
          double trackWidth = 0;
          if (isDragging) {
            trackWidth = (_thumbPosX + (_thumbTouchZoneWidth / 2))
                .clamp(_thumbWidth, constraints.maxWidth);
          } else {
            trackWidth = trackWidthForValue(
              min: minTrackWidth,
              max: maxTrackWidth,
              thumbPosition: _thumbPosX,
            );
          }

          return Stack(
            alignment: Alignment.centerLeft,
            clipBehavior: Clip.none,
            children: [
              _buildLabels(),
              _buildInactiveTrack(),
              Positioned(
                width: trackWidth,
                child: _buildActiveTrack(),
              ),
              Positioned(
                left: _thumbPosX,
                child: _buildThumb(),
              ),
            ],
          );
        },
      ),
    );
  }

    Widget _buildThumb() {
        return GestureDetector(
          behavior: HitTestBehavior.opaque,
          dragStartBehavior: DragStartBehavior.down,
          onHorizontalDragUpdate: (details) {
            setState(() {
              _thumbPosX += details.delta.dx;
              isDragging = true;
            });
          },
          child: // Thumb UI
        );
      }
Run Code Online (Sandbox Code Playgroud)

yel*_*ray 0

更新:我通过添加延迟状态和lastChangedTime 进行了一些调整。

  • 如果用户停止拖动一小段时间(3秒),滑块将被锁定,直到更新下一个新值+短暂的延迟(1.5秒)

我按照你的思路,从 Slider 小部件中做了一个简单的例子。

结果和你预想的一样吗?(您可以将持续时间调整为任意数字)

DartPad:https://dartpad.dev/?id =95f2bd6d004604b3c37f27dd2852cb31

class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({super.key});

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  double _currentSliderValue = 20;
  DateTime lastChangedTime = DateTime.now();
  bool isDalying = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(_currentSliderValue.toString()),
        const SizedBox(height: 30),
        Slider(
          value: _currentSliderValue,
          max: 100,
          label: _currentSliderValue.round().toString(),
          onChanged: (double value) async {
            if (isDalying) {
              await Future.delayed(
                Duration(milliseconds: 1500), 
                () => isDalying = false,
              );
            } else {
              if (DateTime.now().difference(lastChangedTime) >
                  Duration(seconds: 3)) {
                isDalying = true;
              } else {
                setState(() {
                  _currentSliderValue = value;
                });
              }
            }
            lastChangedTime = DateTime.now();
          },
        ),
      ],
    );
  }
}
Run Code Online (Sandbox Code Playgroud)