Skip to main content

处理用户输入

现在您已经了解了如何在 Flutter 应用中管理状态,那么如何让用户与您的应用交互并更改其状态呢?

处理用户输入简介

#

作为一个多平台 UI 框架,用户与 Flutter 应用交互的方式有很多种。本节中的资源将向您介绍一些常用的小部件,这些小部件用于启用应用内的用户交互。

一些用户输入机制,例如滚动,已在布局中介绍过。

参考: 小部件目录包含 MaterialCupertino 库中常用小部件的清单。

接下来,我们将介绍一些支持在 Flutter 应用中处理用户输入的常见用例的 Material 小部件。

按钮

#

一系列 Material 3 按钮。

按钮允许用户通过点击或轻触来启动 UI 中的操作。Material 库提供了各种类型的按钮,它们的功能相似,但样式不同,适用于各种用例,包括:

  • ElevatedButton:带有一定深度的按钮。使用凸起按钮可以为原本大部分平面的布局添加维度。
  • FilledButton:填充按钮,应用于重要的最终操作,以完成流程,例如 保存立即加入 确认
  • Tonal ButtonFilledButtonOutlinedButton 之间的中间地带按钮。它们在需要比轮廓更高的优先级按钮的上下文中非常有用,例如 下一步
  • OutlinedButton:带有文本和可见边框的按钮。这些按钮包含重要的操作,但不是应用中的主要操作。
  • TextButton:可点击的文本,没有边框。由于文本按钮没有可见的边框,因此它们必须依赖于相对于其他内容的位置来确定上下文。
  • IconButton:带有图标的按钮。
  • FloatingActionButton:悬停在内容上以促进主要操作的图标按钮。

视频: 浮动操作按钮(每周的小部件)

构建按钮通常有三个主要方面:样式、回调及其子项,如下面的 ElevatedButton 代码示例所示:

  • 按钮的回调函数 onPressed 决定点击按钮时会发生什么,因此,此函数是您更新应用状态的地方。如果回调为 null,则按钮被禁用,并且用户按下按钮时不会发生任何事情。

  • 按钮的 child 显示在按钮的内容区域内,通常是文本或图标,指示按钮的目的。

  • 最后,按钮的 style 控制其外观:颜色、边框等等。

dart
int count = 0;

@override
Widget build(BuildContext context) {
  return ElevatedButton(
    style: ElevatedButton.styleFrom(
      textStyle: const TextStyle(fontSize: 20),
    ),
    onPressed: () {
      setState(() {
        count += 1;
      });
    },
    child: const Text('Enabled'),
  );
}

带有文本“启用”的 ElevatedButton 的 GIF
此图显示了一个带有文本“启用”的 ElevatedButton 被点击。


检查点: 完成本教程,学习如何构建“收藏”按钮:向您的 Flutter 应用添加交互性


API 文档: ElevatedButtonFilledButtonOutlinedButtonTextButtonIconButtonFloatingActionButton

文本

#

几个小部件支持文本输入。

SelectableText

#

Flutter 的 Text 小部件在屏幕上显示文本,但不允许用户突出显示或复制文本。SelectableText 显示用户可以选择的一系列文本。

dart
@override
Widget build(BuildContext context) {
  return const SelectableText('''
两个家庭,地位相同,
在美丽的维罗纳,我们设置场景,
从古老的怨恨爆发为新的叛乱,
内战的鲜血使公民的手变得不洁。
从这两个敌人的致命腰部开始''');
}

一个光标突出显示段落中两行文本的 GIF。
此图显示一个光标突出显示文本字符串的一部分。

视频: SelectableText(每周的小部件)

RichText

#

RichText 允许您在应用中显示富文本字符串。TextSpanRichText 类似,允许您以不同的文本样式显示文本的部分。它不用于处理用户输入,但如果您允许用户编辑和格式化文本,则非常有用。

dart
@override
Widget build(BuildContext context) {
  return RichText(
    text: TextSpan(
      text: 'Hello ',
      style: DefaultTextStyle.of(context).style,
      children: const <TextSpan>[
        TextSpan(text: 'bold', style: TextStyle(fontWeight: FontWeight.bold)),
        TextSpan(text: ' world!'),
      ],
    ),
  );
}

“Hello bold world!”文本的屏幕截图,“bold”一词以粗体显示。
此图显示一个用不同的文本样式格式化的文本字符串。

视频: 富文本(每周的小部件)

演示: 富文本编辑器

代码: 富文本编辑器代码

TextField

#

TextField 允许用户使用硬件或屏幕键盘在文本框中输入文本。

TextField 具有许多不同的属性和配置。一些亮点:

  • InputDecoration 确定文本字段的外观,例如颜色和边框。
  • controllerTextEditingController 控制正在编辑的文本。为什么您可能需要一个控制器?默认情况下,您的应用用户可以在文本字段中键入文本,但如果您想以编程方式控制 TextField 并清除其值(例如),则需要 TextEditingController
  • onChanged:当用户更改文本字段的值时(例如插入或删除文本时),此回调函数会触发。
  • onSubmitted:当用户指示他们已完成对字段中文本的编辑时,此回调将被触发;例如,当文本字段处于焦点时,点击“Enter”键。

此类支持其他可配置属性,例如 obscureText,它会将其输入的每个字母都转换为 readOnly 圆圈,以及 readOnly,它可以防止用户更改文本。

dart
final TextEditingController _controller = TextEditingController();

@override
Widget build(BuildContext context) {
  return TextField(
    controller: _controller,
    decoration: const InputDecoration(
      border: OutlineInputBorder(),
      labelText: 'Mascot Name',
    ),
  );
}

一个带有标签“吉祥物名称”的文本字段的 GIF,紫色焦点边框和正在输入的短语“Dash the hummingbird”。
此图显示文本被输入到带有选定边框和标签的 TextField 中。

检查点: 完成这个包含 4 个部分的 cookbook 系列教程,该教程将引导您逐步创建文本字段、检索其值并更新您的应用状态:

  1. 创建和设置文本字段的样式
  2. 检索文本字段的值
  3. 处理文本字段的更改
  4. 焦点和文本字段

表单

#

Form 是一个可选容器,用于将多个表单字段小部件(例如 TextField)组合在一起。

每个单独的表单字段都应包装在一个 FormField 小部件中,并以 Form 小部件作为公共祖先。存在预先将表单字段小部件包装在 FormField 中的便捷小部件。 例如,TextFieldForm 小部件版本是 TextFormField

使用 Form 可以访问 FormState,它允许您保存、重置和验证从此 Form 派生的每个 FormField。您还可以提供 GlobalKey 来标识特定表单,如下面的代码所示:

dart
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();

@override
Widget build(BuildContext context) {
  return Form(
    key: _formKey,
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        TextFormField(
          decoration: const InputDecoration(
            hintText: 'Enter your email',
          ),
          validator: (String? value) {
            if (value == null || value.isEmpty) {
              return 'Please enter some text';
            }
            return null;
          },
        ),
        Padding(
          padding: const EdgeInsets.symmetric(vertical: 16.0),
          child: ElevatedButton(
            onPressed: () {
              // Validate returns true if the form is valid, or false otherwise.
              if (_formKey.currentState!.validate()) {
                // Process data.
              }
            },
            child: const Text('Submit'),
          ),
        ),
      ],
    ),
  );
}

检查点: 完成本教程,学习如何[构建带有验证的表单][]。

演示: 表单应用

代码: 表单应用代码


API 文档: TextFieldRichTextSelectableTextForm

从一组选项中选择一个值

#

为用户提供从多个选项中选择的方法。

SegmentedButton

#

SegmentedButton 允许用户从最少 2-5 个项目组中进行选择。

数据类型 <T> 可以是内置类型,例如 intStringbool 或枚举。SegmentedButton 有一些相关的属性:

  • segments,一个 ButtonSegment 列表,其中每个表示用户可以选择的一个“段”或选项。在视觉上,每个 ButtonSegment 可以具有图标、文本标签或两者兼有。

  • multiSelectionEnabled 指示用户是否可以选中多个选项。此属性默认为 false。

  • selected 标识当前选定的值。注意:selected 的类型为 Set<T>,因此,如果您只允许用户选择一个值,则必须将该值作为包含单个元素的 Set 提供。

  • 当用户选择任何段时,onSelectionChanged 回调将被触发。它提供所选段的列表,以便您可以更新应用程序状态。

  • 其他样式参数允许您修改按钮的外观。例如,style 获取 ButtonStyle,提供了一种配置 selectedIcon 的方法。

dart
enum Calendar { day, week, month, year }

// StatefulWidget...
Calendar calendarView = Calendar.day;

@override
Widget build(BuildContext context) {
  return SegmentedButton<Calendar>(
    segments: const <ButtonSegment<Calendar>>[
      ButtonSegment<Calendar>(
          value: Calendar.day,
          label: Text('Day'),
          icon: Icon(Icons.calendar_view_day)),
      ButtonSegment<Calendar>(
          value: Calendar.week,
          label: Text('Week'),
          icon: Icon(Icons.calendar_view_week)),
      ButtonSegment<Calendar>(
          value: Calendar.month,
          label: Text('Month'),
          icon: Icon(Icons.calendar_view_month)),
      ButtonSegment<Calendar>(
          value: Calendar.year,
          label: Text('Year'),
          icon: Icon(Icons.calendar_today)),
    ],
    selected: <Calendar>{calendarView},
    onSelectionChanged: (Set<Calendar> newSelection) {
      setState(() {
        // 默认情况下,一次只能选择一个段,因此其值始终是第一个
        calendarView = newSelection.first;
      });
    },
  );
}

一个 SegmentedButton 的 GIF,它有 4 个段:日、周、月和年。每个都有一个日历图标来表示其值和文本标签。首先选中日,然后是周和月,最后是年。
此图显示一个 SegmentedButton,每个段都带有图标和表示其值的文本。

Chip

#

Chip 是以简洁的方式表示特定上下文中的属性、文本、实体或操作的一种方法。针对特定用例存在专门的 Chip 小部件:

  • InputChip 以简洁的形式表示复杂的信息,例如实体(人、地点或事物)或会话文本。
  • ChoiceChip 允许从一组选项中进行单选。Choice chip 包含相关的描述性文本或类别。
  • FilterChip 使用标签或描述性词语来过滤内容。
  • ActionChip 表示与主要内容相关的操作。

每个 Chip 小部件都需要一个 label。它可以选择具有 avatar(例如图标或用户的个人资料图片)和 onDeleted 回调,该回调显示一个删除图标,当触发时,会删除该 chip。Chip 小部件的外观也可以通过设置许多可选参数(例如 shapecoloriconTheme)来自定义。

您通常会使用 Wrap(一个以多行水平或垂直方式显示其子项的小部件)来确保您的 chip 包装起来并且不会被应用的边缘切断。

dart
@override
Widget build(BuildContext context) {
  return const SizedBox(
    width: 500,
    child: Wrap(
      alignment: WrapAlignment.center,
      spacing: 8,
      runSpacing: 4,
      children: [
        Chip(
          avatar: CircleAvatar(
              backgroundImage: AssetImage('assets/images/dash_chef.png')),
          label: Text('Chef Dash'),
        ),
        Chip(
          avatar: CircleAvatar(
              backgroundImage:
                  AssetImage('assets/images/dash_firefighter.png')),
          label: Text('Firefighter Dash'),
        ),
        Chip(
          avatar: CircleAvatar(
              backgroundImage: AssetImage('assets/images/dash_musician.png')),
          label: Text('Musician Dash'),
        ),
        Chip(
          avatar: CircleAvatar(
              backgroundImage: AssetImage('assets/images/dash_artist.png')),
          label: Text('Artist Dash'),
        ),
      ],
    ),
  );
}

一个屏幕截图,显示 4 个 Chip 分成两行,每个 Chip 都有一个圆形的引导个人资料图像和内容文本。
此图显示两行 Chip 小部件,每个小部件都包含一个圆形的引导个人资料图像和内容文本。

#

DropdownMenu 允许用户从选项菜单中选择一个选项并将所选文本放入 TextField 中。它还允许用户根据文本输入过滤菜单项。

配置参数包括以下内容:

  • dropdownMenuEntries 提供一个 DropdownMenuEntry 列表,用于描述每个菜单项。菜单可能包含文本标签以及引导或尾随图标等信息。(这也是唯一必需的参数。)
  • TextEditingController 允许以编程方式控制 TextField
  • 当用户选择一个选项时,onSelected 回调将被触发。
  • initialSelection 允许您配置默认值。
  • 还提供其他参数来定制小部件的外观和行为。
dart
enum ColorLabel {
  blue('Blue', Colors.blue),
  pink('Pink', Colors.pink),
  green('Green', Colors.green),
  yellow('Orange', Colors.orange),
  grey('Grey', Colors.grey);

  const ColorLabel(this.label, this.color);
  final String label;
  final Color color;
}

// StatefulWidget...
@override
Widget build(BuildContext context) {
  return DropdownMenu<ColorLabel>(
    initialSelection: ColorLabel.green,
    controller: colorController,
    // requestFocusOnTap is enabled/disabled by platforms when it is null.
    // On mobile platforms, this is false by default. Setting this to true will
    // trigger focus request on the text field and virtual keyboard will appear
    // afterward. On desktop platforms however, this defaults to true.
    requestFocusOnTap: true,
    label: const Text('Color'),
    onSelected: (ColorLabel? color) {
      setState(() {
        selectedColor = color;
      });
    },
    dropdownMenuEntries: ColorLabel.values
      .map<DropdownMenuEntry<ColorLabel>>(
          (ColorLabel color) {
            return DropdownMenuEntry<ColorLabel>(
              value: color,
              label: color.label,
              enabled: color.label != 'Grey',
              style: MenuItemButton.styleFrom(
                foregroundColor: color.color,
              ),
            );
      }).toList(),
  );
}

一个选定的 DropdownMenu 小部件的 GIF,它显示 5 个选项:蓝色、粉色、绿色、橙色和灰色。选项文本以其值的颜色显示。
此图显示一个带有 5 个值选项的 DropdownMenu 小部件。每个选项的文本颜色都经过样式化处理以表示颜色值。

视频: DropdownMenu(每周的小部件)

Slider

#

Slider 小部件允许用户通过移动指示器(例如音量条)来调整值。

Slider 小部件的配置参数:

  • value 表示滑块的当前值
  • onChanged 是在移动滑块手柄时触发的回调
  • minmax 建立滑块允许的最小值和最大值
  • divisions 建立用户可以沿轨道移动手柄的离散间隔。
dart
double _currentVolume = 1;

@override
Widget build(BuildContext context) {
  return Slider(
    value: _currentVolume,
    max: 5,
    divisions: 5,
    label: _currentVolume.toString(),
    onChanged: (double value) {
      setState(() {
        _currentVolume = value;
      });
    },
  );
}

一个滑块的 GIF,其旋钮以 1 为增量从左向右拖动,从 0.0 到 5.0
此图显示一个滑块小部件,其值范围为 0.0 到 5.0,分为 5 个部分。它在拖动旋钮时将当前值显示为标签。

视频: Slider、RangeSlider、CupertinoSlider(每周的小部件)


API 文档: SegmentedButtonDropdownMenuSliderChip

在值之间切换

#

您的 UI 可以通过多种方式允许在值之间切换。

复选框、开关和单选按钮

#

提供一个选项来打开和关闭单个值。这些小部件背后的功能逻辑相同,因为所有三个都是基于 ToggleableStateMixin 构建的,尽管每个都提供了细微的演示差异:

  • Checkbox 是一个容器,当为 false 时为空,当为 true 时填充复选标记。
  • Switch 有一个手柄,当为 false 时在左边,当为 true 时滑动到右边。
  • RadioCheckbox 类似,它是一个容器,当为 false 时为空,但当为 true 时填充。

CheckboxSwitch 的配置包含:

  • 一个值为 truefalsevalue
  • 一个 onChanged 回调,当用户切换小部件时触发

复选框

#
dart
bool isChecked = false;

@override
Widget build(BuildContext context) {
  return Checkbox(
    checkColor: Colors.white,
    value: isChecked,
    onChanged: (bool? value) {
      setState(() {
        isChecked = value!;
      });
    },
  );
}

一个 GIF,显示一个指针单击一个复选框,然后再次单击以取消选中它。
此图显示一个复选框被选中和取消选中。

开关

#
dart
bool light = true;

@override
Widget build(BuildContext context) {
  return Switch(
    // 此布尔值切换开关。
    value: light,
    activeColor: Colors.red,
    onChanged: (bool value) {
      // 当用户切换开关时调用此函数。
      setState(() {
        light = value;
      });
    },
  );
}

一个 Switch 小部件的 GIF,它被打开和关闭。在其关闭状态下,它是灰色的,带有深灰色边框。在其打开状态下,它是红色的,带有浅红色边框。
此图显示一个被打开和关闭的 Switch 小部件。

单选按钮

#

一组 Radio 按钮,允许用户在互斥值之间进行选择。当用户选择组中的一个单选按钮时,其他单选按钮将被取消选择。

  • 特定 Radio 按钮的 value 表示该按钮的值,
  • 一组单选按钮的选定值由 groupValue 参数标识。
  • Radio 还具有一个 onChanged 回调,当用户单击它时会触发,就像 SwitchCheckbox 一样
dart
enum Character { musician, chef, firefighter, artist }

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

  @override
  State<RadioExample> createState() => _RadioExampleState();
}

class _RadioExampleState extends State<RadioExample> {
  Character? _character = Character.musician;

  void setCharacter(Character? value) {
    setState(() {
      _character = value;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        ListTile(
          title: const Text('Musician'),
          leading: Radio<Character>(
            value: Character.musician,
            groupValue: _character,
            onChanged: setCharacter,
          ),
        ),
        ListTile(
          title: const Text('Chef'),
          leading: Radio<Character>(
            value: Character.chef,
            groupValue: _character,
            onChanged: setCharacter,
          ),
        ),
        ListTile(
          title: const Text('Firefighter'),
          leading: Radio<Character>(
            value: Character.firefighter,
            groupValue: _character,
            onChanged: setCharacter,
          ),
        ),
        ListTile(
          title: const Text('Artist'),
          leading: Radio<Character>(
            value: Character.artist,
            groupValue: _character,
            onChanged: setCharacter,
          ),
        ),
      ],
    );
  }
}

一个 GIF,显示一列 4 个 ListTile,每个 ListTile 包含一个引导单选按钮和标题文本。单选按钮从上到下按顺序选择。
此图显示一列包含单选按钮和标签的 ListTile,一次只能选择一个单选按钮。

附加内容:CheckboxListTile & SwitchListTile

#

这些便捷小部件与复选框和小部件相同,但支持标签(作为 ListTile)。

dart
double timeDilation = 1.0;
bool _lights = false;

@override
Widget build(BuildContext context) {
  return Column(
    children: [
      CheckboxListTile(
        title: const Text('Animate Slowly'),
        value: timeDilation != 1.0,
        onChanged: (bool? value) {
          setState(() {
            timeDilation = value! ? 10.0 : 1.0;
          });
        },
        secondary: const Icon(Icons.hourglass_empty),
      ),
      SwitchListTile(
        title: const Text('Lights'),
        value: _lights,
        onChanged: (bool value) {
          setState(() {
            _lights = value;
          });
        },
        secondary: const Icon(Icons.lightbulb_outline),
      ),
    ],
  );
}

一个 ListTile,包含一个引导图标、标题文本和一个正在选中和取消选中的尾随复选框。它还显示一个 ListTile,包含一个引导图标、标题文本和一个正在打开和关闭的开关。
此图显示一列包含 CheckboxListTile 和 SwitchListTile 的内容,它们正在切换。

视频: CheckboxListTile(每周的小部件)

视频: SwitchListTile(每周的小部件)


API 文档: CheckboxCheckboxListTileSwitchSwitchListTileRadio

选择日期或时间

#

提供小部件,以便用户可以选择日期和时间。

有一组对话框允许用户选择日期或时间,您将在以下部分看到。除了不同的日期类型——日期为 DateTime,时间为 TimeOfDay 之外——这些对话框的功能相似,您可以通过提供以下内容来配置它们:

  • 默认的 initialDateinitialTime
  • 或确定显示的选取器 UI 的 initialEntryMode

DatePickerDialog

#

此对话框允许用户选择一个日期或一系列日期。通过调用 showDatePicker 函数激活,该函数返回 Future<DateTime>,因此不要忘记等待异步函数调用!

dart
DateTime? selectedDate;

@override
Widget build(BuildContext context) {
  var date = selectedDate;

  return Column(children: [
    Text(
      date == null
          ? "You haven't picked a date yet."
          : DateFormat('MM-dd-yyyy').format(date),
    ),
    ElevatedButton.icon(
      icon: const Icon(Icons.calendar_today),
      onPressed: () async {
        var pickedDate = await showDatePicker(
          context: context,
          initialEntryMode: DatePickerEntryMode.calendarOnly,
          initialDate: DateTime.now(),
          firstDate: DateTime(2019),
          lastDate: DateTime(2050),
        );

        setState(() {
          selectedDate = pickedDate;
        });
      },
      label: const Text('Pick a date'),
    )
  ]);
}

一个指针单击一个显示“选择日期”的按钮的 GIF,然后显示一个日期选择器。选中 8 月 30 日(星期五)的日期,然后单击“确定”按钮。
此图显示当单击“选择日期”按钮时显示的 DatePicker。

TimePickerDialog

#

TimePickerDialog 是一个显示时间选择器的对话框。可以通过调用 showTimePicker() 函数激活它。showTimePicker 返回 Future<TimeOfDay>,而不是返回 Future<DateTime>。同样,不要忘记等待函数调用!

dart
TimeOfDay? selectedTime;

@override
Widget build(BuildContext context) {
  var time = selectedTime;

  return Column(children: [
    Text(
      time == null ? "You haven't picked a time yet." : time.format(context),
    ),
    ElevatedButton.icon(
      icon: const Icon(Icons.calendar_today),
      onPressed: () async {
        var pickedTime = await showTimePicker(
          context: context,
          initialEntryMode: TimePickerEntryMode.dial,
          initialTime: TimeOfDay.now(),
        );

        setState(() {
          selectedTime = pickedTime;
        });
      },
      label: const Text('Pick a date'),
    )
  ]);
}

一个指针单击一个显示“选择时间”的按钮的 GIF,然后显示一个时间选择器。时间选择器显示一个圆形时钟,当光标移动时针、分针、选择 PM,然后单击“确定”按钮。
此图显示当单击“选择时间”按钮时显示的 TimePicker。


API 文档: showDatePickershowTimePicker

滑动和滑动

#

Dismissible 是一个小部件,它允许用户通过滑动来将其关闭。

它有很多配置参数,包括:

  • 一个 child 小部件
  • 当用户滑动时触发的 onDismissed 回调
  • 样式参数,例如 background
  • 同样重要的是要包含一个 key 对象,以便可以从窗口小部件树中同级 Dismissible 小部件唯一地识别它们。
dart
List<int> items = List<int>.generate(100, (int index) => index);

@override
Widget build(BuildContext context) {
  return ListView.builder(
    itemCount: items.length,
    padding: const EdgeInsets.symmetric(vertical: 16),
    itemBuilder: (BuildContext context, int index) {
      return Dismissible(
        background: Container(
          color: Colors.green,
        ),
        key: ValueKey<int>(items[index]),
        onDismissed: (DismissDirection direction) {
          setState(() {
            items.removeAt(index);
          });
        },
        child: ListTile(
          title: Text(
            'Item ${items[index]}',
          ),
        ),
      );
    },
  );
}

三个小部件的屏幕截图,彼此均匀间隔。
此图显示一系列 Dismissible 小部件,每个小部件都包含一个 ListTile。在 ListTile 上滑动会显示绿色背景,使磁贴消失。

视频: Dismissible(每周的小部件)

检查点: 完成本教程,学习如何使用 dismissible 小部件[实现滑动以关闭][]。


API 文档: Dismissible

寻找更多小部件?

#

此页面仅介绍了可在 Flutter 应用中用于处理用户输入的一些常用 Material 小部件。查看[Material 小部件库][]和Material 库 API 文档以获取小部件的完整列表。

演示: 查看 Flutter 的Material 3 演示,了解 Material 库中提供的精选用户输入小部件示例。

如果 Material 和 Cupertino 库没有满足您需求的小部件,请查看 pub.dev 以查找 Flutter 和 Dart 社区拥有和维护的包。例如,flutter_slidable 包提供了一个 Slidable 小部件,它比上一节中描述的 Dismissible 小部件更具可定制性。

视频: flutter_slidable(每周的包)

使用 GestureDetector 构建交互式小部件

#

您是否已经仔细检查了小部件库、pub.dev,询问了您的编码朋友,仍然找不到符合您正在寻找的用户交互的小部件?您可以构建自己的自定义小部件,并使用 GestureDetector 使其具有交互性。

检查点: 使用此配方作为起点,创建您自己的自定义按钮小部件,该小部件可以处理点击

视频: GestureDetector(每周的小部件)

参考: 查看点击、拖动和其他手势,它解释了如何在 Flutter 中侦听和响应手势。

附加视频: 好奇 Flutter 的 GestureArena 如何将原始用户交互数据转换为人类可识别的概念,如点击、拖动和捏合?查看此视频:GestureArena(解码 Flutter)

不要忘记辅助功能!

#

如果您正在构建自定义小部件,请使用 Semantics 小部件对其含义进行注释。它为屏幕阅读器和其他基于语义分析的工具提供描述和元数据。

视频: Semantics(Flutter 每周的小部件)


API 文档: GestureDetectorSemantics

测试

#

完成在应用中构建用户交互后,不要忘记编写测试以确保一切按预期工作!

这些教程将引导您编写模拟应用中用户交互的测试:

检查点: 按照此[点击、拖动和输入文本][] cookbook 文章,学习如何使用 WidgetTester 模拟和测试应用中的用户交互。

附加教程: [处理滚动][] cookbook 配方向您展示了如何通过使用窗口小部件测试滚动列表来验证窗口小部件列表是否包含预期内容。

下一步:网络

#

此页面介绍了如何处理用户输入。现在您已经了解了如何处理来自应用程序用户的输入,您可以通过添加外部数据来使您的应用程序更有趣。在下一节中,您将学习如何通过网络为您的应用程序获取数据,如何将数据在 JSON 之间进行转换,身份验证以及其他网络功能。

反馈

#

由于本网站的此部分正在不断发展,我们欢迎您的反馈