Flutter 2.0 - 自动完成小部件从右侧退出屏幕

问题描述

如何使自动完成框与没有特定宽度的 TextField 大小相同,它占用最大宽度。

              Autocomplete(
                optionsBuilder: (TextEditingValue textEditingValue) {
                  if (textEditingValue.text == '') {
                    return ['aa','bb','cc','aa','cc'];
                  }
                  return ['aa','cc']
                      .where((String option) {
                    return option
                        .toString()
                        .contains(textEditingValue.text.toLowerCase());
                  });
                },onSelected: (option) {
                  print(option);
                },),

enter image description here

解决方法

我在尝试基于 RawAutocomplete 制作我自己的自动完成小部件时遇到了同样的问题,我使用布局构建器在我的 optionsViewBuilder 中获得完美的容器宽度大小:

LayoutBuilder(
  builder: (context,constraints) => RawAutocomplete<String>(
    focusNode: focusNode,fieldViewBuilder: fieldViewBuilder,optionsViewBuilder: (context,onSelected,options) => Align(
      alignment: Alignment.topLeft,child: Material(
        shape: const RoundedRectangleBorder(
          borderRadius: BorderRadius.vertical(bottom: Radius.circular(4.0)),),child: Container(
          height: 52.0 * options.length,width: constraints.biggest.width,// <-- Right here !
          child: ListView.builder(
            padding: EdgeInsets.zero,itemCount: options.length,shrinkWrap: false,itemBuilder: (BuildContext context,int index) {
              final String option = options.elementAt(index);
              return InkWell(
                onTap: () => onSelected(option),child: Padding(
                  padding: const EdgeInsets.all(16.0),child: Text(option),);
            },optionsBuilder: (textEditingValue) =>
        suggestions.where((element) => element.contains(textEditingValue.text.trim().toUpperCase())),textEditingController: controller,)

结果: Expected result on my component

,

我也经常遇到这个问题,选项构建器非常具体地说明了它希望将大小或填充放置在层次结构中的位置。这是我的工作示例:

optionsViewBuilder: (context,onAutoCompleteSelect,options) {
  return Align(
    alignment: Alignment.topLeft,child: Material(
         color: Theme.of(context).primaryColorLight,elevation: 4.0,// size works,when placed here below the Material widget
           child: Container(
              // I have the text field wrapped in a container with
              // EdgeInsets.all(20) so subtract 40 from the width for the width
              // of the text box. You could also just use a padding widget
              // with EdgeInsets.only(right: 20)
              width: MediaQuery.of(context).size.width - 40,child: ListView.separated(
                   shrinkWrap: true,padding: const EdgeInsets.all(8.0),separatorBuilder: (context,i) {
                     return Divider();
                   },int index) {
                     // some child here
                   },)
              ),)
        );
     }

,

您是否尝试将整个自动完成小部件放在一个大小合适的框中? 如果大小调整小部件不起作用,这可能是一个错误。

,

我遇到了同样的问题,并通过使用 ValueListenableBuilder 解决了它。

Material 小部件的宽度取决于 field ViewBuilder 返回的小部件的宽度(默认为 TextFormField),因此一种解决方案是在之后获得 TextFormField 的宽度完成一帧并通知您的自定义optionsViewBuilder

示例代码:

import 'package:flutter/material.dart';

class AutocompleteText extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => AutocompleteTextState();
}

class AutocompleteTextState extends State<AutocompleteText> {
  final ValueNotifier<double?> optionsViewWidthNotifier = ValueNotifier(null);

  static const List<User> _userOptions = <User>[
    User(name: 'Alice',email: 'alice@example.com'),User(name: 'Bob',email: 'bob@example.com'),User(name: 'Charlie',email: 'charlie123@gmail.com'),];

  static String _displayStringForOption(User option) => option.name;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Padding(
      padding: EdgeInsets.all(20),child: Autocomplete<User>(
        displayStringForOption: _displayStringForOption,optionsBuilder: (TextEditingValue textEditingValue) {
          if (textEditingValue.text == '') {
            return const Iterable<User>.empty();
          }
          return _userOptions.where((User option) {
            return option
                .toString()
                .contains(textEditingValue.text.toLowerCase());
          });
        },onSelected: (User selection) {
          print('You just selected ${_displayStringForOption(selection)}');
        },fieldViewBuilder: (BuildContext context,TextEditingController textEditingController,FocusNode focusNode,VoidCallback onFieldSubmitted) {
          return OrientationBuilder(builder: (context,orientation) {
            WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
              optionsViewWidthNotifier.value =
                  (context.findRenderObject() as RenderBox).size.width;
            });
            return TextFormField(
              controller: textEditingController,focusNode: focusNode,onFieldSubmitted: (String value) {
                onFieldSubmitted();
              },);
          });
        },optionsViewBuilder: (
          BuildContext context,AutocompleteOnSelected<User> onSelected,Iterable<User> options,) {
          return ValueListenableBuilder<double?>(
              valueListenable: optionsViewWidthNotifier,builder: (context,width,_child) {
                return Align(
                  alignment: Alignment.topLeft,child: Material(
                    elevation: 4.0,child: SizedBox(
                      width: width,height: 200.0,child: ListView.builder(
                        padding: EdgeInsets.zero,int index) {
                          final User option = options.elementAt(index);
                          return InkWell(
                            onTap: () {
                              onSelected(option);
                            },child: Padding(
                              padding: const EdgeInsets.all(16.0),child: Text(_displayStringForOption(option)),);
                        },);
              });
        },));
  }

  @override
  void dispose() {
    optionsViewWidthNotifier.dispose();
    super.dispose();
  }
}

@immutable
class User {
  const User({
    required this.email,required this.name,});

  final String email;
  final String name;

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

  @override
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType) {
      return false;
    }
    return other is User && other.name == name && other.email == email;
  }

  @override
  int get hashCode => hashValues(email,name);
}