CustomTextField
A beautiful, animated, and highly customizable TextField widget for Flutter with smooth animations, elegant design, and comprehensive features.
Features
- Beautiful Animations: Smooth border color, width, and fill color transitions
- Fully Customizable: Every aspect can be customized including colors, borders, typography
- Theme Adaptive: Automatically adapts to light/dark themes
- Password Support: Built-in obscure text functionality
- Input Types: Support for all keyboard types and input actions
- Form Validation: Built-in validator support with error styling
- Icons Support: Prefix and suffix icons with proper spacing
- Multi-line Support: Configurable for single or multi-line input
- Focus Management: Advanced focus handling with animations
- Performance Optimized: Efficient animations with proper resource management
Installation
Add the following to your pubspec.yaml:
dependencies:
simple_dynamic_textfield: ^1.0.0
Then run:
flutter pub get
Quick Start
Import the package
import 'package:simple_dynamic_textfield/custom_textfield.dart';
Basic Usage
class MyForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CustomTextField(
label: 'Enter your name',
hint: 'John Doe',
),
),
);
}
}
Complete Examples
1. Email Input Field
import 'package:flutter/material.dart';
import 'package:simple_dynamic_textfield/custom_textfield.dart'; // Adjust the import as needed
class EmailInputExample extends StatelessWidget {
final TextEditingController emailController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Email Input')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: CustomTextField(
label: 'Email Address',
hint: 'Enter your email',
controller: emailController,
keyboardType: TextInputType.emailAddress,
prefixIcon: Icon(Icons.email_outlined),
validator: (value) {
if (value?.isEmpty ?? true) return 'Email is required';
if (!value!.contains('@')) return 'Invalid email format';
return null;
},
onChanged: (value) {
print('Email: $value');
},
),
),
);
}
}
void main() {
runApp(MaterialApp(
home: EmailInputExample(),
));
}
2. Password Field with Toggle
class PasswordField extends StatefulWidget {
@override
_PasswordFieldState createState() => _PasswordFieldState();
}
class _PasswordFieldState extends State<PasswordField> {
bool _obscureText = true;
final TextEditingController _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return CustomTextField(
label: 'Password',
hint: 'Enter your password',
controller: _passwordController,
obscureText: _obscureText,
prefixIcon: Icon(Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(_obscureText ? Icons.visibility : Icons.visibility_off),
onPressed: () {
setState(() {
_obscureText = !_obscureText;
});
},
),
validator: (value) {
if (value?.isEmpty ?? true) return 'Password is required';
if (value!.length < 6) return 'Password must be at least 6 characters';
return null;
},
);
}
}
3. Search Field with Custom Styling
import 'package:flutter/material.dart';
import 'package:simple_dynamic_textfield/custom_textfield.dart'; // Ensure this matches your package name
class SearchFieldExample extends StatefulWidget {
@override
_SearchFieldExampleState createState() => _SearchFieldExampleState();
}
class _SearchFieldExampleState extends State<SearchFieldExample> {
String _searchQuery = '';
void _performSearch(String query) {
setState(() {
_searchQuery = query;
});
print('Searching for: $query');
// Implement additional search logic here
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Search Field')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
CustomTextField(
label: 'Search',
hint: 'Search products, brands...',
prefixIcon: Icon(Icons.search, color: Colors.blue),
borderRadius: 25.0,
fillColor: Colors.blue.shade50,
focusedBorderColor: Colors.blue.shade400,
enabledBorderColor: Colors.blue.shade200,
animationDuration: Duration(milliseconds: 300),
onChanged: _performSearch,
),
SizedBox(height: 20),
Text('You searched for: $_searchQuery'),
],
),
),
);
}
}
void main() {
runApp(MaterialApp(
home: SearchFieldExample(),
));
}
4. Multi-line Text Area
import 'package:flutter/material.dart';
import 'package:simple_dynamic_textfield/custom_textfield.dart'; // Adjust based on your setup
class DescriptionForm extends StatefulWidget {
@override
_DescriptionFormState createState() => _DescriptionFormState();
}
class _DescriptionFormState extends State<DescriptionForm> {
final _formKey = GlobalKey<FormState>();
final _descriptionController = TextEditingController();
@override
void dispose() {
_descriptionController.dispose();
super.dispose();
}
void _submit() {
if (_formKey.currentState!.validate()) {
print('Submitted: ${_descriptionController.text}');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Multi-line Input Example')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
CustomTextField(
label: 'Description',
hint: 'Tell us about yourself...',
controller: _descriptionController,
maxLines: 5,
textCapitalization: TextCapitalization.sentences,
contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 20),
validator: (value) {
if (value?.isEmpty ?? true) return 'Description is required';
if (value!.length < 10) return 'Please provide more details';
return null;
},
),
SizedBox(height: 24),
ElevatedButton(
onPressed: _submit,
child: Text('Submit'),
),
],
),
),
),
);
}
}
void main() {
runApp(MaterialApp(
home: DescriptionForm(),
));
}
5. Phone Number Field
import 'package:flutter/material.dart';
import 'package:simple_dynamic_textfield/custom_textfield.dart'; // Adjust if needed
class PhoneNumberForm extends StatefulWidget {
@override
_PhoneNumberFormState createState() => _PhoneNumberFormState();
}
class _PhoneNumberFormState extends State<PhoneNumberForm> {
final _formKey = GlobalKey<FormState>();
final _phoneController = TextEditingController();
@override
void dispose() {
_phoneController.dispose();
super.dispose();
}
void _submit() {
if (_formKey.currentState!.validate()) {
print('Phone number: ${_phoneController.text}');
// Add your submission logic here
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Phone Input')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
CustomTextField(
label: 'Phone Number',
hint: '+1 (555) 123-4567',
controller: _phoneController,
keyboardType: TextInputType.phone,
prefixIcon: Icon(Icons.phone_outlined),
textInputAction: TextInputAction.done,
validator: (value) {
if (value?.isEmpty ?? true) return 'Phone number is required';
if (value!.length < 10) return 'Invalid phone number';
return null;
},
),
SizedBox(height: 24),
ElevatedButton(
onPressed: _submit,
child: Text('Submit'),
),
],
),
),
),
);
}
}
void main() {
runApp(MaterialApp(
home: PhoneNumberForm(),
));
}
6. Complete Login Form
class LoginForm extends StatefulWidget {
@override
_LoginFormState createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _obscurePassword = true;
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
CustomTextField(
label: 'Email',
hint: 'Enter your email',
controller: _emailController,
keyboardType: TextInputType.emailAddress,
prefixIcon: Icon(Icons.email_outlined),
textInputAction: TextInputAction.next,
validator: (value) {
if (value?.isEmpty ?? true) return 'Email is required';
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value!)) {
return 'Invalid email format';
}
return null;
},
),
SizedBox(height: 20),
CustomTextField(
label: 'Password',
hint: 'Enter your password',
controller: _passwordController,
obscureText: _obscurePassword,
prefixIcon: Icon(Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(_obscurePassword ? Icons.visibility : Icons.visibility_off),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
textInputAction: TextInputAction.done,
validator: (value) {
if (value?.isEmpty ?? true) return 'Password is required';
if (value!.length < 6) return 'Password must be at least 6 characters';
return null;
},
onSubmitted: (value) {
_handleLogin();
},
),
SizedBox(height: 30),
ElevatedButton(
onPressed: _handleLogin,
child: Text('Login'),
),
],
),
);
}
void _handleLogin() {
if (_formKey.currentState!.validate()) {
// Process login
print('Email: ${_emailController.text}');
print('Password: ${_passwordController.text}');
}
}
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
}
Customization Options
All Available Properties
import 'package:flutter/material.dart';
import 'package:simple_dynamic_textfield/custom_textfield.dart';
class AllOptionsExample extends StatelessWidget {
final TextEditingController _controller = TextEditingController();
final FocusNode _focusNode = FocusNode();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Full CustomTextField Example')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: CustomTextField(
// Content
label: 'Field Label',
hint: 'Placeholder text',
controller: _controller,
// Input Configuration
obscureText: false,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.done,
textCapitalization: TextCapitalization.none,
maxLines: 1,
maxLength: null,
// Behavior
enabled: true,
readOnly: false,
autofocus: false,
// Styling - Colors
fillColor: Colors.grey.shade50,
focusedBorderColor: Colors.blue,
enabledBorderColor: Colors.grey.shade300,
labelColor: Colors.grey.shade700,
hintColor: Colors.grey.shade500,
textColor: Colors.black,
// Styling - Dimensions
borderRadius: 16.0,
borderWidth: 1.5,
contentPadding: EdgeInsets.all(16),
// Styling - Typography
labelStyle: TextStyle(fontSize: 16),
hintStyle: TextStyle(color: Colors.grey),
textStyle: TextStyle(fontWeight: FontWeight.bold),
// Icons
prefixIcon: Icon(Icons.person),
suffixIcon: Icon(Icons.clear),
// Animation
animationDuration: Duration(milliseconds: 200),
// Validation & Callbacks
validator: (value) => null,
onChanged: (value) {
print('Changed: $value');
},
onTap: () {
print('Field tapped');
},
onSubmitted: (value) {
print('Submitted: $value');
},
// Advanced
focusNode: _focusNode,
errorText: 'Error message',
floatingLabelBehavior: FloatingLabelBehavior.auto,
),
),
);
}
}
void main() {
runApp(MaterialApp(
home: AllOptionsExample(),
));
}
Theming
The widget automatically adapts to your app's theme:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final ThemeData lightTheme = ThemeData.light().copyWith(
colorScheme: ColorScheme.light(
primary: Colors.blue,
surface: Colors.white,
onSurface: Colors.black,
),
);
final ThemeData darkTheme = ThemeData.dark().copyWith(
colorScheme: ColorScheme.dark(
primary: Colors.blueAccent,
surface: Colors.grey.shade900,
onSurface: Colors.white,
),
);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Theming Example',
theme: lightTheme,
darkTheme: darkTheme,
themeMode: ThemeMode.system, // Automatically switches based on system setting
home: Scaffold(
appBar: AppBar(title: Text('Custom Theme Example')),
body: Center(
child: Text('Hello Themed World!'),
),
),
);
}
}
Best Practices
1. Form Validation
// Always use Form widget for validation
import 'package:flutter/material.dart';
import 'package:simple_dynamic_textfield/custom_textfield.dart';
class SimpleFormExample extends StatefulWidget {
@override
_SimpleFormExampleState createState() => _SimpleFormExampleState();
}
class _SimpleFormExampleState extends State<SimpleFormExample> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final TextEditingController _fieldController = TextEditingController();
@override
void dispose() {
_fieldController.dispose();
super.dispose();
}
void _submit() {
if (_formKey.currentState!.validate()) {
print('Submitted value: ${_fieldController.text}');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Form Example')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
CustomTextField(
label: 'Required Field',
hint: 'Enter something...',
controller: _fieldController,
validator: (value) {
if (value?.isEmpty ?? true) return 'This field is required';
return null;
},
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _submit,
child: Text('Submit'),
),
],
),
),
),
);
}
}
void main() {
runApp(MaterialApp(
home: SimpleFormExample(),
));
}
2. Controller Management
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
final TextEditingController _controller = TextEditingController();
@override
void dispose() {
_controller.dispose(); // Always dispose controllers
super.dispose();
}
@override
Widget build(BuildContext context) {
return CustomTextField(controller: _controller);
}
}
3. Focus Management
class FocusExample extends StatefulWidget {
@override
_FocusExampleState createState() => _FocusExampleState();
}
class _FocusExampleState extends State<FocusExample> {
final FocusNode _emailFocus = FocusNode();
final FocusNode _passwordFocus = FocusNode();
@override
Widget build(BuildContext context) {
return Column(
children: [
CustomTextField(
label: 'Email',
focusNode: _emailFocus,
textInputAction: TextInputAction.next,
onSubmitted: (value) {
_passwordFocus.requestFocus();
},
),
CustomTextField(
label: 'Password',
focusNode: _passwordFocus,
textInputAction: TextInputAction.done,
),
],
);
}
@override
void dispose() {
_emailFocus.dispose();
_passwordFocus.dispose();
super.dispose();
}
}
Advanced Usage
Custom Animation Curves
// For different animation feels
import 'package:flutter/material.dart';
import 'package:simple_dynamic_textfield/custom_textfield.dart';
class AnimatedTextFieldExample extends StatelessWidget {
final TextEditingController _controller = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Custom Animation Duration')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: CustomTextField(
label: 'Name',
hint: 'Enter your name',
controller: _controller,
animationDuration: Duration(milliseconds: 400),
// Internally uses Curves.easeInOut by default
),
),
);
}
}
void main() {
runApp(MaterialApp(
home: AnimatedTextFieldExample(),
));
}
Conditional Styling
import 'package:flutter/material.dart';
import 'package:simple_dynamic_textfield/custom_textfield.dart';
class ConditionalStyleExample extends StatefulWidget {
@override
_ConditionalStyleExampleState createState() => _ConditionalStyleExampleState();
}
class _ConditionalStyleExampleState extends State<ConditionalStyleExample> {
final _controller = TextEditingController();
final _formKey = GlobalKey<FormState>();
bool isError = false;
void _validate() {
setState(() {
isError = _controller.text.isEmpty;
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Dynamic Error Styling')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: Column(
children: [
CustomTextField(
label: 'Username',
hint: 'Enter your username',
controller: _controller,
fillColor: isError ? Colors.red.shade50 : Colors.grey.shade50,
focusedBorderColor: isError ? Colors.red : Colors.blue,
enabledBorderColor: isError ? Colors.red.shade300 : Colors.grey.shade300,
validator: (value) {
if (value?.isEmpty ?? true) return 'Username is required';
return null;
},
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _validate,
child: Text('Validate'),
),
],
),
),
),
);
}
}
void main() {
runApp(MaterialApp(
home: ConditionalStyleExample(),
));
}
Common Issues & Solutions
Issue: Animation not working
Solution: Make sure you're using the widget in a context where setState can be called.
Issue: Validation not showing
Solution: Wrap your CustomTextField in a Form widget and use a GlobalKey<FormState>.
Issue: Controller not updating
Solution: Ensure you're disposing controllers properly and not creating new instances on each build.
Platform Support
- Android
- iOS
- Web
- Desktop (Windows, macOS, Linux)
Migration Guide
From basic TextField
import 'package:flutter/material.dart';
import 'package:simple_dynamic_textfield/custom_textfield.dart';
class MigrationExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Migration Example')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// Before
TextField(
decoration: InputDecoration(
labelText: 'Email',
hintText: 'Enter email',
),
),
SizedBox(height: 20),
// After
CustomTextField(
label: 'Email',
hint: 'Enter email',
),
],
),
),
);
}
}
void main() {
runApp(MaterialApp(
home: MigrationExample(),
));
}
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.