Flutter中的节流与防抖(过滤重复点击)

4,655 阅读2分钟

Flutter中的节流与防抖

背景

在一些计算较为复杂、操作较为耗时或者操作为引起页面重绘的场景,如果事件触发的频率毫无限制,除了带来性能上的负担,还会导致糟糕的用户体验。如:根据输入框输入的内容向服务端查询相关文章,用户多次点击收藏按钮……

防抖(debounce)

在触发事件时,不立即执行目标操作,而是给出一个延迟的时间,在该时间范围内如果再次触发了事件,则重置延迟时间,直到延迟时间结束才会执行目标操作。

示例

如设定延迟时间为2000ms,

  • 如果在2000ms内没有再次触发事件,则执行目标操作
// debounce.dart

import 'dart:async';

Map<String, Timer> _funcDebounce = {};

/// 函数防抖
///
/// [func]: 要执行的方法
/// [milliseconds]: 要迟延的毫秒时间
Function debounce(Function func, [int milliseconds = 2000]) {
  Function target = () {
    String key = func.hashCode.toString();
    Timer _timer = _funcDebounce[key];
    if (_timer == null) {
      func?.call();
      _timer = Timer(Duration(milliseconds: milliseconds), () {
        Timer t = _funcDebounce.remove(key);
        t?.cancel();
        t = null;
      });
      _funcDebounce[key] = _timer;
    }
  };
  return target;
}

节流(throttle)

在触发事件时,立即执行目标操作,在该目标操作未执行完成时,如果再次触发了事件,该次事件会被忽略,直到超过该目标操作执行完成后触发事件才会被处理。

示例

import 'dart:async';

Map<String, bool> _funcThrottle = {};

/// 函数节流
///329017575
/// [func]: 要执行的方法
Function throttle(Future Function() func) {
  if (func == null) {
    return func;
  }
  Function target = () {
    String key = func.hashCode.toString();
    print(key);
    bool _enable = _funcThrottle[key] ?? true;
    if (_enable) {
      _funcThrottle[key] = false;
      func().then((_) {
        _funcThrottle[key] = false;
      }).whenComplete(() {
        _funcThrottle.remove(key);
      });
    }
  };
  return target;
}

使用

import 'package:flutter/material.dart';

import 'fun_utils.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      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> {
  int _counter = 0;

  _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          debounce(_incrementCounter)();
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}