Flutter Autocomplete初步探索

Autocomplete 自动填充,在我们输入内容的时候进行关键字联想。效果在输入框下方展示。其属于Flutter原生自带的组件。
在这里插入图片描述

Autocomplete组件

class Autocomplete<T extends Object> extends StatelessWidget {
  /// Creates an instance of [Autocomplete].
  const Autocomplete({
    super.key,
    required this.optionsBuilder,
    this.displayStringForOption = RawAutocomplete.defaultStringForOption,
    this.fieldViewBuilder = _defaultFieldViewBuilder,
    this.onSelected,
    this.optionsMaxHeight = 200.0,
    this.optionsViewBuilder,
    this.initialValue,
  }) : assert(displayStringForOption != null),
       assert(optionsBuilder != null);
optionsBuilder 必须提供的参数
displayStringForOption 文字展示
fieldViewBuilder 输入内容构造器
onSelected 选中条目的回调
optionsMaxHeight 最大高度
optionsViewBuilder 面板构造器

final AutocompleteOptionsBuilder optionsBuilder; 可以通过onSelected回调监听选中的条目

typedef AutocompleteOptionsBuilder<T extends Object> = FutureOr<Iterable<T>> Function(TextEditingValue textEditingValue);

此函数是一个异步函数,我们可以在此函数内部进行网络请求、数据库查询等操作,返回一个 Iterable 的可迭代对象。

buildOptions 构造数据模型以及模拟搜索网络请求操作

Future<Iterable<User>> buildOptions(TextEditingValue textEditingValue) async {
    if (textEditingValue.text == '') {
      return const Iterable<User>.empty();
    }
    return searchByArgs(textEditingValue.text);
  }

searchByArgs 模拟网络请求,通过 args 参数搜索数据:

  Future<Iterable<User>> searchByArgs(String args) async {
    // 模拟网络请求
    await Future.delayed(const Duration(milliseconds: 200));
    ///初始化数据
    const List<User> data = [
      User('程咬金', false, 'icon_5.webp'),
      User('李元芳', false, 'icon_6.webp'),
      User('李白', false, 'icon_7.webp'),
      User('武则天', true, 'icon_8.webp'),
      User('王昭君', true, 'icon_6.webp'),
      User('妲己', true, 'icon_8.webp'),
      User('刘禅', false, 'icon_6.webp'),
      User('张飞', false, 'icon_8.webp'),
      User('刘邦', false, 'icon_5.webp'),
      User('李信', false, 'icon_6.webp'),
      User('anne', true, 'icon_8.webp'),
      User('andy', true, 'icon_5.webp'),
      User('joy', true, 'icon_7.webp'),
    ];
    return data.where((User user) => user.name.contains(args));
  }

buildOptions 完成输入–> 搜索 --> 展示联想词的流程。这也是 Autocomplete 组件最简单的使用

    return Scaffold(
      appBar: AppBar(
        title: Text('Autocomplete'),
        centerTitle: true,
      ),
      body: SizedBox(
        child: Container(
          child: buildAutoComplete(),
        ),
      ),
    );

效果如下:
在这里插入图片描述

自定义Autocomplete组件内容

    return Scaffold(
      appBar: AppBar(
        title: Text('Autocomplete'),
        centerTitle: true,
      ),
      body: SizedBox(
        height: 300,
        child: Scaffold(
          appBar: AppBar(
            automaticallyImplyLeading: false,
            title: buildAutoComplete(),
          ),
        ),
      ),
    );
  }

定义组件内容

  Widget buildAutoComplete() {
    return Autocomplete<User>(
      optionsBuilder: buildOptions,
      onSelected: onSelected,
      optionsViewBuilder: _buildOptionsView,
      fieldViewBuilder: _buildFieldView,
      displayStringForOption: (user) => user.name,
    );
  }

Autocomplete 中提供了 fieldViewBuilder 和 optionsViewBuilder 分别用于构造输入框和浮层面板。
_buildOptionsView 方法的实现,其中会回调 onSelected 回调函数,和 options 数据,我们需要做的就是依靠数据,构建组件进行展示即可。另外,默认浮层面板和输入框底部平齐,可以通过 Padding 进行下移。另外,由于是浮层,展示文字时,上面需要嵌套 Material 组件。

  Widget _buildOptionsView(BuildContext context,
      AutocompleteOnSelected<User> onSelected, Iterable<User> options) {
    return Align(
      alignment: Alignment.topCenter,
      child: Padding(
        padding: const EdgeInsets.only(top: 20.0),
        child: Material(
          child: ConstrainedBox(
            constraints: const BoxConstraints(maxHeight: 150.0),
            child: ListView.builder(
              padding: EdgeInsets.zero,
              itemBuilder: (_, index) {
                final User option = options.elementAt(index);
                return InkWell(
                  onTap: () => onSelected.call(option),
                  child: Container(
                    padding: const EdgeInsets.symmetric(
                        vertical: 6.0, horizontal: 0),
                    child: Row(
                      crossAxisAlignment: CrossAxisAlignment.center,
                      children: [
                        CircleAvatar(
                          foregroundColor: Colors.transparent,
                          backgroundImage: AssetImage('assets/head_icon/${option.image}'),
                        ),
                        const SizedBox(
                          width: 20,
                        ),
                        Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            Text.rich(formSpan(
                                option.name, _textEditingController.text)),
                            Text(
                              '性别:${option.man ? '女' : '男'}',
                              style: const TextStyle(color: Colors.grey),
                            )
                          ],
                        ),
                      ],
                    ),
                  ),
                );
              },
              itemCount: options.length,
            ),
          ),
        ),
      ),
    );
  }

设置与输入框匹配的内容高亮

  ///高亮某些文字
  final TextStyle lightTextStyle = const TextStyle(
    color: Colors.blue,
    fontWeight: FontWeight.bold,
  );
  InlineSpan formSpan(String src, String pattern) {
    List<TextSpan> span = [];
    List<String> parts = src.split(pattern);
    if (parts.length > 1) {
      for (int i = 0; i < parts.length; i++) {
        span.add(TextSpan(text: parts[i]));
        if (i != parts.length - 1) {
          span.add(TextSpan(text: pattern, style: lightTextStyle));
        }
      }
    } else {
      span.add(TextSpan(text: src));
    }
    return TextSpan(children: span);
  }

设置输入框样式:
_textEditingController 用于记录输入框的内容,_focusNode用于焦点的监听。
因为 optionsViewBuilder 回调中没有回调输入arg字符,所以_textEditingController用于记录输入的字符,在部分字体高亮下使用。
使用TextFormField 构建您想要的输入框样式

  Widget _buildFieldView(
      BuildContext context,
      TextEditingController textEditingController,
      FocusNode focusNode,
      VoidCallback onFieldSubmitted) {
    _textEditingController = textEditingController;
    _focusNode = focusNode;
    return SizedBox(
      height: 34,
      child: TextFormField(
        controller: textEditingController,
        focusNode: focusNode,
        keyboardType: TextInputType.text,
        decoration: const InputDecoration(
          fillColor: Color(0xFFF7F8FA),
          prefixIcon: Icon(Icons.search),
          filled: true,
          contentPadding: EdgeInsets.only(top: 1.0),
          border: UnderlineInputBorder(
            borderSide: BorderSide.none,
            borderRadius: BorderRadius.all(
              Radius.circular(19.0),
            ),
          ),
        ),
        onFieldSubmitted: (String value) {
          onFieldSubmitted();
        },
      ),
    );
  }

构造数据模型

class User {
  final String name;
  final bool man;
  final String image;
  const User(this.name, this.man, this.image);

  @override
  String toString() {
    return 'User{name:$name, man:$man, image:$image}';
  }
}

最终效果如第一个图

以下是全部代码:

///构造模型
class User {
  final String name;
  final bool man;
  final String image;
  const User(this.name, this.man, this.image);

  @override
  String toString() {
    return 'User{name:$name, man:$man, image:$image}';
  }
}

class AutocompletePage extends StatelessWidget {
  TextEditingController _textEditingController = TextEditingController();
  FocusNode _focusNode = FocusNode();
  User? user;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Autocomplete'),
        centerTitle: true,
      ),
      body: SizedBox(
        height: 300,
        child: Scaffold(
          appBar: AppBar(
            automaticallyImplyLeading: false,
            title: buildAutoComplete(),
          ),
        ),
      ),
    );
  }

  Widget buildAutoComplete() {
    return Autocomplete<User>(
      optionsBuilder: buildOptions,
      onSelected: onSelected,
      optionsViewBuilder: _buildOptionsView,
      fieldViewBuilder: _buildFieldView,
      displayStringForOption: (user) => user.name,
    );
  }

  Future<Iterable<User>> buildOptions(TextEditingValue textEditingValue) async {
    if (textEditingValue.text == '') {
      return const Iterable<User>.empty();
    }
    return searchByArgs(textEditingValue.text);
  }

  Future<Iterable<User>> searchByArgs(String args) async {
    // 模拟网络请求
    await Future.delayed(const Duration(milliseconds: 200));
    const List<User> data = [
      User('程咬金', false, 'icon_5.webp'),
      User('李元芳', false, 'icon_6.webp'),
      User('李白', false, 'icon_7.webp'),
      User('武则天', true, 'icon_8.webp'),
      User('王昭君', true, 'icon_6.webp'),
      User('妲己', true, 'icon_8.webp'),
      User('刘禅', false, 'icon_6.webp'),
      User('张飞', false, 'icon_8.webp'),
      User('刘邦', false, 'icon_5.webp'),
      User('李信', false, 'icon_6.webp'),
      User('anne', true, 'icon_8.webp'),
      User('andy', true, 'icon_5.webp'),
      User('joy', true, 'icon_7.webp'),
    ];
    return data.where((User user) => user.name.contains(args));
  }

  onSelected(User options) {
    _focusNode.unfocus(); //输入框失去焦点,收起键盘
    print('选择结束:${options}');
  }

  Widget _buildOptionsView(BuildContext context,
      AutocompleteOnSelected<User> onSelected, Iterable<User> options) {
    return Align(
      alignment: Alignment.topCenter,
      child: Padding(
        padding: const EdgeInsets.only(top: 20.0),
        child: Material(
          child: ConstrainedBox(
            constraints: const BoxConstraints(maxHeight: 150.0),
            child: ListView.builder(
              padding: EdgeInsets.zero,
              itemBuilder: (_, index) {
                final User option = options.elementAt(index);
                return InkWell(
                  onTap: () => onSelected.call(option),
                  child: Container(
                    padding: const EdgeInsets.symmetric(
                        vertical: 6.0, horizontal: 0),
                    child: Row(
                      crossAxisAlignment: CrossAxisAlignment.center,
                      children: [
                        CircleAvatar(
                          foregroundColor: Colors.transparent,
                          backgroundImage: AssetImage('assets/head_icon/${option.image}'),
                        ),
                        const SizedBox(
                          width: 20,
                        ),
                        Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            Text.rich(formSpan(
                                option.name, _textEditingController.text)),
                            Text(
                              '性别:${option.man ? '女' : '男'}',
                              style: const TextStyle(color: Colors.grey),
                            )
                          ],
                        ),
                      ],
                    ),
                  ),
                );
              },
              itemCount: options.length,
            ),
          ),
        ),
      ),
    );
  }

  ///高亮某些文字
  final TextStyle lightTextStyle = const TextStyle(
    color: Colors.blue,
    fontWeight: FontWeight.bold,
  );
  InlineSpan formSpan(String src, String pattern) {
    List<TextSpan> span = [];
    List<String> parts = src.split(pattern);
    if (parts.length > 1) {
      for (int i = 0; i < parts.length; i++) {
        span.add(TextSpan(text: parts[i]));
        if (i != parts.length - 1) {
          span.add(TextSpan(text: pattern, style: lightTextStyle));
        }
      }
    } else {
      span.add(TextSpan(text: src));
    }
    return TextSpan(children: span);
  }

  Widget _buildFieldView(
      BuildContext context,
      TextEditingController textEditingController,
      FocusNode focusNode,
      VoidCallback onFieldSubmitted) {
    _textEditingController = textEditingController;
    _focusNode = focusNode;
    return SizedBox(
      height: 34,
      child: TextFormField(
        controller: textEditingController,
        focusNode: focusNode,
        keyboardType: TextInputType.text,
        decoration: const InputDecoration(
          fillColor: Color(0xFFF7F8FA),
          prefixIcon: Icon(Icons.search),
          filled: true,
          contentPadding: EdgeInsets.only(top: 1.0),
          border: UnderlineInputBorder(
            borderSide: BorderSide.none,
            borderRadius: BorderRadius.all(
              Radius.circular(19.0),
            ),
          ),
        ),
        onFieldSubmitted: (String value) {
          onFieldSubmitted();
        },
      ),
    );
  }
}

demo路径下的 lib/views/autocomplete
链接: demo地址(https://github.com/Iamhopemiracle/function_project).

demo中包含其他功能,使用get框架配置

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值