Interaction is one of the core functions of mobile applications, and buttons and various interactive components are the foundation for implementing user interactions. Flutter provides a rich set of built-in interactive components that can meet user interaction needs in various scenarios. This lesson will detailedly introduce commonly used button components, other interactive controls, and gesture recognition mechanisms in Flutter, helping you build responsive user interfaces.
I. Common Button Components
Flutter offers several predefined button components that follow Material Design specifications, suitable for different interface scenarios.
1. ElevatedButton
ElevatedButton is a button with shadow and background color that has an elevation effect when clicked, suitable for primary actions.
ElevatedButton(
// Button click callback
onPressed: () {
print("ElevatedButton was clicked");
},
// Button text content
child: const Text("Submit"),
// Button style
style: ElevatedButton.styleFrom(
// Background color
backgroundColor: Colors.blue,
// Text color
foregroundColor: Colors.white,
// Button padding
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
// Font size
textStyle: const TextStyle(fontSize: 16),
// Border radius
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
// Shadow
elevation: 4,
),
)
When onPressed is null, the button will be in a disabled state:
ElevatedButton(
onPressed: null, // Disabled button
child: const Text("Disabled State"),
)
2. TextButton
TextButton is a text button without background color or shadow, containing only text and click effects, suitable for secondary actions.
TextButton(
onPressed: () {
print("TextButton was clicked");
},
child: const Text("Cancel"),
style: TextButton.styleFrom(
foregroundColor: Colors.grey[700], // Text color
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
textStyle: const TextStyle(fontSize: 14),
),
)
3. OutlinedButton
OutlinedButton is a button with a border but no background color, suitable for tertiary actions.
OutlinedButton(
onPressed: () {
print("OutlinedButton was clicked");
},
child: const Text("View Details"),
style: OutlinedButton.styleFrom(
foregroundColor: Colors.blue, // Text and border color
side: const BorderSide(color: Colors.blue), // Border style
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
),
)
4. Buttons with Icons
All buttons can have icons added through the child property to achieve combined text and icon effects:
// ElevatedButton with icon
ElevatedButton.icon(
onPressed: () {},
icon: const Icon(Icons.save, size: 18), // Icon
label: const Text("Save"), // Text
)
// TextButton with icon
TextButton.icon(
onPressed: () {},
icon: const Icon(Icons.share, size: 18),
label: const Text("Share"),
)
// OutlinedButton with icon
OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.delete, size: 18),
label: const Text("Delete"),
)
5. Unified Button Theme Management
To maintain consistency in button styles throughout the application, you can use Theme or ButtonTheme to configure button styles uniformly:
Theme(
data: ThemeData(
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.purple,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
),
child: Column(
children: [
ElevatedButton(
onPressed: () {},
child: const Text("Button 1"), // Will apply theme style
),
ElevatedButton(
onPressed: () {},
child: const Text("Button 2"), // Will apply theme style
),
],
),
)
II. Button Click Events and State Changes
The core function of buttons is to respond to user click operations, implemented through the onPressed callback. Additionally, button states change with interactions.
1. Basic Click Events
onPressed is the most basic callback function for buttons, triggered when the user clicks the button:
ElevatedButton(
onPressed: () {
// Handle click event
print("Button clicked");
// You can update state, navigate to new pages, or perform other operations here
},
child: const Text("Click Me"),
)
2. Long Press Events
In addition to clicks, buttons also support long press events, implemented through the onLongPress callback:
ElevatedButton(
onPressed: () {
print("Click event");
},
onLongPress: () {
print("Long press event");
},
child: const Text("Try Long Press"),
)
3. Button State Management
Button states (enabled/disabled, loading) usually need to change dynamically based on application state:
class ButtonStateExample extends StatefulWidget {
const ButtonStateExample({super.key});
@override
State<ButtonStateExample> createState() => _ButtonStateExampleState();
}
class _ButtonStateExampleState extends State<ButtonStateExample> {
bool _isLoading = false;
bool _isEnabled = true;
void _handleSubmit() async {
// Disable button and show loading state
setState(() {
_isLoading = true;
_isEnabled = false;
});
// Simulate network request
await Future.delayed(const Duration(seconds: 2));
// Restore button state
setState(() {
_isLoading = false;
_isEnabled = true;
});
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: _isEnabled ? _handleSubmit : null,
child: _isLoading
? const Row(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(
color: Colors.white,
strokeWidth: 2,
),
SizedBox(width: 8),
Text("Processing..."),
],
)
: const Text("Submit"),
);
}
}
III. Other Interactive Components
In addition to buttons, Flutter provides various commonly used interactive components for form input, selection, and other functions.
1. Checkbox
Checkbox is used for multiple selection scenarios, allowing users to select multiple options:
class CheckboxExample extends StatefulWidget {
const CheckboxExample({super.key});
@override
State<CheckboxExample> createState() => _CheckboxExampleState();
}
class _CheckboxExampleState extends State<CheckboxExample> {
bool _isChecked = false;
@override
Widget build(BuildContext context) {
return Checkbox(
value: _isChecked, // Current selection state
onChanged: (value) {
// State change callback
setState(() {
_isChecked = value ?? false;
});
},
activeColor: Colors.blue, // Color when checked
checkColor: Colors.white, // Color of check icon
);
}
}
It's usually used in combination with Text and laid out with Row:
Row(
children: [
Checkbox(
value: _isChecked,
onChanged: (value) {
setState(() => _isChecked = value ?? false);
},
),
const Text("I have read and agree to the user agreement"),
],
)
CheckboxListTile is a more complete component containing a checkbox, text, and icon:
CheckboxListTile(
value: _isChecked,
onChanged: (value) {
setState(() => _isChecked = value ?? false);
},
title: const Text("Receive push notifications"),
subtitle: const Text("Receive latest activities and offers"),
secondary: const Icon(Icons.notifications),
controlAffinity: ListTileControlAffinity.leading, // Checkbox position
)
2. Radio
Radio is used for single selection scenarios where only one option in a group can be selected:
class RadioExample extends StatefulWidget {
const RadioExample({super.key});
@override
State<RadioExample> createState() => _RadioExampleState();
}
class _RadioExampleState extends State<RadioExample> {
String? _selectedOption;
final List<String> _options = ['Option A', 'Option B', 'Option C'];
@override
Widget build(BuildContext context) {
return Column(
children: _options.map((option) {
return RadioListTile(
title: Text(option),
value: option,
groupValue: _selectedOption,
onChanged: (value) {
setState(() {
_selectedOption = value;
});
},
);
}).toList(),
);
}
}
RadioListTile is a complete component containing a radio button and text, more convenient than using Radio alone.
3. Switch
Switch is used to toggle between two states (on/off), usually for settings:
class SwitchExample extends StatefulWidget {
const SwitchExample({super.key});
@override
State<SwitchExample> createState() => _SwitchExampleState();
}
class _SwitchExampleState extends State<SwitchExample> {
bool _isEnabled = false;
@override
Widget build(BuildContext context) {
return Switch(
value: _isEnabled,
onChanged: (value) {
setState(() {
_isEnabled = value;
});
},
activeColor: Colors.green, // Color when enabled
);
}
}
SwitchListTile is a complete component containing a switch and text:
SwitchListTile(
title: const Text("Dark Mode"),
subtitle: const Text("Switch to dark display mode"),
value: _isDarkMode,
onChanged: (value) {
setState(() => _isDarkMode = value);
},
secondary: const Icon(Icons.dark_mode),
)
4. Slider
Slider is used to select a value within a range, such as volume adjustment, brightness settings, etc.:
class SliderExample extends StatefulWidget {
const SliderExample({super.key});
@override
State<SliderExample> createState() => _SliderExampleState();
}
class _SliderExampleState extends State<SliderExample> {
double _value = 50;
@override
Widget build(BuildContext context) {
return Column(
children: [
Slider(
value: _value,
min: 0, // Minimum value
max: 100, // Maximum value
divisions: 10, // Number of divisions
label: "${_value.round()}", // Label displayed when dragging
onChanged: (value) {
setState(() {
_value = value;
});
},
onChangeStart: (value) {
print("Start dragging: $value");
},
onChangeEnd: (value) {
print("End dragging: $value");
},
),
Text("Current value: ${_value.round()}"),
],
);
}
}
IV. Gesture Recognition: GestureDetector
GestureDetector is a powerful gesture recognition component that can wrap any widget and detect various user gestures such as taps, swipes, long presses, etc.
1. Basic Tap Gestures
GestureDetector(
// Single tap
onTap: () {
print("Single tap");
},
// Double tap
onDoubleTap: () {
print("Double tap");
},
// Long press
onLongPress: () {
print("Long press");
},
child: Container(
width: 100,
height: 100,
color: Colors.blue,
child: const Center(
child: Text(
"Tap Me",
style: TextStyle(color: Colors.white),
),
),
),
)
2. Swipe Gestures
GestureDetector can detect various swipe gestures, including horizontal swipes, vertical swipes, etc.:
class SwipeExample extends StatefulWidget {
const SwipeExample({super.key});
@override
State<SwipeExample> createState() => _SwipeExampleState();
}
class _SwipeExampleState extends State<SwipeExample> {
String _direction = "Please swipe";
@override
Widget build(BuildContext context) {
return GestureDetector(
// Swipe start
onPanStart: (details) {
print("Swipe started: ${details.globalPosition}");
},
// Swipe update
onPanUpdate: (details) {
// Swipe offset
print("Swipe offset: ${details.delta}");
},
// Swipe end
onPanEnd: (details) {
// Swipe velocity and direction
final velocity = details.velocity.pixelsPerSecond;
setState(() {
if (velocity.dx.abs() > velocity.dy.abs()) {
// Horizontal swipe
_direction = velocity.dx > 0 ? "Swipe right" : "Swipe left";
} else {
// Vertical swipe
_direction = velocity.dy > 0 ? "Swipe down" : "Swipe up";
}
});
},
child: Container(
width: 300,
height: 200,
color: Colors.grey[200],
child: Center(
child: Text(
_direction,
style: const TextStyle(fontSize: 18),
),
),
),
);
}
}
3. Scale Gestures
GestureDetector can also detect scale gestures for functions like image zooming:
class ScaleExample extends StatefulWidget {
const ScaleExample({super.key});
@override
State<ScaleExample> createState() => _ScaleExampleState();
}
class _ScaleExampleState extends State<ScaleExample> {
double _scale = 1.0;
double _previousScale = 1.0;
@override
Widget build(BuildContext context) {
return GestureDetector(
onScaleStart: (details) {
_previousScale = _scale;
},
onScaleUpdate: (details) {
setState(() {
_scale = _previousScale * details.scale;
// Limit scale range
_scale = _scale.clamp(0.5, 3.0);
});
},
child: Transform.scale(
scale: _scale,
child: Image.network(
"https://picsum.photos/400/300",
width: 400,
height: 300,
fit: BoxFit.cover,
),
),
);
}
}
4. Gesture Competition and Priority
When multiple gestures might be recognized simultaneously (such as tap and long press), Flutter has a gesture competition mechanism to determine which gesture should be responded to. You can control gesture detection behavior through the behavior property:
GestureDetector(
behavior: HitTestBehavior.opaque, // Can detect gestures even if child is transparent
onTap: () {
print("Container clicked");
},
child: Container(
width: 100,
height: 100,
// Transparent container can also respond to gestures
),
)
Values for HitTestBehavior include:
- deferToChild: Default value, child components prioritize responding to gestures
- opaque: Current component prioritizes responding to gestures, even if transparent
- translucent: Both current component and child components can respond to gestures
Top comments (0)