Commit a13147d0 authored by Hixie's avatar Hixie

Make ink wells animate their highlight.

parent ddd4ea81
......@@ -9,7 +9,7 @@ import 'icon.dart';
import 'ink_well.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 })
: super(key: key);
......@@ -18,35 +18,23 @@ class DrawerItem extends StatefulComponent {
final GestureTapCallback onPressed;
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 result = themeData.text.body2;
if (config.selected)
if (selected)
result = result.copyWith(color: themeData.primaryColor);
return result;
}
Color _getBackgroundColor(ThemeData themeData) {
if (_highlight)
Color _getBackgroundColor(ThemeData themeData, { bool highlight }) {
if (highlight)
return themeData.highlightColor;
if (config.selected)
if (selected)
return themeData.selectedColor;
return Colors.transparent;
}
ColorFilter _getColorFilter(ThemeData themeData) {
if (config.selected)
if (selected)
return new ColorFilter.mode(themeData.primaryColor, TransferMode.srcATop);
return new ColorFilter.mode(const Color(0x73000000), TransferMode.dstIn);
}
......@@ -55,14 +43,15 @@ class _DrawerItemState extends State<DrawerItem> {
ThemeData themeData = Theme.of(context);
List<Widget> flexChildren = new List<Widget>();
if (config.icon != null) {
if (icon != null) {
flexChildren.add(
new Padding(
padding: const EdgeDims.symmetric(horizontal: 16.0),
child: new Icon(
type: config.icon,
type: icon,
size: 24,
colorFilter: _getColorFilter(themeData))
colorFilter: _getColorFilter(themeData)
)
)
);
}
......@@ -72,7 +61,7 @@ class _DrawerItemState extends State<DrawerItem> {
padding: const EdgeDims.symmetric(horizontal: 16.0),
child: new DefaultTextStyle(
style: _getTextStyle(themeData),
child: config.child
child: child
)
)
)
......@@ -80,12 +69,13 @@ class _DrawerItemState extends State<DrawerItem> {
return new Container(
height: 48.0,
decoration: new BoxDecoration(backgroundColor: _getBackgroundColor(themeData)),
child: new InkWell(
onTap: config.onPressed,
onHighlightChanged: _handleHighlightChanged,
onTap: onPressed,
defaultColor: _getBackgroundColor(themeData, highlight: false),
highlightColor: _getBackgroundColor(themeData, highlight: true),
child: new Row(flexChildren)
)
);
}
}
......@@ -26,7 +26,7 @@ class _FlatButtonState extends MaterialButtonState<FlatButton> {
int get level => 0;
Color getColor(BuildContext context) {
Color getColor(BuildContext context, { bool highlight }) {
if (!config.enabled || !highlight)
return null;
switch (Theme.of(context).brightness) {
......
......@@ -10,11 +10,65 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
const int _kSplashInitialOpacity = 0x30;
const double _kSplashCanceledVelocity = 0.7;
const double _kSplashConfirmedVelocity = 0.7;
const double _kSplashInitialSize = 0.0;
const double _kSplashUnconfirmedVelocity = 0.2;
// This file has the following classes:
// InkWell - the widget for material-design-style inkly-reacting material, showing splashes and a highlight
// _InkWellState - InkWell's State class
// _InkSplash - tracks a single splash
// _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 d1 = (position - bounds.topLeft(Point.origin)).distance;
......@@ -25,27 +79,30 @@ double _getSplashTargetSize(Size bounds, Point position) {
}
class _InkSplash {
_InkSplash(this.position, this.well) {
_targetRadius = _getSplashTargetSize(well.size, position);
_radius = new AnimatedValue<double>(
_kSplashInitialSize, end: _targetRadius, curve: Curves.easeOut);
_performance = new ValuePerformance<double>(
variable: _radius,
_InkSplash(this.position, this.renderer) {
_targetRadius = _getSplashTargetSize(renderer.size, position);
_radius = new ValuePerformance<double>(
variable: new AnimatedValue<double>(
_kSplashInitialSize,
end: _targetRadius,
curve: Curves.easeOut
),
duration: new Duration(milliseconds: (_targetRadius / _kSplashUnconfirmedVelocity).floor())
)..addListener(_handleRadiusChange);
// 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);
}
final Point position;
final _RenderInkWell well;
final _RenderInkSplashes renderer;
double _targetRadius;
double _pinnedRadius;
AnimatedValue<double> _radius;
Performance _performance;
ValuePerformance<double> _radius;
Timer _startTimer;
bool _cancelStartTimer() {
......@@ -59,12 +116,12 @@ class _InkSplash {
void _play() {
_cancelStartTimer();
_performance.play();
_radius.play();
}
void _updateVelocity(double velocity) {
int duration = (_targetRadius / velocity).floor();
_performance.duration = new Duration(milliseconds: duration);
_radius.duration = new Duration(milliseconds: duration);
_play();
}
......@@ -84,8 +141,8 @@ class _InkSplash {
void _handleRadiusChange() {
if (_radius.value == _targetRadius)
well._splashes.remove(this);
well.markNeedsPaint();
renderer._splashes.remove(this);
renderer.markNeedsPaint();
}
void paint(PaintingCanvas canvas) {
......@@ -98,15 +155,14 @@ class _InkSplash {
typedef _HighlightChangedCallback(bool value);
class _RenderInkWell extends RenderProxyBox {
_RenderInkWell({
class _RenderInkSplashes extends RenderProxyBox {
_RenderInkSplashes({
RenderBox child,
GestureTapCallback onTap,
GestureLongPressCallback onLongPress,
_HighlightChangedCallback onHighlightChanged
this.onHighlightChanged
}) : super(child) {
this.onTap = onTap;
this.onHighlightChanged = onHighlightChanged;
this.onLongPress = onLongPress;
}
......@@ -117,13 +173,6 @@ class _RenderInkWell extends RenderProxyBox {
_syncTapRecognizer();
}
_HighlightChangedCallback get onHighlightChanged => _onHighlightChanged;
_HighlightChangedCallback _onHighlightChanged;
void set onHighlightChanged (_HighlightChangedCallback value) {
_onHighlightChanged = value;
_syncTapRecognizer();
}
GestureTapCallback get onLongPress => _onLongPress;
GestureTapCallback _onLongPress;
void set onLongPress (GestureTapCallback value) {
......@@ -131,6 +180,8 @@ class _RenderInkWell extends RenderProxyBox {
_syncLongPressRecognizer();
}
_HighlightChangedCallback onHighlightChanged;
final List<_InkSplash> _splashes = new List<_InkSplash>();
TapGestureRecognizer _tap;
......@@ -157,7 +208,7 @@ class _RenderInkWell extends RenderProxyBox {
}
void _syncTapRecognizer() {
if (onTap == null && onHighlightChanged == null) {
if (onTap == null) {
_disposeTapRecognizer();
} else {
_tap ??= new TapGestureRecognizer(router: FlutterBinding.instance.pointerRouter)
......@@ -227,24 +278,24 @@ class _RenderInkWell extends RenderProxyBox {
}
}
class InkWell extends OneChildRenderObjectWidget {
InkWell({
class _InkSplashes extends OneChildRenderObjectWidget {
_InkSplashes({
Key key,
Widget child,
this.onTap,
this.onHighlightChanged,
this.onLongPress
this.onLongPress,
this.onHighlightChanged
}) : super(key: key, child: child);
final GestureTapCallback onTap;
final _HighlightChangedCallback onHighlightChanged;
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.onHighlightChanged = onHighlightChanged;
renderObject.onLongPress = onLongPress;
renderObject.onHighlightChanged = onHighlightChanged;
}
}
......@@ -59,8 +59,7 @@ abstract class MaterialButtonState<T extends MaterialButton> extends State<T> {
bool highlight = false;
int get level;
Color getColor(BuildContext context);
Color getColor(BuildContext context, { bool highlight });
ThemeBrightness getColorBrightness(BuildContext context);
Color getTextColor(BuildContext context) {
......@@ -106,11 +105,12 @@ abstract class MaterialButtonState<T extends MaterialButton> extends State<T> {
child: new Material(
type: MaterialType.button,
level: level,
color: getColor(context),
textStyle: Theme.of(context).text.button.copyWith(color: getTextColor(context)),
child: new InkWell(
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
)
)
......
......@@ -28,7 +28,7 @@ class _RaisedButtonState extends MaterialButtonState<RaisedButton> {
int get level => config.enabled ? (highlight ? 2 : 1) : 0;
Color getColor(BuildContext context) {
Color getColor(BuildContext context, { bool highlight }) {
if (config.enabled) {
switch (Theme.of(context).brightness) {
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