Controlling Widget State
In this tutorial, we’ll explore how to manage manually widget states in Mix, including hover, pressed, and focused states. Mix provides multiple approaches to handle widget states, from simple automatic state management to advanced manual control using WidgetStatesController.
Approach 1: Automatic State Management
The simplest way to handle widget states is using Mix’s automatic state management with the Box widget. Mix automatically tracks hover, press, and focus states for you:
class SimpleExample extends StatelessWidget {
const SimpleExample({super.key});
BoxStyler get style => BoxStyler()
.color(Colors.red)
.size(100, 100)
.onHovered(BoxStyler().color(Colors.blue))
.onPressed(BoxStyler().color(Colors.green))
.onFocused(BoxStyler().color(Colors.yellow));
@override
Widget build(BuildContext context) {
return style();
}
}In this example:
- The base color is red
- When hovered, the color changes to blue
- When pressed, the color changes to green
- When focused, the color changes to yellow
Approach 2: Manual State Control with WidgetStatesController
For more advanced scenarios where you need manual control over widget states, you can use WidgetStatesController with StyleBuilder:
class AdvancedExample extends StatefulWidget {
const AdvancedExample({super.key});
@override
State<AdvancedExample> createState() => _AdvancedExampleState();
}
class _AdvancedExampleState extends State<AdvancedExample> {
final controller = WidgetStatesController();
@override
void dispose() {
controller.dispose();
super.dispose();
}
BoxStyler get style => BoxStyler()
.color(Colors.red)
.size(100, 100)
.onHovered(BoxStyler().color(Colors.blue))
.onPressed(BoxStyler().color(Colors.green))
.onFocused(BoxStyler().color(Colors.yellow));
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (_) => controller.pressed = true,
onTapUp: (_) => controller.pressed = false,
onTapCancel: () => controller.pressed = false,
child: StyleBuilder(
style: style,
controller: controller,
builder: (context, spec) {
return Box(styleSpec: StyleSpec(spec: spec));
},
),
);
}
}Creating the WidgetStatesController
First, create an instance of WidgetStatesController in your state class:
final controller = WidgetStatesController();Remember to dispose of the controller when the widget is disposed to avoid memory leaks:
@override
void dispose() {
controller.dispose();
super.dispose();
}Managing Widget States
The WidgetStatesController provides properties to control different widget states:
controller.pressed = true/false- Controls the pressed statecontroller.hovered = true/false- Controls the hover statecontroller.focused = true/false- Controls the focus statecontroller.disabled = true/false- Controls the disabled state
Using StyleBuilder
StyleBuilder is the low-level building block for creating styled widgets with Mix. It resolves your style definitions and provides the resolved spec to the builder function:
StyleBuilder(
style: style,
controller: controller,
builder: (context, spec) {
return Box(styleSpec: StyleSpec(spec: spec));
},
)The builder function receives:
context- The build contextspec- The resolvedBoxSpecwith all style properties applied based on the current widget states
Connecting Gesture Detection
Use Flutter’s GestureDetector to connect user interactions to the controller:
GestureDetector(
onTapDown: (_) => controller.pressed = true,
onTapUp: (_) => controller.pressed = false,
onTapCancel: () => controller.pressed = false,
child: StyleBuilder(...),
)When to Use Each Approach
Use automatic state management (Approach 1) when:
- You need simple hover, press, and focus interactions
- You want Mix to handle all the complexity for you
- You’re building standard interactive widgets
Use manual state control (Approach 2) when:
- You need custom gesture handling logic
- You want to programmatically control widget states
- You need to share state across multiple widgets
- You’re building complex custom widgets with specific interaction patterns