Flutter中的節流與防抖(過濾重複點選)

靜默的小貓發表於2021-07-26

Flutter中的節流與防抖

背景

在一些計算較為複雜、操作較為耗時或者操作為引起頁面重繪的場景,如果事件觸發的頻率毫無限制,除了帶來效能上的負擔,還會導致糟糕的使用者體驗。如:根據輸入框輸入的內容向服務端查詢相關文章,使用者多次點選收藏按鈕……

防抖(debounce)

在觸發事件時,不立即執行目標操作,而是給出一個延遲的時間,在該時間範圍內如果再次觸發了事件,則重置延遲時間,直到延遲時間結束才會執行目標操作。

示例

如設定延遲時間為2000ms,

  • 如果在2000ms內沒有再次觸發事件,則執行目標操作
  • 如果在2000ms內再次觸發了事件,則重置延遲時間,重新開始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)

在觸發事件時,立即執行目標操作,同時給出一個延遲的時間,在該時間範圍內如果再次觸發了事件,該次事件會被忽略,直到超過該時間範圍後觸發事件才會被處理。

示例

如設定延遲時間為2000ms,

  • 如果在2000ms內再次觸發事件,該事件會被忽略
  • 如果2000ms延遲結束,則事件不會被忽略,觸發事件會立即執行目標操作,並再次開啟2000ms延遲
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.
    );
  }
}
複製程式碼

相關文章