防抖是指在短时间内无意义的多次操作,然后我们规定单位时间内只处理一次。比如搜索框正在输入,就不应该频繁的去查询处理,控制在500毫秒响应一次查询操作。本文将会说明 AutoComplete 组件的使用以及加入防抖的控制,提示用户交互体验。

Flutter AutoComplete Debounce 防抖

@programunity Photo shared by programunity on February 06, 2023 taggin

效果

image-20230206121620330

前言

防抖是指在短时间内无意义的多次操作,然后我们规定单位时间内只处理一次。

比如搜索框正在输入,就不应该频繁的去查询处理,控制在500毫秒响应一次查询操作。

本文将会说明 AutoComplete 组件的使用以及加入防抖的控制,提示用户交互体验。

代码

完整代码详见 github .

https://github.com/ducafecat/flutter_develop_tips/tree/main/flutter_application_autocomplete

正文

Autocomplete 参数

参数类型说明
optionsBuilder函数返回可选列表数据值
displayStringForOption函数格式化显示字段
fieldViewBuilder函数输入字段组件
onSelected函数选中项时
optionsViewBuilder函数构建下拉列表项

参考

https://api.flutter.dev/flutter/material/Autocomplete-class.html

准备数据

lib/model.dart

/// 国家数据模型
class Country {
  const Country({
    required this.name,
    required this.size,
  });

  final String name;

  final int size;

  @override
  String toString() {
    return '$name ($size)';
  }
}

lib/debounce.dart

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

  @override
  State<DebouncePage> createState() => _DebouncePageState();
}
class _DebouncePageState extends State<DebouncePage> {
  List<Country> countryOptions = <Country>[
    const Country(name: 'Africa', size: 30370000),
    const Country(name: 'Asia', size: 44579000),
    const Country(name: 'Australia', size: 8600000),
    const Country(name: 'Bulgaria', size: 110879),
    const Country(name: 'Canada', size: 9984670),
    const Country(name: 'Denmark', size: 42916),
    const Country(name: 'Europe', size: 10180000),
    const Country(name: 'India', size: 3287263),
    const Country(name: 'North America', size: 24709000),
    const Country(name: 'South America', size: 17840000),
  ];

编写 Autocomplete 组件

返回可选列表数据值

  Widget _mainView() {
    return Autocomplete<Country>(
      // 返回可选列表数据值
      optionsBuilder: (TextEditingValue textEditingValue) async {
        debounceValue = textEditingValue.text;
        return countryOptions
            .where((Country county) => county.name
                .toLowerCase()
                .startsWith(debounceValue.toLowerCase()))
            .toList();
      },
      ...
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Autocomplete Getx 防抖")),
      body: Padding(padding: const EdgeInsets.all(10), child: _mainView()),
    );
  }

格式化显示字段

displayStringForOption: (Country option) => option.name,

输入字段组件

      fieldViewBuilder: (
        BuildContext context,
        TextEditingController fieldTextEditingController,
        FocusNode fieldFocusNode,
        VoidCallback onFieldSubmitted,
      ) {
        return TextField(
          controller: fieldTextEditingController,
          focusNode: fieldFocusNode,
          style: const TextStyle(
            fontWeight: FontWeight.bold,
            color: Colors.amber,
            fontSize: 24,
          ),
        );
      },

选中项时

      onSelected: (Country selection) {
        if (kDebugMode) {
          print('Selected: ${selection.name}');
        }
      },

构建下拉列表项

      optionsViewBuilder: (
        BuildContext context,
        AutocompleteOnSelected<Country> onSelected,
        Iterable<Country> options,
      ) {
        return Align(
          alignment: Alignment.topLeft,
          child: Material(
            child: Container(
              width: 300,
              color: Colors.cyan,
              child: ListView.builder(
                padding: const EdgeInsets.all(10.0),
                itemCount: options.length,
                itemBuilder: (BuildContext context, int index) {
                  final Country option = options.elementAt(index);

                  return GestureDetector(
                    onTap: () {
                      onSelected(option);
                    },
                    child: ListTile(
                      title: Text(option.name,
                          style: const TextStyle(color: Colors.white)),
                    ),
                  );
                },
              ),
            ),
          ),
        );
      },

加入防抖代码

  // 标记是否正在防抖
  bool isDebounce = false;
  // 防抖查询值
  String debounceValue = "";
  Widget _mainView() {
    return Autocomplete<Country>(
      // 返回可选列表数据值
      optionsBuilder: (TextEditingValue textEditingValue) async {
        debounceValue = textEditingValue.text;
        // 防抖处理
        // 防抖状态下直接返回空 List []
        if (isDebounce) {
          return Future.value([]);
        } else {
          isDebounce = true; // 设置防抖标志
          // 1秒防抖直接返回 FutureOr
          return Future.delayed(const Duration(seconds: 1), () async {
            List<Country> rows = [];
            // http 拉取数据
            try {
              rows = await Future.delayed(
                  const Duration(milliseconds: 500), // 延迟 500 毫秒,模拟 http 请求
                  () => countryOptions
                      .where((Country county) => county.name
                          .toLowerCase()
                          .startsWith(debounceValue.toLowerCase()))
                      .toList());
            } finally {
              isDebounce = false; // 关闭防抖标志
            }
            return rows;
          });
        }
      },

完整代码

lib/debounce.dart

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import 'model.dart';

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

  @override
  State<DebouncePage> createState() => _DebouncePageState();
}

class _DebouncePageState extends State<DebouncePage> {
  List<Country> countryOptions = <Country>[
    const Country(name: 'Africa', size: 30370000),
    const Country(name: 'Asia', size: 44579000),
    const Country(name: 'Australia', size: 8600000),
    const Country(name: 'Bulgaria', size: 110879),
    const Country(name: 'Canada', size: 9984670),
    const Country(name: 'Denmark', size: 42916),
    const Country(name: 'Europe', size: 10180000),
    const Country(name: 'India', size: 3287263),
    const Country(name: 'North America', size: 24709000),
    const Country(name: 'South America', size: 17840000),
  ];

  // 标记是否正在防抖
  bool isDebounce = false;
  // 防抖查询值
  String debounceValue = "";

  Widget _mainView() {
    return Autocomplete<Country>(
      // 返回可选列表数据值
      optionsBuilder: (TextEditingValue textEditingValue) async {
        debounceValue = textEditingValue.text;
        // 防抖处理
        // 防抖状态下直接返回空 List []
        if (isDebounce) {
          return Future.value([]);
        } else {
          isDebounce = true; // 设置防抖标志
          // 1秒防抖直接返回 FutureOr
          return Future.delayed(const Duration(seconds: 1), () async {
            List<Country> rows = [];
            // http 拉取数据
            try {
              rows = await Future.delayed(
                  const Duration(milliseconds: 500), // 延迟 500 毫秒,模拟 http 请求
                  () => countryOptions
                      .where((Country county) => county.name
                          .toLowerCase()
                          .startsWith(debounceValue.toLowerCase()))
                      .toList());
            } finally {
              isDebounce = false; // 关闭防抖标志
            }
            return rows;
          });
        }
      },

      // 格式化显示字段
      displayStringForOption: (Country option) => option.name,

      // 输入字段组件
      fieldViewBuilder: (
        BuildContext context,
        TextEditingController fieldTextEditingController,
        FocusNode fieldFocusNode,
        VoidCallback onFieldSubmitted,
      ) {
        return TextField(
          controller: fieldTextEditingController,
          focusNode: fieldFocusNode,
          style: const TextStyle(
            fontWeight: FontWeight.bold,
            color: Colors.amber,
            fontSize: 24,
          ),
        );
      },

      // 选中项时
      onSelected: (Country selection) {
        if (kDebugMode) {
          print('Selected: ${selection.name}');
        }
      },

      // 构建下拉列表项
      optionsViewBuilder: (
        BuildContext context,
        AutocompleteOnSelected<Country> onSelected,
        Iterable<Country> options,
      ) {
        return Align(
          alignment: Alignment.topLeft,
          child: Material(
            child: Container(
              width: 300,
              color: Colors.cyan,
              child: ListView.builder(
                padding: const EdgeInsets.all(10.0),
                itemCount: options.length,
                itemBuilder: (BuildContext context, int index) {
                  final Country option = options.elementAt(index);

                  return GestureDetector(
                    onTap: () {
                      onSelected(option);
                    },
                    child: ListTile(
                      title: Text(option.name,
                          style: const TextStyle(color: Colors.white)),
                    ),
                  );
                },
              ),
            ),
          ),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Autocomplete Getx 防抖")),
      body: Padding(padding: const EdgeInsets.all(10), child: _mainView()),
    );
  }
}

end