既然你已经了解如何在 Flutter 应用中管理状态,那么如何让用户与你的应用互动并改变其状态呢?

用户输入处理简介

#

作为一款多平台 UI 框架,Flutter 有许多不同的方式让用户与应用互动。本节资源将向你介绍一些用于在应用中实现用户互动的常用组件。

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

参考:该组件目录包含了MaterialCupertino 库中常用的组件。

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

按钮

#

A collection of Material 3 Buttons.

按钮允许用户通过点击或轻触来在 UI 中发起操作。Material 库提供了多种功能相似但针对不同用例进行了不同样式设计的按钮类型,包括

  • ElevatedButton: 一种带有深度感的按钮。使用凸起按钮为原本扁平的布局增加立体感。
  • FilledButton: 一种填充按钮,应用于完成流程的重要最终操作,例如保存立即加入确认
  • Tonal Button: 介于 FilledButtonOutlinedButton 之间的一种中等按钮。当优先级较低的按钮需要比轮廓线更强的强调时,它们非常有用,例如下一步
  • OutlinedButton: 一种带有文本和可见边框的按钮。这些按钮包含重要的操作,但不是应用中的主要操作。
  • TextButton: 可点击的文本,没有边框。由于文本按钮没有可见边框,它们必须依靠其相对于其他内容的位置来提供上下文信息。
  • IconButton: 一种带图标的按钮。
  • FloatingActionButton: 一种浮动在内容上方以突出主要操作的图标按钮。

视频FloatingActionButton(本周组件)

构建按钮通常有 3 个主要方面:样式、回调和其子组件,如下面的 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'),
  );
}
A GIF of an elevated button with the text "Enabled"
此图显示了一个带有文本“启用”的 ElevatedButton 被点击。

检查点:完成此教程,学习如何构建一个“收藏”按钮:为你的 Flutter 应用添加交互性


API 文档ElevatedButtonFilledButtonOutlinedButtonTextButtonIconButtonFloatingActionButton

文本

#

有几种组件支持文本输入。

SelectableText

#

Flutter 的 Text 组件在屏幕上显示文本,但用户无法高亮或复制文本。SelectableText 显示一段用户可选的文本。

dart
@override
Widget build(BuildContext context) {
  return const SelectableText('''
Two households, both alike in dignity,
In fair Verona, where we lay our scene,
From ancient grudge break to new mutiny,
Where civil blood makes civil hands unclean.
From forth the fatal loins of these two foes''');
}
A GIF of a cursor highlighting two lines of text from a paragraph.
此图显示了光标高亮显示文本字符串的一部分。

视频SelectableText(本周组件)

RichText

#

RichText 允许你在应用中显示富文本字符串。TextSpan,与 RichText 类似,允许你用不同的文本样式显示文本的一部分。它不是用于处理用户输入,但如果你允许用户编辑和格式化文本,它会很有用。

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!'),
      ],
    ),
  );
}
A screenshot of the text "Hello bold world!" with the word "bold" in bold font.
此图显示了一段使用不同文本样式格式化的文本字符串。

视频富文本(本周组件)

演示富文本编辑器

代码富文本编辑器代码

TextField

#

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

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

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

该类支持其他可配置属性,例如 obscureText 会将每个字母变成一个密文圆点(输入时),以及 readOnly,它会阻止用户更改文本。

dart
final TextEditingController _controller = TextEditingController();

@override
Widget build(BuildContext context) {
  return TextField(
    controller: _controller,
    decoration: const InputDecoration(
      border: OutlineInputBorder(),
      labelText: 'Mascot Name',
    ),
  );
}
A GIF of a text field with the label "Mascot Name", purple focus border and the phrase "Dash the hummingbird" being typed in.
此图显示了文本被输入到带有选中边框和标签的 TextField 中。

检查点:完成这个分为 4 部分的烹饪指南系列,它将指导你如何创建一个文本字段、获取其值并更新你的应用状态

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

Form

#

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

每个单独的表单字段都应该封装在 FormField 组件中,并以 Form 组件作为共同的祖先。存在一些便利组件,它们会为你预先将表单字段组件封装在 FormField 中。例如,TextField 组件的表单版本是 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(() {
        Suggested change
        // By default there is only a single segment that can be
        // selected at one time, so its value is always the first
        // By default, only a single segment can be
        // selected at one time, so its value is always the first
        calendarView = newSelection.first;
      });
    },
  );
}
A GIF of a SegmentedButton with 4 segments: Day, Week, Month, and Year.
Each has a calendar icon to represent its value and a text label.
Day is first selected, then week and month, then year.
此图显示了一个 SegmentedButton,每个段都带有图标和文本来表示其值。

Chip

#

Chip 是一种紧凑的方式,用于表示特定上下文的属性、文本、实体或操作。针对特定用例存在专门的 Chip 组件:

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

每个 Chip 组件都需要一个 label。它可以选择性地包含一个 avatar(例如图标或用户头像)和一个 onDeleted 回调,当触发时显示删除图标并删除该筹码。一个 Chip 组件的外观也可以通过设置一些可选参数(例如 shapecoloriconTheme)进行自定义。

你通常会使用 Wrap,一个将其子组件显示在多行水平或垂直排列中的组件,以确保你的筹码能够换行,而不会在应用边缘被截断。

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'),
        ),
      ],
    ),
  );
}
A screenshot of 4 Chips split over two rows with a leading circular
profile image with content text.
此图显示了两行 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(),
  );
}
A GIF the DropdownMenu widget that is selected, it displays 5 options:
Blue, Pink, Green, Orange, and Grey. The option text is displayed in the color
of its value.
此图显示了一个 DropdownMenu 组件,包含 5 个值选项。每个选项的文本颜色都经过样式设置,以代表其颜色值。

视频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;
      });
    },
  );
}
A GIF of a slider that has the dial dragged left to right in increments 
of 1, from 0.0 to 5.0
此图显示了一个滑块组件,其值范围从 0.0 到 5.0,分为 5 个刻度。它在拖动时将当前值显示为标签。

视频Slider, RangeSlider, CupertinoSlider(本周组件)


API 文档: SegmentedButtonDropdownMenuSliderChip

在值之间切换

#

你的 UI 有几种方式可以实现值之间的切换。

Checkbox, Switch, and Radio

#

提供一个选项来切换单个值的开关。这些组件的底层功能逻辑是相同的,因为所有 3 个组件都基于 ToggleableStateMixin 构建,尽管每个组件在表现形式上略有不同。

  • Checkbox 是一个容器,当值为 false 时为空,当值为 true 时填充一个勾选标记。
  • Switch 有一个滑块,当值为 false 时在左侧,当值为 true 时滑到右侧。
  • RadioCheckbox 相似,它是一个容器,当值为 false 时为空,但当值为 true 时被填充。

CheckboxSwitch 的配置包含:

  • 一个 value,值为 truefalse
  • 和一个 onChanged 回调,当用户切换组件时触发

Checkbox

#
dart
bool isChecked = false;

@override
Widget build(BuildContext context) {
  return Checkbox(
    checkColor: Colors.white,
    value: isChecked,
    onChanged: (bool? value) {
      setState(() {
        isChecked = value!;
      });
    },
  );
}
A GIF that shows a pointer clicking a checkbox 
and then clicking again to uncheck it.
此图显示了一个复选框被选中和取消选中。

Switch

#
dart
bool light = true;

@override
Widget build(BuildContext context) {
  return Switch(
    // This bool value toggles the switch.
    value: light,
    activeColor: Colors.red,
    onChanged: (bool value) {
      // This is called when the user toggles the switch.
      setState(() {
        light = value;
      });
    },
  );
}
A GIF of a Switch widget that is toggled on and off. In its off state,
it is gray with dark gray borders. In its on state, 
it is red with a light red border.
此图显示了一个 Switch 组件被切换开和关。

Radio

#

一组 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,
          ),
        ),
      ],
    );
  }
}
A GIF of 4 ListTiles in a column, each containing a leading Radio button
and title text. The Radio buttons are selected in order from top to bottom.
此图显示了一列包含单选按钮和标签的 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),
      ),
    ],
  );
}
A ListTile with a leading icon, title text, and a trailing checkbox being
checked and unchecked. It also shows a ListTile with a leading icon, title text
and a switch being toggled on and off.
此图显示了一列包含 CheckboxListTile 和 SwitchListTile 被切换的状态。

视频CheckboxListTile(本周组件)

视频SwitchListTile(本周组件)


API 文档CheckboxCheckboxListTileSwitchSwitchListTileRadio

选择日期或时间

#

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

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

  • 默认的 initialDateinitialTime
  • 或一个 initialEntryMode 它决定了所显示的拾取器 UI。

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'),
    )
  ]);
}
A GIF of a pointer clicking a button that says "Pick a date",
then shows a date picker. The date Friday, August 30 is selected and the "OK"
button is clicked.
此图显示了一个 DatePicker,当“选择日期”按钮被点击时显示。

TimePickerDialog

#

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

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 time'),
    )
  ]);
}
A GIF of a pointer clicking a button that says "Pick a time", then shows
 a time picker. The time picker shows a circular clock as the cursor moves the 
 hour hand, then minute hand, selects PM, then the "OK" button is clicked.
此图显示了一个 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]}',
          ),
        ),
      );
    },
  );
}
A screenshot of three widgets, spaced evenly from each other.
此图显示了一系列 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

测试

#

在你的应用中构建完用户交互后,别忘了编写测试以确保一切按预期工作!

这些教程将指导你如何编写模拟应用中用户交互的测试

检查点:遵循此轻触、拖动和输入文本烹饪指南文章,学习如何使用 WidgetTester 来模拟和测试应用中的用户交互。

额外教程:该处理滚动烹饪指南教程展示了如何通过使用组件测试滚动列表来验证组件列表是否包含预期内容。

下一篇:网络

#

本页是用户输入处理的简介。既然你已经知道如何处理应用用户的输入,你可以通过添加外部数据来让你的应用更加有趣。在下一节中,你将学习如何通过网络为你的应用获取数据、如何将数据与 JSON 相互转换、身份验证以及其他网络功能。

反馈

#

随着本网站的这一部分不断发展,我们欢迎你的反馈