Skip to content

Instantly share code, notes, and snippets.

@brianegan
Created June 19, 2019 17:22
Show Gist options
  • Save brianegan/e1702d036d7525a41239b7e170d828b9 to your computer and use it in GitHub Desktop.
Save brianegan/e1702d036d7525a41239b7e170d828b9 to your computer and use it in GitHub Desktop.
Animations with Redux (or Streams or ChangeNotifiers, etc)
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
void main() {
runApp(
MyApp(
store: Store<AppState>(
reducer,
initialState: AppState(),
),
),
);
}
class UpdateThemeAction {
final bool lightTheme;
UpdateThemeAction(this.lightTheme);
}
class AppState {
final bool lightTheme;
AppState({this.lightTheme = true});
AppState copyWith({bool lightTheme}) {
return AppState(
lightTheme: lightTheme ?? this.lightTheme,
);
}
}
AppState reducer(AppState state, dynamic action) {
if (action is UpdateThemeAction) {
return state.copyWith(lightTheme: action.lightTheme);
}
return state;
}
class MyApp extends StatelessWidget {
final Store<AppState> store;
const MyApp({Key key, this.store}) : super(key: key);
@override
Widget build(BuildContext context) {
return StoreProvider<AppState>(
store: store,
child: MaterialApp(
title: 'Flutter Redux Animations',
home: MyHomePage(),
),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Redux Animations'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Enable light theme?'),
StoreBuilder<AppState>(
builder: (context, store) {
return Switch(
// Every time the Store emits a change event, the builder
// function will run, updating the value of the Switch!
//
// The Switch widget will see that a new value has been
// provided and then run the appropriate animation.
//
// We can do the same thing with our own Widgets!
value: store.state.lightTheme,
onChanged: (lightTheme) {
store.dispatch(UpdateThemeAction(lightTheme));
},
);
},
),
StoreBuilder<AppState>(
builder: (context, store) {
return CurrentThemeDisplay(lightTheme: store.state.lightTheme);
},
),
],
),
),
);
}
}
/// Our custom class that works exactly like the Switch (just looks different)!
/// The user provides whether `lightTheme` is true or false, and this Widget
/// takes care of performing an animation when the provided `lightTheme` value
/// changes.
class CurrentThemeDisplay extends StatefulWidget {
final bool lightTheme;
const CurrentThemeDisplay({Key key, this.lightTheme}) : super(key: key);
@override
_CurrentThemeDisplayState createState() => _CurrentThemeDisplayState();
}
class _CurrentThemeDisplayState extends State<CurrentThemeDisplay>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<Color> _animation;
@override
void initState() {
// Create an animation controller to drive the animation
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 300),
);
// Our animation will go between a "Light Blue" and a "Dark Blue" depending
// on whether the user has selected a light or dark theme
_animation = ColorTween(
begin: Colors.blue.shade100,
end: Colors.blue.shade900,
).animate(_controller);
super.initState();
}
@override
void dispose() {
// Make sure to clean up after yourself!
_controller.dispose();
super.dispose();
}
/// This is where the magic happens! You must override `didUpdateWidget` to
/// capture when the Widget is rebuilt with new values.
///
/// To create this animation, we check if the old theme is the same as the new
/// theme. If both are the same, we do not take any action.
///
/// If they are different, then we use the controller to play the animation
/// in the correct direction (forward or reverse).
@override
void didUpdateWidget(CurrentThemeDisplay oldWidget) {
if (oldWidget.lightTheme != widget.lightTheme) {
if (widget.lightTheme) {
_controller.reverse();
} else {
_controller.forward();
}
}
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
// Use the animation to display the appropriate color
return AnimatedBuilder(
animation: _animation,
builder: (context, _) {
return Container(
width: 200,
height: 200,
color: _animation.value,
);
},
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment