Flutter学习笔记:04. Flutter 2

226 阅读4分钟

6. 表单

6.1. Switch

  • Switch
    • value (开关的值,一般与状态字段绑定)
    • onChanged (开关状态变更时调用)
    • activeColor (开关开启时的圆圈颜色)
    • activeTrackColor (开关开启时的轨道颜色)
    • inactiveThumbColor (开关关闭时的圆圈颜色)
    • inactiveTrackColor (开关关闭时的轨道颜色)
  • CupertinoSwitch (ios 风格的开关)
    • import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('Switch'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: const SwitchDemo(),
    );
  }
}

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

  @override
  State<SwitchDemo> createState() => _SwitchDemoState();
}

class _SwitchDemoState extends State<SwitchDemo> {
  bool _switchValue = false;

  @override
  Widget build(BuildContext context) {
    return Container(
      child: ListView(
        children: [
          ListTile(
            leading: Switch(
              value: _switchValue,
              activeColor: Colors.orange,
              activeTrackColor: Colors.pink,
              inactiveThumbColor: Colors.blue[100],
              inactiveTrackColor: Colors.grey,
              onChanged: (bool value) {
                setState(() {
                  _switchValue = value;
                });
              },
            ),
            title: Text('当前的状态:${_switchValue ? '选中' : '未选中'}'),
          ),
          ListTile(
            leading: CupertinoSwitch(
              value: _switchValue,
              activeColor: Colors.red,
              trackColor: Colors.yellow,
              onChanged: (bool value) {
                setState(() {
                  _switchValue = value;
                });
              },
            ),
            title: const Text('IOS风格的Switch'),
          ),
        ],
      ),
    );
  }
}

6.2. Checkbox

  • Checkbox
    • value (复选框的值)
    • onChanged (复选框状态更改时调用)
    • activeColor (选中时,复选框背景的颜色)
    • checkColor (选中时,复选框中对号的颜色)
  • CheckboxListTile
    • title (标题)
    • subtitle (子标题)

image.png

import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('CheckBox'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: const CheckBoxDemo(),
    );
  }
}

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

  @override
  State<CheckBoxDemo> createState() => _CheckBoxDemoState();
}

class _CheckBoxDemoState extends State<CheckBoxDemo> {
  bool _male = true;
  bool _female = false;
  bool _transgender = true;
  bool _value1 = true;
  bool _value2 = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ListTile(
          title: const Text('男'),
          leading: Checkbox(
            value: _male,
            onChanged: (value) {
              setState(() {
                _male = value!;
              });
            },
          ),
        ),
        ListTile(
          title: const Text('女'),
          leading: Checkbox(
            value: _female,
            onChanged: (value) {
              setState(() {
                _female = value!;
              });
            },
          ),
        ),
        ListTile(
          title: const Text('人妖'),
          leading: Checkbox(
            value: _transgender,
            activeColor: Colors.red,
            checkColor: Colors.green,
            onChanged: (value) {
              setState(() {
                _transgender = value!;
              });
            },
          ),
        ),
        CheckboxListTile(
          title: const Text('1:00 叫我起床'),
          subtitle: const Text('太困了,起不来'),
          secondary: const Icon(
            Icons.settings,
            size: 50,
          ),
          activeColor: Colors.red,
          checkColor: Colors.blue,
          selected: _value1,
          value: _value1,
          onChanged: (value) {
            setState(() {
              _value1 = value!;
            });
          },
        ),
        CheckboxListTile(
          title: const Text('2:00 叫我起床'),
          subtitle: const Text('这还差不多'),
          secondary: const Icon(
            Icons.settings,
            size: 50,
          ),
          value: _value2,
          onChanged: (value) {
            setState(() {
              _value2 = value!;
            });
          },
        ),
      ],
    );
  }
}

6.3. Radio

  • Radio (单选框)
    • value (开关的值,一般与状态字段绑定)
    • onChanged (开关状态变更时调用)
    • groupValue (选择组的值)
  • RadioListTile (单选列表)
    • value (开关的值,一般与状态字段绑定)
    • onChanged (开关状态变更时调用)
    • groupValue (选择组的值)

image.png

import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('Radio'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: const RadioDemo(),
    );
  }
}

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

  @override
  State<RadioDemo> createState() => _RadioDemoState();
}

class _RadioDemoState extends State<RadioDemo> {
  int gender = 1;

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text('男'),
              Radio(
                value: 1,
                groupValue: gender,
                onChanged: (value) {
                  setState(() {
                    gender = value!;
                  });
                },
              ),
              const Text('女'),
              Radio(
                value: 2,
                groupValue: gender,
                onChanged: (value) {
                  setState(() {
                    gender = value!;
                  });
                },
              ),
            ],
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text(gender == 1 ? '男' : '女'),
            ],
          ),
          RadioListTile(
            title: const Text('男性'),
            subtitle: const Text('有胡子'),
            selected: gender == 1,
            selectedTileColor: const Color.fromARGB(255, 240, 213, 137),
            secondary: const Icon(
              Icons.man,
              size: 32,
            ),
            value: 1,
            groupValue: gender,
            onChanged: (value) {
              setState(() {
                gender = value!;
              });
            },
          ),
          RadioListTile(
            title: const Text('女性'),
            subtitle: const Text('没有胡子'),
            selected: gender == 2,
            selectedTileColor: const Color.fromARGB(255, 240, 213, 137),
            secondary: const Icon(
              Icons.woman,
              size: 32,
            ),
            value: 2,
            groupValue: gender,
            onChanged: (value) {
              setState(() {
                gender = value!;
              });
            },
          ),
        ],
      ),
    );
  }
}

6.4. TextField

  • autofocus (是否获取焦点)
  • keyboardType (键盘类型)
  • obscureText (设置为密码框)
  • decoration (样式修饰)
  • onChanged (内容更改时自动调用 - value)
  • labelText (标题)
  • hintText (提示文字 - placeholder)
  • maxLines (显示行数-文本域)

image.png

import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('TextField'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: const TextFieldDemo(),
    );
  }
}

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

  @override
  State<TextFieldDemo> createState() => _TextFieldDemoState();
}

class _TextFieldDemoState extends State<TextFieldDemo> {
  late String phone;
  late String password;
  late String description;

  _register() {
    debugPrint(phone);
    debugPrint(password);
    debugPrint(description);
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(20),
      child: Column(
        children: [
          TextField(
            autofocus: true,
            keyboardType: TextInputType.phone,
            decoration: const InputDecoration(
              prefixIcon: Icon(Icons.mobile_screen_share),
              labelText: '手机号',
              hintText: '请输入手机号',
              hintStyle: TextStyle(
                color: Colors.green,
                fontSize: 14,
              ),
              // 获取焦点时,高亮的边框样式
              focusedBorder: UnderlineInputBorder(
                borderSide: BorderSide(
                  color: Colors.green,
                ),
              ),
              // 默认边框样式
              enabledBorder: UnderlineInputBorder(
                borderSide: BorderSide(
                  color: Colors.yellow,
                ),
              ),
            ),
            maxLength: 11,
            onChanged: (value) {
              setState(() {
                phone = value!;
              });
            },
          ),
          TextField(
            // 不可见
            obscureText: true,
            keyboardType: TextInputType.text,
            decoration: const InputDecoration(
              prefixIcon: Icon(Icons.remove_red_eye),
              labelText: '密码',
              hintText: '请输入密码',
              border: OutlineInputBorder(),
            ),
            onChanged: (value) {
              setState(() {
                password = value!;
              });
            },
          ),
          TextField(
            maxLines: 5,
            keyboardType: TextInputType.text,
            decoration: const InputDecoration(
              prefixIcon: Icon(Icons.person),
              labelText: '简介',
              hintText: '请介绍一下自己',
            ),
            onChanged: (value) {
              setState(() {
                description = value!;
              });
            },
          ),

          // 提交按钮
          Container(
            width: double.infinity,
            child: ElevatedButton(
              onPressed: () {
                _register();
              },
              child: const Text('提交'),
            ),
          ),
        ],
      ),
    );
  }
}

6.5. 日历

  • CalendarDatePicker (日历选择器)
    • initialCalendarMode
      • DatePickerMode.day
      • DatePickerMode.year

image.png

  • showDatePicker (日期选择器)
    • initialDatePickerMode (year | day)
    • initialEntryMode (calendar | input)

image.png

  • showTimePicker (时间选择器)

image.png

6.6. Form

  • 使用步骤

    • 创建表单 Form,并以 GlobalKey 作为唯一性标识
    • 添加带验证逻辑的 TextFormFieldForm
    • 创建按钮以验证和提交表单
  • Form (表单容器)

    • key (GlobalKey)
    • child (子组件)
  • TextFormField (输入框)

    • TextField 的区别: 必须在 Form 内使用 & 带有验证器
    • validator (验证器)
  • Form (表单容器)

    • 创建表单唯一键: final GlobalKey<FormState> formKey = GlobalKey<FormState>();
    • 验证表单: _formKey.currentState.validate()
    • 提交表单: _formKey.currentState.save[)
    • 重置表单: _formKey.currentState.reset()
  • TextFormField (输入框)

    • validator (验证器)
    • obscureText (密码框)
    • onSaved
      • 设定表单字段的值
      • 在表单的 save() 方法之后执行
import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 头部导航条
      appBar: AppBar(
        // 标题
        title: const Text('Form'),
        // 左侧插槽
        leading: const Icon(Icons.menu),
        // 右侧插槽
        actions: const [
          Icon(Icons.add_circle_outline),
        ],
        // 设置下边阴影
        elevation: 0.0,
        // 设置标题居中
        centerTitle: true,
      ),
      body: const FormDemo(),
    );
  }
}

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

  @override
  State<FormDemo> createState() => _FormDemoState();
}

class _FormDemoState extends State<FormDemo> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  late String _phone;
  late String _password;

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      padding: const EdgeInsets.all(20),
      child: Column(
        children: [
          Form(
            key: _formKey,
            child: Column(
              children: [
                TextFormField(
                  decoration: const InputDecoration(
                    hintText: '请输入手机号',
                  ),
                  validator: (value) {
                    RegExp reg = RegExp(r'^1\d{10}$');
                    if (!reg.hasMatch(value!)) {
                      return '请输入正确的手机号';
                    }
                    return null;
                  },
                  onSaved: (value) {
                    debugPrint('_phone onSaved');
                    _phone = value!;
                  },
                ),
                TextFormField(
                  obscureText: true,
                  decoration: const InputDecoration(
                    hintText: '请输入密码',
                  ),
                  validator: (value) {
                    return value!.length < 6 ? '密码长度不合法' : null;
                  },
                  onSaved: (value) {
                    debugPrint('_password onSaved');
                    _password = value!;
                  },
                ),
              ],
            ),
          ),
          Row(
            children: [
              Expanded(
                child: ElevatedButton(
                  child: const Text('提交'),
                  onPressed: () {
                    if (_formKey.currentState!.validate()) {
                      debugPrint('验证通过');
                      // 提交表单
                      debugPrint('_formKey.currentState!.save() - Before');
                      _formKey.currentState!.save();
                      debugPrint('_formKey.currentState!.save() - After');
                      debugPrint(_phone);
                      debugPrint(_password);
                    }
                  },
                ),
              ),
              const SizedBox(
                width: 20,
              ),
              Expanded(
                child: ElevatedButton(
                  onPressed: () {
                    _formKey.currentState!.reset();
                  },
                  child: const Text('重置'),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

7. 其他

7.1. 动画

  • Why
    • ui 界面设计合理的动画,可以让用户觉得更加流畅、直观,可以极大提高和改善用户体验。
  • What (实现原理)
    • 动画就是动起来的画面
    • 视觉暂留: 画面经视神经传入大脑后,不会立即消失(会存留一段时间)
    • 帧 (Frame) : 单个的画面,在学术上叫帧
    • 每秒中展示的帧数简称 fps (Frame per Second)

7.1.1. 动画的分类

  • 补间 (Tween)动画

    • 在补间动画中我们定义开始点结束点、时间线以及定义转换时间速度曲线。然后由系统计算,从开始点运动到结束点。从而形成动画效果。
    • 例如: 透明度从 0到 1,颜色值从 到 255
  • 拟物动画

    • 拟物动画是对真实世界的行为进行建模,,使动画效果类似于现实中的物理效果。
    • 例如: 弹簧,阻尼,重力,抛物线等
  • 动画 - Animation

  • Animation,是Flutter 动画库中的一个核心类。它包含动画的值和状态两个属性,定义了动画的一系列监听函数

    • 监听值:
      • addListener
      • removeListener
    • 监听状态:
      • addStatusListener
      • removeStatusListener
  • 动画状态

    • AnimationStatus.dismissed
      • 动画初始状态
    • AnimationStatus.completed
      • 动画结束状态
    • AnimationStatus.forward
      • 动画处在从开始到结束的运行状态
    • AnimationStatus.reverse
      • 动画处在从结束到开始的运行状态

image.png

  • AnimationController (动画控制器)

    • 在指定时间内,将组件属性值由初始值演变到终止值。从而形成动画效果
  • AnimationController 参数

    • duration (动画的执行时间)
    • reverseDuration (动画反向执行时间)
    • lowerBound = 0.0 (动画最小值)
    • upperBound = 1.0 (动画最大值)
    • value (动画初始值,默认是 lowerBound)
    • vsync (TickerProvider 类型的对象,用来创建 Ticker 对象)
  • 当创建一个 AnimationController 时,需要传递一个 vsync 参数

    • vsync 的作用是: 防止屏幕外动画 (动画页面切换到后台时)消耗不必要的资源
    • 通过将 SingleTickerProviderStateMixin 添加到类定义中,可以将 stateful 对象作为 vsync 的值。

image.png

  • AnimationController 具有控制动画的方法
    • forward() 可以正向执行动画
    • reverse() 可以反向执行动画
    • dispose() 用来释放动画资源(在不使用时需要调用该方法,否则会造成资源泄露)
    • stop() 用来停止动画运行

7.1.2. 动画 - Tween

  • 简介
    • AnimationController 动画生成值的默认区间是0.0到1.0,如果希望使用不同的区间,或不同的数据类型,需要使用 Tween (补间动画)。
    • Tween 的唯一职责就是定义从输入范围到输出范围的映射
    • 例如: 颜色区间是0到 255
  • Tween
    • Tween<double>(begin: 起始值, end: 终止值);
    • ColorTween(begin: Colors.withe, end: Colors.black);

7.1.3. 动画 - CurvedAnimation

  • 简介
    • 动画执行的速度有多种 (匀速、先快后慢或先慢后快) 这里的速度称为动画曲线
    • CurvedAnimation 的目的是为 AnimationController 添加动画曲线
  • 组件
    • CurvedAnimation(parent: controller, curve: Curves.easeln)
    • parent (动画控制器对象)
    • curve (正向执行的动画曲线)
    • reverseCurve (反向执行的动画曲线
  • Curves

7.1.4. 动画-步骤

  • 创建动画控制器
    • controller = AnimationController(duration, vsync)
  • 创建动画
    • 动画曲线 (CurvedAnimation)
    • 补间动画(Tween )
  • 监听动画
    • addListener() // 监听动画生成值
    • addStatusListener() // 监听动画状态
  • 执行动画
    • controller.forward() // 正向执行
    • controllerreverse() // 反向执行

7.2. 多语言

7.3. 多主题