This tutorial shows you how to use MaterialStateProperty
in Flutter.
When using a Flutter widget, you may find an argument whose type is MaterialStateProperty
. For example, the backgroundColor
argument of SearchBar
widget uses MaterialStateProperty<Color?>
as the type. Meanwhile, the type of SearchBar
's elevation
is MaterialStateProperty<double?>
. In this tutorial, I am going to explain how to create a value with that type and how it can be resolved to a value.
What's MaterialStateProperty
Flutter has an abstract class MaterialStateProperty
which is an interface for classes that resolve to a value based on the value of MaterialState
. Therefore, it can be useful for cases where the value might depend on the states of the widget. With this way, it becomes possible to define different values for each MaterialState
. The MaterialState
itself is an enum whose values are:
hovered
: when the user drags the cursor over the widget.focused
: when the widget is in focus (e.g. when aTextField
is tapped).pressed
: when the widget is being pressed down.dragged
: when the widget is being dragged.selected
: when the widget has been selected.scrolledUnder
: when the widget overlaps the content of a scrollable below.disabled
: when the widget is disabled.error
: when the widget is in an invalid state.
It's possible that a widget has more than one state at the same time.
MaterialStateProperty
has a parameterized type. The purpose is to make sure that it can only resolve to values with a particular type. The code won't be able to compile if the return type is different from the parameterized type.
By using a MaterialStateProperty
as the type, one argument can be used to return different values based on the states. It also provides a clear way to define the value to be used in each state.
Creating a MaterialStateProperty
If you have to create a value for an argument whose type is a MaterialStateProperty
, below are several ways to create it.
Using MaterialStateProperty.resolveWith
This static method requires you to pass a function that accepts one parameter and return a value. The function's parameter type is Set<MaterialState>
which represents the current states of the widget. Therefore, you can use the states to determine what value should be returned. The type of the returned value must comply with the parameterized type.
static MaterialStateProperty<T> resolveWith<T>(T function Set<MaterialState> callback)
In the example below, we create a FilledButton
which has a different color when the button is being pressed.
ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith((states) {
if (states.contains(MaterialState.pressed)) {
return Colors.pinkAccent;
} else {
return Colors.teal;
}
}),
)
Output:
Using MaterialStateProperty.all
or MaterialStatePropertyAll
MaterialStateProperty.all
is suitable if the returned value is always the same for all states.
static MaterialStateProperty<T> all<T>(T value)
Example:
ButtonStyle(
backgroundColor: const MaterialStateProperty.all(Colors.teal),
)
Output:
If you need a const value, the alternative is to use MaterialStatePropertyAll
.
const MaterialStatePropertyAll(T value)
Example:
ButtonStyle(
backgroundColor: const MaterialStatePropertyAll(Colors.teal),
)
Using MaterialStateProperty.lerp
There is another static constructor namedlerp
that can be used to linearly interpolate between two MaterialStateProperty
s.
static MaterialStateProperty<T?>? lerp<T>(
MaterialStateProperty<T>? a,
MaterialStateProperty<T>? b,
double t,
T? Function(T?, T?, double) lerpFunction,
)
There are four positional arguments. The first two arguments a
and b
are the MaterialStateProperty
s to be lerped.
The third argument t
represents position on the timeline. If the value is 0.0, the interpolation has not started and the returned value is equivalent to the value from a
. If the value is 1.0, the interpolation has ended and the returned value is equivalent to the value from b
. If the value is between 0.0. and 1.0, the interpolation is somewhere between a
and b
. It also supports values below 0.0. and greater than 1.0 which means it's being extrapolated.
The last argument is a function that's responsible to return a value based on the other three arguments. For certain types, Flutter provides a static function that can be passed as the argument. For example, you can use Color.lerp
if the type is Color
unless you want to create a custom logic.
If you use lerp
, Flutter will try to resolve values from both a
and b
based on the current states. Then, the value will be lerped using the lerpFunction
which uses the t
value to compute the return value.
ButtonStyle(
backgroundColor: MaterialStateProperty.lerp<Color?>(
const MaterialStatePropertyAll(Colors.red),
const MaterialStatePropertyAll(Colors.yellow),
0,
Color.lerp,
),
)
Resolving a Value
If you have a widget whose parameter is a MaterialStateProperty
, you may need to resolve it to a value based on the current states. Below are the examples.
Using MaterialStateProperty.resolve
The resolve
method can be used to resolve the value of a MaterialStateProperty
instance based on a set of states.
T resolve(Set<MaterialState> states);
Example:
MaterialStateProperty<Color> msp = MaterialStateProperty.resolveWith((states) {
if (states.contains(MaterialState.pressed)) {
return Colors.pinkAccent;
} else {
return Colors.teal;
}
});
Color color = msp.resolve({ MaterialState.pressed, MaterialState.error });
Using MaterialStateProperty.resolveAs
Another way is to use the resolveAs
static method.
static T resolveAs<T>(T value, Set<MaterialState> states)
If the passed value is a MaterialStateProperty
, it will resolve the value based on the given states. Otherwise, it will return the value itself. This method can be useful inside a widget whose parameter can be a MaterialStateProperty
or another type of value.
For example, the mouseCursor
of InkWell
can be a MouseCursor
or a MaterialStateMouseCursor
, which implements MaterialStateProperty<MouseCursor>
. If you look at the source code of the InkWell
, it has the code below to resolve the MouseCursor
value.
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
statesController.value,
);
Summary
In this tutorial, we have learned how to create a MaterialStateProperty
value. You can use the resolveWith
method if you need to return different values based on the current states. If the returned value is always the same, use the all
method or MaterialStatePropertyAll
constructor. For resolving a MaterialStateProperty
to a value, call the resolve
method of the instance or the resolveAs
static method.