Hi everyone, Made a custom TextFormField widget for myself, because the standard widget does not provide some features. I hope the developers will expand the possibilities in the future. Perhaps it will be useful for you too.[01.JPG]Create a widget, copy the code, and create parameters with the data types specified in the code:
import 'package:flutter/services.dart';
class DTextFormField extends StatefulWidget {
const DTextFormField({
Key key,
this.width,
this.height,
this.pValue,
this.pMaxLines,
this.pMaxLength,
this.pLengthCounter,
this.pIntegerCharacters,
this.pDoubleCharacters,
this.pMinNumberValue,
this.pMaxNumberValue,
this.pFieldStateOnSubmit,
this.pReadOnly,
this.pFontFamily,
this.pFontSize,
this.pFontWeight,
this.pTextColor,
this.pLabel,
this.pHint,
this.pHelperText,
this.pErrorText,
this.pPrefix,
this.pSuffix,
this.pPrefixIcon,
this.pPrefixIconType,
this.pSuffixIcon,
this.pSuffixIconType,
this.pSuffixIconHide,
this.pSuffixIconCopyTap,
this.pSuffixIconClearTap,
this.pCursorColor,
this.pFilled,
this.pFillColor,
this.pUnderlineInputBorder,
this.pBorderRadius,
this.pBorderWidth,
this.pEnableBorderColor,
this.pFocusBorderColor,
this.pAutofocus,
this.pDense,
}) : super(key: key);
// need to create the following settings:
final double width;
final double height;
final String pValue;
final int pMaxLines;
final int pMaxLength;
final bool pLengthCounter;
final bool pIntegerCharacters; // only integer input if true
final bool pDoubleCharacters; // only double input if true
final double pMinNumberValue; // available: - and +
final double pMaxNumberValue; // available: - and +
// updating local state variable on submit, need to create local state, look below comments
final bool pFieldStateOnSubmit;
final bool pReadOnly;
final String pFontFamily; // input font name like: Poppins
final double pFontSize;
final double pFontWeight; // add from 0 to 1
final Color pTextColor;
final String pLabel;
final String pHint;
final String pHelperText;
final String pErrorText;
final String pPrefix;
final String pSuffix;
final bool pPrefixIcon;
final Widget pPrefixIconType;
final bool pSuffixIcon;
final Widget pSuffixIconType;
final bool pSuffixIconHide; // if true icon hiding if text added
final bool pSuffixIconCopyTap; // copy to clipboard tap action
final bool pSuffixIconClearTap; // text clear tap action
final Color pCursorColor;
final bool pFilled;
final Color pFillColor;
final bool pUnderlineInputBorder; // if false: OutlineInputBorder
final double pBorderRadius; // all 4 corners
final double pBorderWidth; // focus and enable
final Color pEnableBorderColor;
final Color pFocusBorderColor;
final bool pAutofocus;
final bool pDense;
//------------------------------------------------------
// customize and optimize to your requirements :)
//------------------------------------------------------
@override
_DTextFormFieldState createState() => _DTextFormFieldState();
}
class _DTextFormFieldState extends State {
String _text;
final textController = TextEditingController();
_changeText() {
setState(() => _text = textController.text);
}
@override
void initState() {
_text = widget.pValue; // initial value
super.initState();
textController.text = _text;
// start listening to changes
textController.addListener(_changeText);
}
@override
void dispose() {
// cleaning up the controller
textController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// define value types
var allCharacters = RegExp(r'.*');
var textType = TextInputType.text;
if (widget.pIntegerCharacters == true) {
allCharacters = RegExp(r'^-?[0-9]*');
textType = TextInputType.number;
}
if (widget.pDoubleCharacters == true) {
allCharacters = RegExp(r'^[-]?\d*(\.\d*)?$');
textType = const TextInputType.numberWithOptions(decimal: true);
}
FilteringTextInputFormatter textCharacters =
FilteringTextInputFormatter.allow(allCharacters);
if (widget.pIntegerCharacters == true || widget.pDoubleCharacters == true) {
// seting digits available range between min & max
if (widget.pMinNumberValue != null && widget.pMaxNumberValue != null) {
final dtext = num.tryParse(textController.text)?.toDouble();
if (dtext < widget.pMinNumberValue && dtext != null) {
textController.text = widget.pMinNumberValue.toString();
textController.selection = TextSelection.fromPosition(
TextPosition(offset: textController.text.length));
}
if (dtext > widget.pMaxNumberValue && dtext != null) {
textController.text = widget.pMaxNumberValue.toString();
textController.selection = TextSelection.fromPosition(
TextPosition(offset: textController.text.length));
}
}
if (textController.text == '-.') {
textController.text = '-0.';
textController.selection = TextSelection.fromPosition(
TextPosition(offset: textController.text.length));
}
if (textController.text == '.') {
textController.text = '0.';
textController.selection = TextSelection.fromPosition(
TextPosition(offset: textController.text.length));
}
}
// widget state value
// create local state: fieldStateOnChange / rename everywehere if needed / activate code below
/*
setState(
() => FFAppState().fieldStateOnChange = textController.text.toString());
*/
return TextFormField(
// key: Key(textController.text.toString()), // -- update testing
controller: textController,
// create local state: fieldStateOnSubmit / rename everywehere if needed / activate code below
/*
onFieldSubmitted: widget.pFieldStateOnSubmit == true
? (text) => setState(() =>
FFAppState().fieldStateOnSubmit = textController.text.toString())
: null,
*/
autofocus: widget.pAutofocus,
obscureText: false,
// initialValue: widget.pValue, // not needed because text controller is used
// maxLength and invisible counter
maxLength: widget.pMaxLength,
buildCounter: widget.pLengthCounter == false
? (BuildContext context,
{int currentLength, int maxLength, bool isFocused}) =>
null
: null,
// allowed characters with textCharacters, look above
keyboardType: textType,
inputFormatters: [
textCharacters,
LengthLimitingTextInputFormatter(
widget.pMaxLength,
),
],
maxLines: widget.pMaxLines,
readOnly: widget.pReadOnly,
cursorColor: widget.pCursorColor,
// styles
decoration: InputDecoration(
prefixIcon: widget.pPrefixIcon == true ? widget.pPrefixIconType : null,
suffixIcon: widget.pSuffixIcon == true &&
(widget.pSuffixIconHide == false ||
widget.pSuffixIconHide == true &&
textController.text.isNotEmpty)
? InkWell(
onTap: (widget.pSuffixIconClearTap == true ||
widget.pSuffixIconCopyTap == true) &&
textController.text.isNotEmpty
? () => setState(
() {
if (widget.pSuffixIconCopyTap == true) {
Clipboard.setData(
ClipboardData(text: textController.text));
}
if (widget.pSuffixIconClearTap == true) {
textController.clear();
}
},
)
: null,
child: widget.pSuffixIconType,
)
: null,
prefix: Text(widget.pPrefix),
suffix: Text(widget.pSuffix),
labelText: widget.pLabel.toString().isNotEmpty ? widget.pLabel : null,
hintText: widget.pHint.toString().isNotEmpty ? widget.pHint : null,
helperText: widget.pHelperText.toString().isNotEmpty
? widget.pHelperText
: null,
errorText:
widget.pErrorText.toString().isNotEmpty ? widget.pErrorText : null,
filled: widget.pFilled,
fillColor: widget.pFillColor.value != null
? widget.pFillColor
: Colors.transparent,
enabledBorder: widget.pUnderlineInputBorder == false
? OutlineInputBorder(
borderSide: BorderSide(
color: widget.pEnableBorderColor,
width: widget.pBorderWidth,
),
borderRadius: widget.pBorderRadius != null
? BorderRadius.circular(widget.pBorderRadius)
: 0,
)
: UnderlineInputBorder(
borderSide: BorderSide(
color: widget.pEnableBorderColor,
width: widget.pBorderWidth,
),
borderRadius: widget.pBorderRadius != null
? BorderRadius.circular(widget.pBorderRadius)
: 0,
),
focusedBorder: widget.pUnderlineInputBorder == false
? OutlineInputBorder(
borderSide: BorderSide(
color: widget.pFocusBorderColor,
width: widget.pBorderWidth,
),
borderRadius: widget.pBorderRadius != null
? BorderRadius.circular(widget.pBorderRadius)
: 0,
)
: UnderlineInputBorder(
borderSide: BorderSide(
color: widget.pFocusBorderColor,
width: widget.pBorderWidth,
),
borderRadius: widget.pBorderRadius != null
? BorderRadius.circular(widget.pBorderRadius)
: 0,
),
isDense: widget.pDense,
),
style: TextStyle(
fontFamily: widget.pFontFamily,
fontSize: widget.pFontSize,
fontWeight: FontWeight.lerp(
FontWeight.w100, FontWeight.w900, widget.pFontWeight),
color: widget.pTextColor,
),
);
}
}
Also you will need to create 2 local states with string type. Read the comments in the code. An example of how to use the error text option: [02.JPG]Custom function example in pErrorText parameter:[03.JPG]But it doesn't work as a validate, it's a page refresh issue. SetState and Key don't work correctly. I didn't put the widget in pub.dev and decided to put it here. Of course, it would be convenient to have a base of custom Flutter Flow platform widgets instead of pub, including templates that could be copied to projects with one click. Also, in each project, the widget needs to be created anew, but it would be more convenient to indicate the attribute of the global widget, for example: project / own account / all users. But this question is for the developers. Maybe the code needs some optimization. Optimize and customize as needed. Perhaps there will be other ideas for the development of this widget, which could be placed in the comments of this post 🙂 Have a good day! ✌️