Open popeyelau opened 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
DartPad: https://dartpad.dev/b6409e10de32b280b8938aa75364fa7b