防抖Debounce
什么是防抖(Debounce)
防抖是一种 控制函数调用频率的技术。典型场景是:
- 用户输入搜索关键词,你不想每敲一个字就发送请求,而是在用户停止输入一定时间后再发送。
- 滑动条频繁变化时,不想每次变化都触发计算或请求。
原理:
当事件被触发时,延迟执行函数。
如果在延迟时间内再次触发事件,前一次延迟取消,重新计时。
只有在最后一次触发后经过设定时间没有再次触发,函数才真正执行。
GetX 的 debounce 方法
在 GetX 中,debounce 用来对 Rx 变量(响应式变量)的变化进行防抖处理。
void debounce<T>(
Rx<T> rx,
VoidCallback callback, {
Duration time = const Duration(milliseconds: 500),
bool? leading,
});
| 参数 | 说明 |
|---|---|
rx |
要监听的响应式变量(如 RxString, RxInt, RxList 等) |
callback |
当防抖条件满足时执行的回调 |
time |
防抖时间间隔,默认为 500ms |
leading |
是否在首次触发立即执行回调(默认为 false) |
场景一:搜索输入防抖
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(const MyApp());
}
/// 主程序
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(title: 'GetX Debounce 搜索 Demo', home: SearchPage());
}
}
/// 控制器
class SearchController extends GetxController {
// 响应式字符串变量,用来存储搜索关键词
var query = ''.obs;
@override
void onInit() {
super.onInit();
// 防抖监听 query 变量
// 当 query 停止变化超过 800ms,才会执行 fetchSearchResults
debounce<String>(
query,
(_) => fetchSearchResults(query.value),
time: const Duration(milliseconds: 800),
);
}
// 模拟网络搜索
void fetchSearchResults(String keyword) {
print('搜索关键词: $keyword');
}
}
/// 页面
class SearchPage extends StatelessWidget {
final SearchController controller = Get.put(SearchController());
SearchPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('搜索防抖示例')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// 输入框
TextField(
decoration: const InputDecoration(
labelText: '输入搜索关键词',
border: OutlineInputBorder(),
),
onChanged: (value) {
// 每次输入都会触发 query 的变化
controller.query.value = value;
},
),
const SizedBox(height: 20),
const Text('打开控制台,输入时不会立即触发搜索,停止输入 800ms 后打印搜索关键词'),
],
),
),
);
}
}
// 以下是日志
I/flutter (15544): 搜索关键词: a
I/flutter (15544): 搜索关键词: aa
I/flutter (15544): 搜索关键词: aac
I/flutter (15544): 搜索关键词: aacdd
I/flutter (15544): 搜索关键词: aacddfeserwcewr
为什么debounce(或 interval)放在onInit里面
在 中使用 debounce(或 interval)时,我们通常会把它们写在 onInit() 里,这是有明确原因的:
debounce是「监听 Rx 变量变化」的注册函数debounce(rx, callback, …)本质是:订阅(监听)这个 Rx 变量的变化。它不会立刻执行
callback,而是等到这个 Rx 值发生变化时才会判断是否要触发回调。所以它必须先注册监听,等后续值变化时才能起作用。
onInit()是注册监听逻辑的最佳时机onInit()是GetxController的生命周期方法,在控制器第一次被创建时调用,只执行一次。在这里放debounce,可以确保:- 只注册一次监听,不会重复添加。
- 生命周期清晰,便于维护和释放资源(对应的
onClose()会自动清理订阅)。
所以把
debounce放到onInit()里,是标准做法。
如果你放在 build 或 onChanged 里会有什么问题?
build()会多次执行(每次界面刷新都会重建 UI),如果在里面调用debounce:会不断重复注册监听,导致回调被触发多次(打印多次、发多次请求等)onChanged()是事件回调,每次输入/滑动都会调用一次,如果在这里注册debounce:也会重复绑定监听,导致防抖逻辑完全失效
场景二:滑动条值变化防抖
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'GetX Debounce Slider Demo',
home: SliderPage(),
);
}
}
/// 控制器
class SliderController extends GetxController {
// 响应式 double 变量,表示滑动条值
var sliderValue = 0.0.obs;
@override
void onInit() {
super.onInit();
// 防抖监听 sliderValue
// 用户滑动时,只有停止滑动超过 300ms 才打印最终值
debounce<double>(
sliderValue,
(val) => print('最终滑动值: $val'),
time: const Duration(milliseconds: 300),
);
}
}
/// 页面
class SliderPage extends StatelessWidget {
final SliderController controller = Get.put(SliderController());
SliderPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('滑动条防抖示例')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Obx(
// 实时显示滑动条的值
() => Text(
'当前值: ${controller.sliderValue.value.toStringAsFixed(2)}',
),
),
const SizedBox(height: 20),
Obx(
() => Slider(
min: 0,
max: 100,
value: controller.sliderValue.value,
onChanged: (value) {
// 每次滑动都会触发 sliderValue 的变化
controller.sliderValue.value = value;
},
),
),
const SizedBox(height: 20),
const Text('打开控制台,滑动条变化不会立即打印,停止滑动 300ms 后打印最终值'),
],
),
),
);
}
}
// 以下是日志:
I/flutter (15971): 最终滑动值: 14.13532125538793
I/flutter (15971): 最终滑动值: 36.28982017780172
I/flutter (15971): 最终滑动值: 57.580566406250014
I/flutter (15971): 最终滑动值: 100.0
I/flutter (15971): 最终滑动值: 0.0
I/flutter (15971): 最终滑动值: 36.032209725215516
I/flutter (15971): 最终滑动值: 41.72237001616378
节流interval
什么是节流(interval)
节流是指:让高频率触发的操作,在固定时间间隔内最多只执行一次。
interval 就是用于 限制高频变化的调用频率,也叫节流(throttle)。
- 当被监听的 Rx 变量短时间内频繁变化时,
interval会 固定时间间隔触发一次回调。 - 不同于
debounce等待“停止变化”,interval是 按固定间隔触发。
典型场景:
- 滑动条连续拖动时,每隔 300ms 更新一次显示或发送网络请求。
- 高频按钮点击或动画值变化时,只想限制触发频率。
interval方法
void interval<T>(
Rx<T> rx,
VoidCallback callback, {
Duration time = const Duration(milliseconds: 300),
});
| 参数 | 说明 |
|---|---|
rx |
要监听的响应式变量(如 RxInt, RxDouble, RxString 等) |
callback |
在固定时间间隔触发的回调函数 |
time |
时间间隔(Duration),默认 300ms |
场景:滑动条节流
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(home: ThrottleSliderPage());
}
}
/// 控制器
class SliderController extends GetxController {
var sliderValue = 0.0.obs;
@override
void onInit() {
super.onInit();
// interval: 每隔 300ms 触发一次回调
interval<double>(
sliderValue,
(val) => print('节流打印滑动值: ${sliderValue.value.toStringAsFixed(2)}'),
time: const Duration(milliseconds: 300),
);
}
}
/// 页面
class ThrottleSliderPage extends StatelessWidget {
final SliderController controller = Get.put(SliderController());
ThrottleSliderPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('滑动条节流示例')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Obx(
() => Text(
'当前值: ${controller.sliderValue.value.toStringAsFixed(2)}',
),
),
Obx(
() => Slider(
min: 0,
max: 100,
value: controller.sliderValue.value,
onChanged: (value) {
controller.sliderValue.value = value;
},
),
),
const SizedBox(height: 20),
const Text('拖动滑块时,每隔 300ms 才会打印一次值,即使滑块频繁变化'),
],
),
),
);
}
}
// 以下是日志
I/flutter (16136): 节流打印滑动值: 31.55
I/flutter (16136): 节流打印滑动值: 31.89
I/flutter (16136): 节流打印滑动值: 69.91
I/flutter (16136): 节流打印滑动值: 95.34
I/flutter (16136): 节流打印滑动值: 96.55
I/flutter (16136): 节流打印滑动值: 94.39
I/flutter (16136): 节流打印滑动值: 90.86
I/flutter (16136): 节流打印滑动值: 38.19
I/flutter (16136): 节流打印滑动值: 34.39
I/flutter (16136): 节流打印滑动值: 41.03
I/flutter (16136): 节流打印滑动值: 41.38
I/flutter (16136): 节流打印滑动值: 96.55
I/flutter (16136): 节流打印滑动值: 100.00
I/flutter (16136): 节流打印滑动值: 0.00
I/flutter (16136): 节流打印滑动值: 38.19
I/flutter (16136): 节流打印滑动值: 48.36
I/flutter (16136): 节流打印滑动值: 53.79
对比
| 特性 | debounce | interval |
|---|---|---|
| 触发时机 | 停止变化后延迟触发 | 按固定时间间隔触发 |
| 场景 | 输入搜索、停止拖动后触发操作 | 高频滑动、动画、按钮点击限制 |
| 调用频率 | 最后一次变化触发一次 | 在间隔时间内重复触发一次 |
| 参数 | time、leading |
time |