Created
June 19, 2019 17:21
-
-
Save brianegan/a4a30d63e71f26ec323d37e9d7f404e8 to your computer and use it in GitHub Desktop.
Animations with Redux (or Streams or ChangeNotifiers, etc)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 acts exactly like the Switch! 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