【开源鸿蒙跨平台开发--Flutter】第三部分:交互的核心——表单与反馈

在前面的章节中,我们已经构建了精美的静态页面(布局 + 样式)并处理了长列表(滚动)。现在的关键问题是:如何让用户与 App 进行交互? 比如登录输入密码、点击开关、弹出提示框等。

第三部分将聚焦于:

  1. 表单与输入 (Input & Forms):获取用户输入的核心组件,包括文本框、开关、单选框以及表单验证机制。
  2. 弹窗与反馈 (Dialogs & Alerts):如何优雅地打断用户或给予操作反馈。

第三部分:交互的核心——表单与反馈

第五章:表单与输入组件 (Input & Forms)

1. TextField (文本输入框)

说明:
TextField 是最常用的文本输入组件。它极其强大,属性繁多,可以定制键盘类型、外观装饰、密码隐藏等。

核心属性 (Properties):

属性名类型说明
controllerTextEditingController最重要。用于控制输入框的内容、获取输入值、监听变化。
decorationInputDecoration用于控制输入框的外观(提示文字、边框、图标等)。
keyboardTypeTextInputType键盘类型 (text, number, emailAddress, phone)。
obscureTextbool是否隐藏输入内容(用于密码),默认为 false
onChangedFunction(String)文本发生变化时的回调。
maxLinesint最大行数。设为 1 为单行输入;设为 null 为多行自动伸缩。

InputDecoration 详解:

  • labelText: 悬浮标签(获取焦点时会上浮)。
  • hintText: 提示占位符(类似 HTML placeholder)。
  • prefixIcon / suffixIcon: 头部/尾部图标(如搜索图标、密码眼睛图标)。
  • border / enabledBorder / focusedBorder: 定义不同状态下的边框样式。

代码示例 (基础输入):

import 'package:flutter/material.dart';

class TextFieldExample extends StatefulWidget {
  
  _TextFieldExampleState createState() => _TextFieldExampleState();
}

class _TextFieldExampleState extends State<TextFieldExample> {
  // 1. 创建控制器
  final TextEditingController _userController = TextEditingController();
  final TextEditingController _pwdController = TextEditingController();

  
  void dispose() {
    // 记得销毁控制器,释放内存
    _userController.dispose();
    _pwdController.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 用户名输入框
        TextField(
          controller: _userController,
          decoration: InputDecoration(
            labelText: "用户名",
            hintText: "请输入手机号/邮箱",
            prefixIcon: Icon(Icons.person),
            border: OutlineInputBorder(), // 外边框风格
          ),
        ),
        SizedBox(height: 20),
        // 密码输入框
        TextField(
          controller: _pwdController,
          obscureText: true, // 隐藏文字
          decoration: InputDecoration(
            labelText: "密码",
            prefixIcon: Icon(Icons.lock),
            suffixIcon: Icon(Icons.visibility), // 这里通常配合逻辑做点击切换显示/隐藏
          ),
        ),
        ElevatedButton(
          onPressed: () {
            // 获取输入内容
            print("User: ${_userController.text}, Pwd: ${_pwdController.text}");
          },
          child: Text("登录"),
        )
      ],
    );
  }
}

2. Form & TextFormField (表单验证)

说明:
如果只是简单的输入,用 TextField 即可。但如果需要校验(如:必填、邮箱格式错误提示),则必须使用 Form 包裹 TextFormField

机制:

  1. 使用 GlobalKey<FormState> 绑定 Form 组件。
  2. 使用 TextFormField 替代 TextField,它多了一个 validator 属性。
  3. 通过 Key 调用 validate() 方法触发所有子项的校验。

代码示例 (带验证的表单):

import 'package:flutter/material.dart';

class FormExample extends StatefulWidget {
  
  _FormExampleState createState() => _FormExampleState();
}

class _FormExampleState extends State<FormExample> {
  final _formKey = GlobalKey<FormState>(); // 1. 全局 Key
  String _email = "";

  
  Widget build(BuildContext context) {
    return Form(
      key: _formKey, // 绑定 Key
      child: Column(
        children: [
          TextFormField(
            decoration: InputDecoration(labelText: "邮箱"),
            // 2. 校验逻辑
            validator: (value) {
              if (value == null || value.isEmpty) {
                return '邮箱不能为空';
              }
              if (!value.contains('@')) {
                return '请输入有效的邮箱格式';
              }
              return null; // 返回 null 代表校验通过
            },
            onSaved: (value) => _email = value!,
          ),
          SizedBox(height: 20),
          ElevatedButton(
            onPressed: () {
              // 3. 触发校验
              if (_formKey.currentState!.validate()) {
                _formKey.currentState!.save(); // 调用 onSaved 保存数据
                print("表单提交成功,邮箱: $_email");
              }
            },
            child: Text("提交"),
          )
        ],
      ),
    );
  }
}

3. 开关与选择 (Switch, Checkbox, Radio, Slider)

说明:
这些组件用于处理布尔值或选项选择。它们的共同点是无状态 (Stateless) 的外观,必须通过 onChanged 回调更新父组件的状态值(State),界面才会变化。

常用组件:

  1. Switch (开关): 类似 iOS 的设置开关。
  2. Checkbox (复选框): 方形打钩框。
  3. Radio (单选框): 圆形选择框,通常成组出现。
  4. Slider (滑块): 拖动选择数值。

代码示例:

import 'package:flutter/material.dart';

class SelectionExample extends StatefulWidget {
  
  _SelectionExampleState createState() => _SelectionExampleState();
}

class _SelectionExampleState extends State<SelectionExample> {
  bool _isSwitched = false;
  bool _isChecked = false;
  int _radioValue = 1;
  double _sliderValue = 50.0;

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 1. 开关
        SwitchListTile(
          title: Text("接收通知"),
          value: _isSwitched,
          onChanged: (val) {
            setState(() => _isSwitched = val);
          },
        ),

        // 2. 复选框
        CheckboxListTile(
          title: Text("同意用户协议"),
          value: _isChecked,
          onChanged: (val) {
            setState(() => _isChecked = val!);
          },
        ),

        // 3. 单选框 (男/女)
        Row(
          children: [
            Text("性别: "),
            Radio(
              value: 1, // 该选项代表的值
              groupValue: _radioValue, // 当前选中的值
              onChanged: (int? val) {
                setState(() => _radioValue = val!);
              },
            ),
            Text("男"),
            Radio(
              value: 2,
              groupValue: _radioValue,
              onChanged: (int? val) {
                setState(() => _radioValue = val!);
              },
            ),
            Text("女"),
          ],
        ),
        
        // 4. 滑块
        Slider(
          value: _sliderValue,
          min: 0,
          max: 100,
          divisions: 10, // 分成10档
          label: _sliderValue.round().toString(),
          onChanged: (val) {
             setState(() => _sliderValue = val);
          },
        )
      ],
    );
  }
}

第六章:弹窗与提示 (Dialogs & Alerts)

在 Flutter 中,弹窗通常通过“路由”机制推入一个新的层级。

1. AlertDialog (对话框)

说明:
标准的 Material Design 对话框,包含标题、内容和操作按钮。通常配合 showDialog 函数使用。

注意: showDialog 是一个异步方法 (Future),当对话框关闭时返回结果。

代码示例:

ElevatedButton(
  child: Text("删除文件"),
  onPressed: () async {
    // 弹出对话框
    bool? delete = await showDialog<bool>(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: Text("警告"),
          content: Text("确定要删除这个文件吗?此操作无法撤销。"),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(false), // 关闭并返回 false
              child: Text("取消"),
            ),
            TextButton(
              onPressed: () => Navigator.of(context).pop(true), // 关闭并返回 true
              child: Text("删除", style: TextStyle(color: Colors.red)),
            ),
          ],
        );
      },
    );

    if (delete == true) {
      print("执行删除操作...");
    }
  },
);

2. SimpleDialog (简单选择框)

说明:
用于提供多个选项供用户选择。

代码示例:

SimpleDialog(
  title: Text('选择语言'),
  children: <Widget>[
    SimpleDialogOption(
      onPressed: () { Navigator.pop(context, '中文'); },
      child: Padding(
        padding: EdgeInsets.symmetric(vertical: 10),
        child: Text('简体中文'),
      ),
    ),
    SimpleDialogOption(
      onPressed: () { Navigator.pop(context, 'English'); },
      child: Padding(
        padding: EdgeInsets.symmetric(vertical: 10),
        child: Text('English'),
      ),
    ),
  ],
);

3. BottomSheet (底部弹窗)

说明:
从屏幕底部滑出的面板,常用于选择器或更多操作菜单。使用 showModalBottomSheet 触发。

代码示例:

ElevatedButton(
  child: Text("更换头像"),
  onPressed: () {
    showModalBottomSheet(
      context: context,
      builder: (BuildContext context) {
        return Container(
          height: 150,
          color: Colors.white,
          child: Column(
            children: [
              ListTile(
                leading: Icon(Icons.camera_alt),
                title: Text("拍照"),
                onTap: () => Navigator.pop(context),
              ),
              ListTile(
                leading: Icon(Icons.photo),
                title: Text("从相册选择"),
                onTap: () => Navigator.pop(context),
              ),
            ],
          ),
        );
      },
    );
  },
);

4. SnackBar (底部轻提示)

说明:
在屏幕底部短暂弹出的黑色提示条,通常用于提示“加载成功”、“已删除”等状态。不会打断用户操作。

重要: Flutter 2.0 之后,推荐使用 ScaffoldMessenger 来管理 SnackBar。

代码示例:

ElevatedButton(
  child: Text("撤销操作"),
  onPressed: () {
    final snackBar = SnackBar(
      content: Text('邮件已删除'),
      duration: Duration(seconds: 3), // 持续时间
      action: SnackBarAction(
        label: '撤销',
        onPressed: () {
          // 这里执行撤销逻辑
          print("用户点击了撤销");
        },
      ),
    );

    // 显示 SnackBar
    ScaffoldMessenger.of(context).showSnackBar(snackBar);
  },
);

第三部分总结:
在本部分中,你学会了:

  1. TextFieldForm:如何让用户输入数据并进行格式校验。
  2. StatefulWidget 交互:通过 Switch、Checkbox 等组件理解了 Flutter “状态驱动 UI” 的理念。
  3. Dialog 和 SnackBar:掌握了 App 中最常用的反馈机制。

现在的你已经具备了开发一个功能性 App(如待办事项清单、登录注册页)的核心能力。


后续预告:
App 通常不仅仅只有一个页面。
第四部分 将进入 多页面管理与导航 (Navigation & Routing)
我们将学习:

  • 如何从 A 页面跳转到 B 页面(Navigator.push)。
  • 如何传递参数(例如点击商品列表进入详情页)。
  • 底部导航栏 (BottomNavigationBar) 和 侧边栏 (Drawer) 的完整实现。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值