Flutter provides a rich set of pre-built widgets for various input methods, like text fields, checkboxes, and sliders. However, there are situations where you need to create a completely custom input widget to meet specific design or functional requirements. Implementing custom input widgets in Flutter can give your app a unique look and feel, and offer specialized interactions tailored to your use case.
Why Create Custom Input Widgets in Flutter?
- Unique Design: Implement designs not achievable with standard widgets.
- Specialized Functionality: Add custom input handling or validation.
- Accessibility: Tailor the widget for improved accessibility.
- Performance: Optimize the widget for specific performance needs.
Steps to Implement Completely Custom Input Widgets in Flutter
Step 1: Extend StatelessWidget
or StatefulWidget
Determine if your widget requires maintaining state. If it does, use StatefulWidget
; otherwise, StatelessWidget
is sufficient.
Stateless Custom Input Widget
import 'package:flutter/material.dart';
class CustomInputWidget extends StatelessWidget {
final String hintText;
const CustomInputWidget({Key? key, required this.hintText}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(16.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8.0),
),
child: Text(hintText),
);
}
}
Stateful Custom Input Widget
import 'package:flutter/material.dart';
class CustomInputWidget extends StatefulWidget {
const CustomInputWidget({Key? key}) : super(key: key);
@override
_CustomInputWidgetState createState() => _CustomInputWidgetState();
}
class _CustomInputWidgetState extends State<CustomInputWidget> {
String _inputValue = '';
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Text('Entered Value: $_inputValue'),
TextField(
onChanged: (text) {
setState(() {
_inputValue = text;
});
},
decoration: InputDecoration(
hintText: 'Enter text here',
border: OutlineInputBorder(),
),
),
],
),
);
}
}
Step 2: Implement the build
Method
The build
method returns the visual representation of your widget. This is where you compose existing widgets to create the desired UI.
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
// Custom action
print('Custom input widget tapped!');
},
child: Container(
padding: EdgeInsets.all(16.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.blue),
borderRadius: BorderRadius.circular(8.0),
color: Colors.white,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.star, color: Colors.amber),
SizedBox(width: 8.0),
Text('Tap Me!', style: TextStyle(fontSize: 16.0)),
],
),
),
);
}
Step 3: Add Custom Styling
Use properties like padding
, margin
, color
, borderRadius
, and TextStyle
to style your widget.
Container(
padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 12.0),
decoration: BoxDecoration(
color: Colors.purple[100],
borderRadius: BorderRadius.circular(25.0),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Text(
'Custom Style',
style: TextStyle(
color: Colors.deepPurple,
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
),
)
Step 4: Handle User Input
Use widgets like GestureDetector
, InkWell
, or dedicated input widgets like TextField
or Slider
inside your custom widget. Manage the state and callbacks accordingly.
String _value = '';
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Text('Current value: $_value'),
TextField(
onChanged: (text) {
setState(() {
_value = text;
});
},
decoration: InputDecoration(
hintText: 'Enter value',
border: OutlineInputBorder(),
),
),
],
),
);
}
Step 5: Implement Callbacks
To communicate changes to the parent widget, use callbacks (functions) that are called when the input changes. These callbacks can pass the new value or other relevant data.
class CustomInputWidget extends StatefulWidget {
final ValueChanged onChanged;
const CustomInputWidget({Key? key, required this.onChanged}) : super(key: key);
@override
_CustomInputWidgetState createState() => _CustomInputWidgetState();
}
class _CustomInputWidgetState extends State<CustomInputWidget> {
String _value = '';
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
onChanged: (text) {
setState(() {
_value = text;
});
widget.onChanged(text); // Calling the callback
},
decoration: InputDecoration(
hintText: 'Enter value',
border: OutlineInputBorder(),
),
),
);
}
}
Example: Building a Rating Widget
Let’s create a custom rating widget with stars:
import 'package:flutter/material.dart';
class RatingWidget extends StatefulWidget {
final int initialRating;
final ValueChanged onRatingChanged;
const RatingWidget({Key? key, this.initialRating = 0, required this.onRatingChanged})
: super(key: key);
@override
_RatingWidgetState createState() => _RatingWidgetState();
}
class _RatingWidgetState extends State<RatingWidget> {
int _currentRating = 0;
@override
void initState() {
super.initState();
_currentRating = widget.initialRating;
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(5, (index) {
return IconButton(
icon: Icon(
index < _currentRating ? Icons.star : Icons.star_border,
color: Colors.amber,
),
onPressed: () {
setState(() {
_currentRating = index + 1;
});
widget.onRatingChanged(_currentRating);
},
);
}),
);
}
}
Usage
class ExamplePage extends StatefulWidget {
@override
_ExamplePageState createState() => _ExamplePageState();
}
class _ExamplePageState extends State<ExamplePage> {
int _rating = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Custom Rating Widget')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Current Rating: $_rating', style: TextStyle(fontSize: 20)),
RatingWidget(
onRatingChanged: (rating) {
setState(() {
_rating = rating;
});
},
),
],
),
),
);
}
}
Conclusion
Creating custom input widgets in Flutter can significantly enhance the user experience and visual appeal of your applications. By extending StatelessWidget
or StatefulWidget
, implementing the build
method, handling user input, applying custom styles, and implementing callbacks, you can create highly tailored and interactive input widgets that meet specific needs.