按住按钮执行的Flutter方法太长

13
我想在用户按下按钮时执行一个方法。伪代码如下:

我想在用户按下按钮时执行一个方法。伪代码:

while (button.isPressed) {
  executeCallback();
}

换句话说,只要用户按住按钮,executeCallback方法就应该不断触发,当按钮被释放时停止触发。在Flutter中我该如何实现这一点?


GestureLongPressCallback onLongPress 属性出了什么问题? - pskink
我需要的正是这个,我需要该方法一直执行,直到手指离开按钮。 - DoctorDoom
1
这个想法是关于音乐播放器中的向前按钮,当用户点击按钮时,我需要快进歌曲。 - DoctorDoom
forword?你是指forward吗?即使如此,我仍然不明白你真正想要实现什么... - pskink
让我们在聊天中继续这个讨论 - DoctorDoom
显示剩余2条评论
5个回答

29

使用Listener和有状态小部件,同时在每个循环之后引入轻微延迟:

import 'dart:async';

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(brightness: Brightness.dark),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  bool _buttonPressed = false;
  bool _loopActive = false;

  void _increaseCounterWhilePressed() async {
    // make sure that only one loop is active
    if (_loopActive) return;

    _loopActive = true;

    while (_buttonPressed) {
      // do your thing
      setState(() {
        _counter++;
      });

      // wait a bit
      await Future.delayed(Duration(milliseconds: 200));
    }

    _loopActive = false;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Listener(
          onPointerDown: (details) {
            _buttonPressed = true;
            _increaseCounterWhilePressed();
          },
          onPointerUp: (details) {
            _buttonPressed = false;
          },
          child: Container(
            decoration: BoxDecoration(color: Colors.orange, border: Border.all()),
            padding: EdgeInsets.all(16.0),
            child: Text('Value: $_counter'),
          ),
        ),
      ),
    );
  }
}

这正是我所需要的。 - DoctorDoom
1
请同时处理 onPointerCancel ;) - boformer

15

没有监听器的更简单的方法如下:

  GestureDetector(
      child: InkWell(
        child: Icon(Icons.skip_previous_rounded),
        onTap: widget.onPrevious,
      ),
      onLongPressStart: (_) async {
        isPressed = true;
        do {
          print('long pressing'); // for testing
          await Future.delayed(Duration(seconds: 1));
        } while (isPressed);
      },
      onLongPressEnd: (_) => setState(() => isPressed = false),
    );
  }

7

ThinkDigital 提供的解决方案基础上,我的观察是 InkWell 包含了所有必要的事件,可以在没有额外的 GestureDetector 的情况下实现这一功能(我发现 GestureDetector 在长按时会干扰墨水动画)。这是一个我为一个宠物项目实现的控件,当按住时其事件带有递减延迟 (这是一个带图标的圆角按钮,但使用 InkWell 的任何东西都可以):

/// A round button with an icon that can be tapped or held
/// Tapping the button once simply calls [onUpdate], holding
/// the button will repeatedly call [onUpdate] with a
/// decreasing time interval.
class TapOrHoldButton extends StatefulWidget {
  /// Update callback
  final VoidCallback onUpdate;

  /// Minimum delay between update events when holding the button
  final int minDelay;

  /// Initial delay between change events when holding the button
  final int initialDelay;

  /// Number of steps to go from [initialDelay] to [minDelay]
  final int delaySteps;

  /// Icon on the button
  final IconData icon;

  const TapOrHoldButton(
      {Key? key,
      required this.onUpdate,
      this.minDelay = 80,
      this.initialDelay = 300,
      this.delaySteps = 5,
      required this.icon})
      : assert(minDelay <= initialDelay,
            "The minimum delay cannot be larger than the initial delay"),
        super(key: key);

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

class _TapOrHoldButtonState extends State<TapOrHoldButton> {
  /// True if the button is currently being held
  bool _holding = false;

  @override
  Widget build(BuildContext context) {
    var shape = CircleBorder();
    return Material(
      color: Theme.of(context).dividerColor,
      shape: shape,
      child: InkWell(
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Icon(
            widget.icon,
            color:
                Theme.of(context).textTheme.headline1?.color ?? Colors.white70,
            size: 36,
          ),
        ),
        onTap: () => _stopHolding(),
        onTapDown: (_) => _startHolding(),
        onTapCancel: () => _stopHolding(),
        customBorder: shape,
      ),
    );
  }

  void _startHolding() async {
    // Make sure this isn't called more than once for
    // whatever reason.
    if (_holding) return;
    _holding = true;

    // Calculate the delay decrease per step
    final step =
        (widget.initialDelay - widget.minDelay).toDouble() / widget.delaySteps;
    var delay = widget.initialDelay.toDouble();

    while (_holding) {
      widget.onUpdate();
      await Future.delayed(Duration(milliseconds: delay.round()));
      if (delay > widget.minDelay) delay -= step;
    }
  }

  void _stopHolding() {
    _holding = false;
  }
}

这是它的实际应用效果:

增量控制


非常好的答案,非常感谢! - Javier Garcia
尝试了所有三个,这是最佳答案。 - Lalit Fauzdar

1
为了改进Elte Hupkes的解决方案,我修复了一个问题:连续点击时,点击次数和调用onUpdate回调函数的次数不匹配。
另外使用了变量_tapDownCount
import 'package:flutter/material.dart';

/// A round button with an icon that can be tapped or held
/// Tapping the button once simply calls [onUpdate], holding
/// the button will repeatedly call [onUpdate] with a
/// decreasing time interval.
class TapOrHoldButton extends StatefulWidget {
  /// Update callback
  final VoidCallback onUpdate;

  /// Minimum delay between update events when holding the button
  final int minDelay;

  /// Initial delay between change events when holding the button
  final int initialDelay;

  /// Number of steps to go from [initialDelay] to [minDelay]
  final int delaySteps;

  /// Icon on the button
  final IconData icon;

  const TapOrHoldButton(
      {Key? key,
      required this.onUpdate,
      this.minDelay = 80,
      this.initialDelay = 300,
      this.delaySteps = 5,
      required this.icon})
      : assert(minDelay <= initialDelay, "The minimum delay cannot be larger than the initial delay"),
        super(key: key);

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

class _TapOrHoldButtonState extends State<TapOrHoldButton> {
  /// True if the button is currently being held
  bool _holding = false;
  int _tapDownCount = 0;

  @override
  Widget build(BuildContext context) {
    var shape = const CircleBorder();
    return Material(
      color: Theme.of(context).dividerColor,
      shape: shape,
      child: InkWell(
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Icon(
            widget.icon,
            color: Theme.of(context).textTheme.headline1?.color ?? Colors.white70,
            size: 36,
          ),
        ),
        onTap: () => _stopHolding(),
        onTapDown: (_) => _startHolding(),
        onTapCancel: () => _stopHolding(),
        customBorder: shape,
      ),
    );
  }

  void _startHolding() async {
    // Make sure this isn't called more than once for
    // whatever reason.
    widget.onUpdate();
    _tapDownCount += 1;
    final int myCount = _tapDownCount;
    if (_holding) return;
    _holding = true;

    // Calculate the delay decrease per step
    final step = (widget.initialDelay - widget.minDelay).toDouble() / widget.delaySteps;
    var delay = widget.initialDelay.toDouble();

    while (true) {
      await Future.delayed(Duration(milliseconds: delay.round()));
      if (_holding && myCount == _tapDownCount) {
        widget.onUpdate();
      } else {
        return;
      }
      if (delay > widget.minDelay) delay -= step;
    }
  }

  void _stopHolding() {
    _holding = false;
  }
}


1

使用setState

您可以通过按钮的"onLongPressStart"和"onLongPressEnd"属性来实现此功能。如果您在小部件中找不到"onLongPressStart" / "onLongPressEnd"属性,请使用"GestureDetector"小部件包装您的小部件。

GestureDetector(
  child: ..,
  onLongPressStart: (_) async {
    isTap = true;
    do {
      await Future.delayed(Duration(seconds: 1));
    } while (isTap );
  },
  onLongPressEnd: (_) => setState(() => isTap = false),
); 

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接