Skip to content

Commit

Permalink
Merge pull request flutter#1813 from Hixie/inkwell-highlight
Browse files Browse the repository at this point in the history
Make ink wells animate their highlight.
  • Loading branch information
Hixie committed Oct 27, 2015
2 parents ee995e3 + c0c7f9d commit fa75d48
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 70 deletions.
40 changes: 15 additions & 25 deletions sky/packages/sky/lib/src/material/drawer_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);
}
Expand All @@ -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)
)
)
);
}
Expand All @@ -72,20 +61,21 @@ class _DrawerItemState extends State<DrawerItem> {
padding: const EdgeDims.symmetric(horizontal: 16.0),
child: new DefaultTextStyle(
style: _getTextStyle(themeData),
child: config.child
child: child
)
)
)
);

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)
)
);
}

}
2 changes: 1 addition & 1 deletion sky/packages/sky/lib/src/material/flat_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
129 changes: 90 additions & 39 deletions sky/packages/sky/lib/src/material/ink_well.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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() {
Expand All @@ -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();
}

Expand All @@ -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) {
Expand All @@ -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;
}

Expand All @@ -117,20 +173,15 @@ 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) {
_onLongPress = value;
_syncLongPressRecognizer();
}

_HighlightChangedCallback onHighlightChanged;

final List<_InkSplash> _splashes = new List<_InkSplash>();

TapGestureRecognizer _tap;
Expand All @@ -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)
Expand Down Expand Up @@ -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;
}
}
8 changes: 4 additions & 4 deletions sky/packages/sky/lib/src/material/material_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
)
)
Expand Down
2 changes: 1 addition & 1 deletion sky/packages/sky/lib/src/material/raised_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit fa75d48

Please sign in to comment.