popeyelau / wiki

📒Wiki for many useful notes, source, commands and snippets.
3 stars 0 forks source link

Flutter 表单处理 #33

Open popeyelau opened 3 years ago

popeyelau commented 3 years ago
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class LoginForm {
  String phone;
  String password;
  LoginForm({this.phone = "", this.password = ""});

  @override
  String toString() => '{"phone": "$phone", "password": "$password"}';
}

@immutable
class FormPage extends StatefulWidget {
  const FormPage({Key key}) : super(key: key);
  @override
  State<FormPage> createState() => _FormPageState();
}

class _FormPageState extends State<FormPage> {
  ///表单验证状态(控制登录按钮状态)
  final ValueNotifier<bool> _allFilled = ValueNotifier(false);

  ///表单数据
  LoginForm _loginForm;

  ///用户名
  FieldStatus<String, LoginForm> _phoneField;

  ///密码
  FieldStatus<String, LoginForm> _pwdField;

  ///表单项
  Iterable<FieldStatus> _fields;

  ///按钮焦点
  final FocusNode _buttonFocusNode = FocusNode();

  @override
  void initState() {
    super.initState();

    _loginForm = LoginForm();

    _phoneField = FieldStatus<String, LoginForm>(
      formData: _loginForm,
      value: (data) => data.phone,
      onChanged: (data, phone) => data.phone = phone,
      validator: (data, phone) => phone?.isEmpty ?? true ? '手机号不能为空' : null,
    );

    _pwdField = FieldStatus<String, LoginForm>(
      formData: _loginForm, 
      value: (data) => data.password,
      onChanged: (data, pwd) => data.password = pwd,
      validator: (data, pwd) => pwd?.isEmpty ?? true ? '密码不能为空' : null,
    );

    _fields = <FieldStatus>[_phoneField, _pwdField];
    _setValidationStatus();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Login")),
      body: SafeArea(
        child: Center(
          child: Card(
            margin: EdgeInsets.all(16),
            elevation: 4,
            clipBehavior: Clip.antiAlias,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.all(
                Radius.circular(8),
              ),
            ),
            child: Padding(padding: EdgeInsets.all(18), child: form),
          ),
        ),
      ),
    );
  }

  Widget get form => Form(
        autovalidateMode: AutovalidateMode.onUserInteraction,
        onChanged: _setValidationStatus,
        child: Builder(
          builder: (context) => SingleChildScrollView(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                TextFormField(
                  autofocus: true,
                  focusNode: _phoneField.focusNode,
                  onEditingComplete: _nextEmpty,
                  initialValue: _loginForm.phone,
                  onSaved: _phoneField.onChanged,
                  onChanged: _phoneField.onChanged,
                  validator: _phoneField.validator,
                  keyboardType: TextInputType.phone,
                  inputFormatters: [FilteringTextInputFormatter.digitsOnly],
                  decoration: const InputDecoration(labelText: '手机号'),
                ),
                const SizedBox(height: 10),
                TextFormField(
                  focusNode: _pwdField.focusNode,
                  onEditingComplete: _nextEmpty,
                  initialValue: _loginForm.password,
                  onSaved: _pwdField.onChanged,
                  onChanged: _pwdField.onChanged,
                  validator: _pwdField.validator,
                  keyboardType: TextInputType.text,
                  obscureText: true,
                  decoration: const InputDecoration(labelText: '密码'),
                ),
                const SizedBox(height: 10),
                ValueListenableBuilder<bool>(
                  valueListenable: _allFilled,
                  builder: (context, value, _) => RaisedButton(
                      shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.all(
                          Radius.circular(8),
                        ),
                      ),
                      focusNode: _buttonFocusNode,
                      color: Theme.of(context).primaryColor,
                      textColor: Theme.of(context).buttonColor,
                      onPressed: value ? () => _submit(context) : null,
                      child: const Text('登录')),
                ),
              ],
            ),
          ),
        ),
      );

  void _nextEmpty() {
    final fieldNode = _fields
        .firstWhere((field) => !field.filled, orElse: () => null)
        ?.focusNode;
    if (fieldNode == null) {
      _unfocusAll();
    } else {
      fieldNode.requestFocus();
    }
  }

  void _unfocusAll() {
    _fields
        .map<FocusNode>((field) => field.focusNode)
        .forEach((node) => node.unfocus());
  }

  void _setValidationStatus() {
    _allFilled.value = _fields.every((field) => field.filled);
  }

  void _submit(BuildContext context) {
    _unfocusAll();
    final form = Form.of(context)..save();
    if (!form.validate()) {
      _nextEmpty();
      return;
    }
    Scaffold.of(context).showSnackBar(
      SnackBar(
        content: Text('SUBMIT: $_loginForm'),
      ),
    );
  }
}

@immutable
class FieldStatus<FieldType, FormDataType> {
  final FormDataType _formData;

  bool get filled => !hasValidator || validator(value(_formData)) == null;

  final FieldType Function(FormDataType data) value;

  final FocusNode focusNode;

  ValueChanged<FieldType> get onChanged =>
      (value) => _onChanged(_formData, value);
  final void Function(FormDataType data, FieldType value) _onChanged;

  FormFieldValidator<FieldType> get validator =>
      (value) => hasValidator ? _validator(_formData, value) : null;
  final String Function(FormDataType data, FieldType value) _validator;

  final bool hasValidator;

  ///表单项状态
  ///[formData] 表单数据
  ///[value] 表单项 (formData.xxx)
  ///[onChanged] 数据更新
  ///[validator] 校验方法
  FieldStatus({
    @required FormDataType formData,
    @required this.value,
    @required void Function(FormDataType data, FieldType value) onChanged,
    String Function(FormDataType data, FieldType value) validator,
    FocusNode focusNode,
  })  : _formData = formData,
        focusNode = focusNode ?? FocusNode(),
        _onChanged = onChanged,
        _validator = validator,
        hasValidator = validator != null;
}

DartPad: https://dartpad.dev/b6409e10de32b280b8938aa75364fa7b