Commit a13147d0 authored by Hixie's avatar Hixie

Make ink wells animate their highlight.

parent ddd4ea81
...@@ -9,7 +9,7 @@ import 'icon.dart'; ...@@ -9,7 +9,7 @@ import 'icon.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'theme.dart'; import 'theme.dart';
class DrawerItem extends StatefulComponent { class DrawerItem extends StatelessComponent {
const DrawerItem({ Key key, this.icon, this.child, this.onPressed, this.selected: false }) const DrawerItem({ Key key, this.icon, this.child, this.onPressed, this.selected: false })
: super(key: key); : super(key: key);
...@@ -18,35 +18,23 @@ class DrawerItem extends StatefulComponent { ...@@ -18,35 +18,23 @@ class DrawerItem extends StatefulComponent {
final GestureTapCallback onPressed; final GestureTapCallback onPressed;
final bool selected; final bool selected;
_DrawerItemState createState() => new _DrawerItemState();
}
class _DrawerItemState extends State<DrawerItem> {
bool _highlight = false;
void _handleHighlightChanged(bool value) {
setState(() {
_highlight = value;
});
}
TextStyle _getTextStyle(ThemeData themeData) { TextStyle _getTextStyle(ThemeData themeData) {
TextStyle result = themeData.text.body2; TextStyle result = themeData.text.body2;
if (config.selected) if (selected)
result = result.copyWith(color: themeData.primaryColor); result = result.copyWith(color: themeData.primaryColor);
return result; return result;
} }
Color _getBackgroundColor(ThemeData themeData) { Color _getBackgroundColor(ThemeData themeData, { bool highlight }) {
if (_highlight) if (highlight)
return themeData.highlightColor; return themeData.highlightColor;
if (config.selected) if (selected)
return themeData.selectedColor; return themeData.selectedColor;
return Colors.transparent; return Colors.transparent;
} }
ColorFilter _getColorFilter(ThemeData themeData) { ColorFilter _getColorFilter(ThemeData themeData) {
if (config.selected) if (selected)
return new ColorFilter.mode(themeData.primaryColor, TransferMode.srcATop); return new ColorFilter.mode(themeData.primaryColor, TransferMode.srcATop);
return new ColorFilter.mode(const Color(0x73000000), TransferMode.dstIn); return new ColorFilter.mode(const Color(0x73000000), TransferMode.dstIn);
} }
...@@ -55,14 +43,15 @@ class _DrawerItemState extends State<DrawerItem> { ...@@ -55,14 +43,15 @@ class _DrawerItemState extends State<DrawerItem> {
ThemeData themeData = Theme.of(context); ThemeData themeData = Theme.of(context);
List<Widget> flexChildren = new List<Widget>(); List<Widget> flexChildren = new List<Widget>();
if (config.icon != null) { if (icon != null) {
flexChildren.add( flexChildren.add(
new Padding( new Padding(
padding: const EdgeDims.symmetric(horizontal: 16.0), padding: const EdgeDims.symmetric(horizontal: 16.0),
child: new Icon( child: new Icon(
type: config.icon, type: icon,
size: 24, size: 24,
colorFilter: _getColorFilter(themeData)) colorFilter: _getColorFilter(themeData)
)
) )
); );
} }
...@@ -72,7 +61,7 @@ class _DrawerItemState extends State<DrawerItem> { ...@@ -72,7 +61,7 @@ class _DrawerItemState extends State<DrawerItem> {
padding: const EdgeDims.symmetric(horizontal: 16.0), padding: const EdgeDims.symmetric(horizontal: 16.0),
child: new DefaultTextStyle( child: new DefaultTextStyle(
style: _getTextStyle(themeData), style: _getTextStyle(themeData),
child: config.child child: child
) )
) )
) )
...@@ -80,12 +69,13 @@ class _DrawerItemState extends State<DrawerItem> { ...@@ -80,12 +69,13 @@ class _DrawerItemState extends State<DrawerItem> {
return new Container( return new Container(
height: 48.0, height: 48.0,
decoration: new BoxDecoration(backgroundColor: _getBackgroundColor(themeData)),
child: new InkWell( child: new InkWell(
onTap: config.onPressed, onTap: onPressed,
onHighlightChanged: _handleHighlightChanged, defaultColor: _getBackgroundColor(themeData, highlight: false),
highlightColor: _getBackgroundColor(themeData, highlight: true),
child: new Row(flexChildren) child: new Row(flexChildren)
) )
); );
} }
} }
...@@ -26,7 +26,7 @@ class _FlatButtonState extends MaterialButtonState<FlatButton> { ...@@ -26,7 +26,7 @@ class _FlatButtonState extends MaterialButtonState<FlatButton> {
int get level => 0; int get level => 0;
Color getColor(BuildContext context) { Color getColor(BuildContext context, { bool highlight }) {
if (!config.enabled || !highlight) if (!config.enabled || !highlight)
return null; return null;
switch (Theme.of(context).brightness) { switch (Theme.of(context).brightness) {
......
...@@ -10,11 +10,65 @@ import 'package:flutter/gestures.dart'; ...@@ -10,11 +10,65 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
const int _kSplashInitialOpacity = 0x30; // This file has the following classes:
const double _kSplashCanceledVelocity = 0.7; // InkWell - the widget for material-design-style inkly-reacting material, showing splashes and a highlight
const double _kSplashConfirmedVelocity = 0.7; // _InkWellState - InkWell's State class
const double _kSplashInitialSize = 0.0; // _InkSplash - tracks a single splash
const double _kSplashUnconfirmedVelocity = 0.2; // _RenderInkSplashes - a RenderBox that renders multiple _InkSplash objects and handles gesture recognition
// _InkSplashes - the RenderObjectWidget for _RenderInkSplashes used by InkWell to handle the splashes
const int _kSplashInitialOpacity = 0x30; // 0..255
const double _kSplashCanceledVelocity = 0.7; // logical pixels per millisecond
const double _kSplashConfirmedVelocity = 0.7; // logical pixels per millisecond
const double _kSplashInitialSize = 0.0; // logical pixels
const double _kSplashUnconfirmedVelocity = 0.2; // logical pixels per millisecond
const Duration _kInkWellHighlightFadeDuration = const Duration(milliseconds: 100);
class InkWell extends StatefulComponent {
InkWell({
Key key,
this.child,
this.onTap,
this.onLongPress,
this.onHighlightChanged,
this.defaultColor,
this.highlightColor
}) : super(key: key);
final Widget child;
final GestureTapCallback onTap;
final GestureLongPressCallback onLongPress;
final _HighlightChangedCallback onHighlightChanged;
final Color defaultColor;
final Color highlightColor;
_InkWellState createState() => new _InkWellState();
}
class _InkWellState extends State<InkWell> {
bool _highlight = false;
Widget build(BuildContext context) {
return new AnimatedContainer(
decoration: new BoxDecoration(
backgroundColor: _highlight ? config.highlightColor : config.defaultColor
),
duration: _kInkWellHighlightFadeDuration,
child: new _InkSplashes(
onTap: config.onTap,
onLongPress: config.onLongPress,
onHighlightChanged: (bool value) {
setState(() {
_highlight = value;
});
if (config.onHighlightChanged != null)
config.onHighlightChanged(value);
},
child: config.child
)
);
}
}
double _getSplashTargetSize(Size bounds, Point position) { double _getSplashTargetSize(Size bounds, Point position) {
double d1 = (position - bounds.topLeft(Point.origin)).distance; double d1 = (position - bounds.topLeft(Point.origin)).distance;
...@@ -25,27 +79,30 @@ double _getSplashTargetSize(Size bounds, Point position) { ...@@ -25,27 +79,30 @@ double _getSplashTargetSize(Size bounds, Point position) {
} }
class _InkSplash { class _InkSplash {
_InkSplash(this.position, this.well) { _InkSplash(this.position, this.renderer) {
_targetRadius = _getSplashTargetSize(well.size, position); _targetRadius = _getSplashTargetSize(renderer.size, position);
_radius = new AnimatedValue<double>( _radius = new ValuePerformance<double>(
_kSplashInitialSize, end: _targetRadius, curve: Curves.easeOut); variable: new AnimatedValue<double>(
_kSplashInitialSize,
_performance = new ValuePerformance<double>( end: _targetRadius,
variable: _radius, curve: Curves.easeOut
),
duration: new Duration(milliseconds: (_targetRadius / _kSplashUnconfirmedVelocity).floor()) duration: new Duration(milliseconds: (_targetRadius / _kSplashUnconfirmedVelocity).floor())
)..addListener(_handleRadiusChange); )..addListener(_handleRadiusChange);
// Wait kPressTimeout to avoid creating tiny splashes during scrolls. // Wait kPressTimeout to avoid creating tiny splashes during scrolls.
// TODO(ianh): Instead of a timer in _InkSplash, we should start splashes from the gesture recognisers' onTapDown.
// ...and onTapDown should use a timer _or_ fire as soon as the tap is committed.
// When we do this, make sure it works even if we're only listening to onLongPress.
_startTimer = new Timer(kPressTimeout, _play); _startTimer = new Timer(kPressTimeout, _play);
} }
final Point position; final Point position;
final _RenderInkWell well; final _RenderInkSplashes renderer;
double _targetRadius; double _targetRadius;
double _pinnedRadius; double _pinnedRadius;
AnimatedValue<double> _radius; ValuePerformance<double> _radius;
Performance _performance;
Timer _startTimer; Timer _startTimer;
bool _cancelStartTimer() { bool _cancelStartTimer() {
...@@ -59,12 +116,12 @@ class _InkSplash { ...@@ -59,12 +116,12 @@ class _InkSplash {
void _play() { void _play() {
_cancelStartTimer(); _cancelStartTimer();
_performance.play(); _radius.play();
} }
void _updateVelocity(double velocity) { void _updateVelocity(double velocity) {
int duration = (_targetRadius / velocity).floor(); int duration = (_targetRadius / velocity).floor();
_performance.duration = new Duration(milliseconds: duration); _radius.duration = new Duration(milliseconds: duration);
_play(); _play();
} }
...@@ -84,8 +141,8 @@ class _InkSplash { ...@@ -84,8 +141,8 @@ class _InkSplash {
void _handleRadiusChange() { void _handleRadiusChange() {
if (_radius.value == _targetRadius) if (_radius.value == _targetRadius)
well._splashes.remove(this); renderer._splashes.remove(this);
well.markNeedsPaint(); renderer.markNeedsPaint();
} }
void paint(PaintingCanvas canvas) { void paint(PaintingCanvas canvas) {
...@@ -98,15 +155,14 @@ class _InkSplash { ...@@ -98,15 +155,14 @@ class _InkSplash {
typedef _HighlightChangedCallback(bool value); typedef _HighlightChangedCallback(bool value);
class _RenderInkWell extends RenderProxyBox { class _RenderInkSplashes extends RenderProxyBox {
_RenderInkWell({ _RenderInkSplashes({
RenderBox child, RenderBox child,
GestureTapCallback onTap, GestureTapCallback onTap,
GestureLongPressCallback onLongPress, GestureLongPressCallback onLongPress,
_HighlightChangedCallback onHighlightChanged this.onHighlightChanged
}) : super(child) { }) : super(child) {
this.onTap = onTap; this.onTap = onTap;
this.onHighlightChanged = onHighlightChanged;
this.onLongPress = onLongPress; this.onLongPress = onLongPress;
} }
...@@ -117,13 +173,6 @@ class _RenderInkWell extends RenderProxyBox { ...@@ -117,13 +173,6 @@ class _RenderInkWell extends RenderProxyBox {
_syncTapRecognizer(); _syncTapRecognizer();
} }
_HighlightChangedCallback get onHighlightChanged => _onHighlightChanged;
_HighlightChangedCallback _onHighlightChanged;
void set onHighlightChanged (_HighlightChangedCallback value) {
_onHighlightChanged = value;
_syncTapRecognizer();
}
GestureTapCallback get onLongPress => _onLongPress; GestureTapCallback get onLongPress => _onLongPress;
GestureTapCallback _onLongPress; GestureTapCallback _onLongPress;
void set onLongPress (GestureTapCallback value) { void set onLongPress (GestureTapCallback value) {
...@@ -131,6 +180,8 @@ class _RenderInkWell extends RenderProxyBox { ...@@ -131,6 +180,8 @@ class _RenderInkWell extends RenderProxyBox {
_syncLongPressRecognizer(); _syncLongPressRecognizer();
} }
_HighlightChangedCallback onHighlightChanged;
final List<_InkSplash> _splashes = new List<_InkSplash>(); final List<_InkSplash> _splashes = new List<_InkSplash>();
TapGestureRecognizer _tap; TapGestureRecognizer _tap;
...@@ -157,7 +208,7 @@ class _RenderInkWell extends RenderProxyBox { ...@@ -157,7 +208,7 @@ class _RenderInkWell extends RenderProxyBox {
} }
void _syncTapRecognizer() { void _syncTapRecognizer() {
if (onTap == null && onHighlightChanged == null) { if (onTap == null) {
_disposeTapRecognizer(); _disposeTapRecognizer();
} else { } else {
_tap ??= new TapGestureRecognizer(router: FlutterBinding.instance.pointerRouter) _tap ??= new TapGestureRecognizer(router: FlutterBinding.instance.pointerRouter)
...@@ -227,24 +278,24 @@ class _RenderInkWell extends RenderProxyBox { ...@@ -227,24 +278,24 @@ class _RenderInkWell extends RenderProxyBox {
} }
} }
class InkWell extends OneChildRenderObjectWidget { class _InkSplashes extends OneChildRenderObjectWidget {
InkWell({ _InkSplashes({
Key key, Key key,
Widget child, Widget child,
this.onTap, this.onTap,
this.onHighlightChanged, this.onLongPress,
this.onLongPress this.onHighlightChanged
}) : super(key: key, child: child); }) : super(key: key, child: child);
final GestureTapCallback onTap; final GestureTapCallback onTap;
final _HighlightChangedCallback onHighlightChanged;
final GestureLongPressCallback onLongPress; final GestureLongPressCallback onLongPress;
final _HighlightChangedCallback onHighlightChanged;
_RenderInkWell createRenderObject() => new _RenderInkWell(onTap: onTap, onHighlightChanged: onHighlightChanged, onLongPress: onLongPress); _RenderInkSplashes createRenderObject() => new _RenderInkSplashes(onTap: onTap, onLongPress: onLongPress, onHighlightChanged: onHighlightChanged);
void updateRenderObject(_RenderInkWell renderObject, InkWell oldWidget) { void updateRenderObject(_RenderInkSplashes renderObject, _InkSplashes oldWidget) {
renderObject.onTap = onTap; renderObject.onTap = onTap;
renderObject.onHighlightChanged = onHighlightChanged;
renderObject.onLongPress = onLongPress; renderObject.onLongPress = onLongPress;
renderObject.onHighlightChanged = onHighlightChanged;
} }
} }
...@@ -59,8 +59,7 @@ abstract class MaterialButtonState<T extends MaterialButton> extends State<T> { ...@@ -59,8 +59,7 @@ abstract class MaterialButtonState<T extends MaterialButton> extends State<T> {
bool highlight = false; bool highlight = false;
int get level; int get level;
Color getColor(BuildContext context, { bool highlight });
Color getColor(BuildContext context);
ThemeBrightness getColorBrightness(BuildContext context); ThemeBrightness getColorBrightness(BuildContext context);
Color getTextColor(BuildContext context) { Color getTextColor(BuildContext context) {
...@@ -106,11 +105,12 @@ abstract class MaterialButtonState<T extends MaterialButton> extends State<T> { ...@@ -106,11 +105,12 @@ abstract class MaterialButtonState<T extends MaterialButton> extends State<T> {
child: new Material( child: new Material(
type: MaterialType.button, type: MaterialType.button,
level: level, level: level,
color: getColor(context),
textStyle: Theme.of(context).text.button.copyWith(color: getTextColor(context)), textStyle: Theme.of(context).text.button.copyWith(color: getTextColor(context)),
child: new InkWell( child: new InkWell(
onTap: config.enabled ? config.onPressed : null, onTap: config.enabled ? config.onPressed : null,
onHighlightChanged: config.enabled ? _handleHighlightChanged : null, defaultColor: getColor(context, highlight: false),
highlightColor: getColor(context, highlight: true),
onHighlightChanged: _handleHighlightChanged,
child: contents child: contents
) )
) )
......
...@@ -28,7 +28,7 @@ class _RaisedButtonState extends MaterialButtonState<RaisedButton> { ...@@ -28,7 +28,7 @@ class _RaisedButtonState extends MaterialButtonState<RaisedButton> {
int get level => config.enabled ? (highlight ? 2 : 1) : 0; int get level => config.enabled ? (highlight ? 2 : 1) : 0;
Color getColor(BuildContext context) { Color getColor(BuildContext context, { bool highlight }) {
if (config.enabled) { if (config.enabled) {
switch (Theme.of(context).brightness) { switch (Theme.of(context).brightness) {
case ThemeBrightness.light: case ThemeBrightness.light:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment