From cf88993492cc32c03c4e58760052a95f87ea7ca2 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Sat, 3 Oct 2015 01:09:14 -0700 Subject: [PATCH] RenderInkWell should use gestures After this patch, InkWell is driven by gesture recognizers, which lets us cleanly cancel splashes when the user actually scrolls. I've also refactored all the clients of InkWell to use InkWell to detect gestures instead of wrapping InkWell in a GestureDetector. Fixes #1271 --- examples/demo_launcher/lib/main.dart | 79 ++++----- examples/fitness/lib/feed.dart | 16 +- examples/fitness/lib/meal.dart | 7 +- examples/fitness/lib/measurement.dart | 7 +- examples/stocks/lib/stock_row.dart | 82 +++++---- packages/flutter/lib/src/gestures/tap.dart | 19 ++- .../flutter/lib/src/rendering/toggleable.dart | 2 +- .../flutter/lib/src/widgets/date_picker.dart | 20 +-- packages/flutter/lib/src/widgets/dialog.dart | 3 +- .../flutter/lib/src/widgets/drawer_item.dart | 15 +- .../flutter/lib/src/widgets/flat_button.dart | 3 +- .../src/widgets/floating_action_button.dart | 21 +-- .../lib/src/widgets/gesture_detector.dart | 12 +- .../flutter/lib/src/widgets/icon_button.dart | 3 +- .../flutter/lib/src/widgets/ink_well.dart | 156 ++++++++++++++---- .../lib/src/widgets/material_button.dart | 26 +-- .../flutter/lib/src/widgets/popup_menu.dart | 4 +- .../lib/src/widgets/popup_menu_item.dart | 17 +- .../lib/src/widgets/raised_button.dart | 3 +- .../flutter/lib/src/widgets/snack_bar.dart | 6 +- packages/flutter/lib/src/widgets/tabs.dart | 26 +-- 21 files changed, 320 insertions(+), 207 deletions(-) diff --git a/examples/demo_launcher/lib/main.dart b/examples/demo_launcher/lib/main.dart index 3a80555ccc1..4fc089c33d0 100644 --- a/examples/demo_launcher/lib/main.dart +++ b/examples/demo_launcher/lib/main.dart @@ -42,8 +42,8 @@ void launch(String relativeUrl, String bundle) { activity.startActivity(intent); } -class SkyDemo { - SkyDemo({ +class FlutterDemo { + FlutterDemo({ name, this.href, this.bundle, @@ -60,8 +60,8 @@ class SkyDemo { final BoxDecoration decoration; } -List demos = [ - new SkyDemo( +List demos = [ + new FlutterDemo( name: 'Stocks', href: '../../stocks/lib/main.dart', bundle: 'stocks.skyx', @@ -74,7 +74,7 @@ List demos = [ ) ) ), - new SkyDemo( + new FlutterDemo( name: 'Asteroids', href: '../../game/lib/main.dart', bundle: 'game.skyx', @@ -87,7 +87,7 @@ List demos = [ ) ) ), - new SkyDemo( + new FlutterDemo( name: 'Fitness', href: '../../fitness/lib/main.dart', bundle: 'fitness.skyx', @@ -97,7 +97,7 @@ List demos = [ backgroundColor: Colors.indigo[500] ) ), - new SkyDemo( + new FlutterDemo( name: 'Swipe Away', href: '../../widgets/card_collection.dart', bundle: 'cards.skyx', @@ -107,7 +107,7 @@ List demos = [ backgroundColor: Colors.redAccent[200] ) ), - new SkyDemo( + new FlutterDemo( name: 'Interactive Text', href: '../../rendering/interactive_flex.dart', bundle: 'interactive_flex.skyx', @@ -120,7 +120,7 @@ List demos = [ // new SkyDemo( // 'Touch Demo', '../../rendering/touch_demo.dart', 'Simple example showing handling of touch events at a low level'), - new SkyDemo( + new FlutterDemo( name: 'Minedigger Game', href: '../../mine_digger/lib/main.dart', bundle: 'mine_digger.skyx', @@ -138,44 +138,47 @@ List demos = [ const double kCardHeight = 120.0; const EdgeDims kListPadding = const EdgeDims.all(4.0); -class DemoList extends StatelessComponent { - Widget buildCardContents(SkyDemo demo) { - return new Container( - decoration: demo.decoration, - child: new InkWell( - child: new Container( - margin: const EdgeDims.only(top: 24.0, left: 24.0), - child: new Column([ - new Text(demo.name, style: demo.textTheme.title), - new Flexible( - child: new Text(demo.description, style: demo.textTheme.subhead) - ) - ], - alignItems: FlexAlignItems.start +class DemoCard extends StatelessComponent { + DemoCard({ Key key, this.demo }) : super(key: key); + + final FlutterDemo demo; + + Widget build(BuildContext context) { + return new Container( + height: kCardHeight, + child: new Card( + child: new Container( + decoration: demo.decoration, + child: new InkWell( + onTap: () => launch(demo.href, demo.bundle), + child: new Container( + margin: const EdgeDims.only(top: 24.0, left: 24.0), + child: new Column([ + new Text(demo.name, style: demo.textTheme.title), + new Flexible( + child: new Text(demo.description, style: demo.textTheme.subhead) + ) + ], + alignItems: FlexAlignItems.start + ) ) ) ) - ); - } - - Widget buildDemo(BuildContext context, SkyDemo demo) { - return new GestureDetector( - key: demo.key, - onTap: () => launch(demo.href, demo.bundle), - child: new Container( - height: kCardHeight, - child: new Card( - child: buildCardContents(demo) - ) ) ); } +} + +class DemoList extends StatelessComponent { + Widget _buildDemoCard(BuildContext context, FlutterDemo demo) { + return new DemoCard(key: demo.key, demo: demo); + } Widget build(BuildContext context) { - return new ScrollableList( + return new ScrollableList( items: demos, itemExtent: kCardHeight, - itemBuilder: buildDemo, + itemBuilder: _buildDemoCard, padding: kListPadding ); } @@ -200,7 +203,7 @@ class DemoHome extends StatelessComponent { void main() { runApp(new App( - title: 'Sky Demos', + title: 'Flutter Demos', theme: _theme, routes: { '/': (NavigatorState navigator, Route route) => new DemoHome() diff --git a/examples/fitness/lib/feed.dart b/examples/fitness/lib/feed.dart index b689531dcf2..a3c0275f7e5 100644 --- a/examples/fitness/lib/feed.dart +++ b/examples/fitness/lib/feed.dart @@ -33,15 +33,13 @@ class DialogMenuItem extends StatelessComponent { Function onPressed; Widget build(BuildContext context) { - return new GestureDetector( - onTap: onPressed, - child: new Container( - height: 48.0, - child: new InkWell( - child: new Padding( - padding: const EdgeDims.symmetric(horizontal: 16.0), - child: new Row(children) - ) + return new Container( + height: 48.0, + child: new InkWell( + onTap: onPressed, + child: new Padding( + padding: const EdgeDims.symmetric(horizontal: 16.0), + child: new Row(children) ) ) ); diff --git a/examples/fitness/lib/meal.dart b/examples/fitness/lib/meal.dart index 5e65025a5ce..e66fb9f9001 100644 --- a/examples/fitness/lib/meal.dart +++ b/examples/fitness/lib/meal.dart @@ -65,12 +65,13 @@ class MealFragmentState extends State { icon: "navigation/close", onPressed: config.navigator.pop), center: new Text('New Meal'), - right: [new InkWell( - child: new GestureDetector( + right: [ + // TODO(abarth): Should this be a FlatButton? + new InkWell( onTap: _handleSave, child: new Text('SAVE') ) - )] + ] ); } diff --git a/examples/fitness/lib/measurement.dart b/examples/fitness/lib/measurement.dart index 573228656e8..695d82a4c63 100644 --- a/examples/fitness/lib/measurement.dart +++ b/examples/fitness/lib/measurement.dart @@ -136,12 +136,13 @@ class MeasurementFragmentState extends State { icon: "navigation/close", onPressed: config.navigator.pop), center: new Text('New Measurement'), - right: [new InkWell( - child: new GestureDetector( + right: [ + // TODO(abarth): Should this be a FlatButton? + new InkWell( onTap: _handleSave, child: new Text('SAVE') ) - )] + ] ); } diff --git a/examples/stocks/lib/stock_row.dart b/examples/stocks/lib/stock_row.dart index bc522bd9d33..da95327cf15 100644 --- a/examples/stocks/lib/stock_row.dart +++ b/examples/stocks/lib/stock_row.dart @@ -55,52 +55,50 @@ class StockRow extends StatelessComponent { String changeInPrice = "${stock.percentChange.toStringAsFixed(2)}%"; if (stock.percentChange > 0) changeInPrice = "+" + changeInPrice; - return new GestureDetector( + return new InkWell( onTap: _getTapHandler(onPressed), onLongPress: _getLongPressHandler(onLongPressed), - child: new InkWell( - child: new Container( - padding: const EdgeDims.TRBL(16.0, 16.0, 20.0, 16.0), - decoration: new BoxDecoration( - border: new Border( - bottom: new BorderSide(color: Theme.of(context).dividerColor) - ) + child: new Container( + padding: const EdgeDims.TRBL(16.0, 16.0, 20.0, 16.0), + decoration: new BoxDecoration( + border: new Border( + bottom: new BorderSide(color: Theme.of(context).dividerColor) + ) + ), + child: new Row([ + new Container( + key: arrowKey, + child: new StockArrow(percentChange: stock.percentChange), + margin: const EdgeDims.only(right: 5.0) ), - child: new Row([ - new Container( - key: arrowKey, - child: new StockArrow(percentChange: stock.percentChange), - margin: const EdgeDims.only(right: 5.0) - ), - new Flexible( - child: new Row([ - new Flexible( - flex: 2, - child: new Text( - stock.symbol, - key: symbolKey - ) - ), - new Flexible( - child: new Text( - lastSale, - style: const TextStyle(textAlign: TextAlign.right), - key: priceKey - ) - ), - new Flexible( - child: new Text( - changeInPrice, - style: Theme.of(context).text.caption.copyWith(textAlign: TextAlign.right) - ) - ), - ], - alignItems: FlexAlignItems.baseline, - textBaseline: DefaultTextStyle.of(context).textBaseline - ) + new Flexible( + child: new Row([ + new Flexible( + flex: 2, + child: new Text( + stock.symbol, + key: symbolKey + ) + ), + new Flexible( + child: new Text( + lastSale, + style: const TextStyle(textAlign: TextAlign.right), + key: priceKey + ) + ), + new Flexible( + child: new Text( + changeInPrice, + style: Theme.of(context).text.caption.copyWith(textAlign: TextAlign.right) + ) + ), + ], + alignItems: FlexAlignItems.baseline, + textBaseline: DefaultTextStyle.of(context).textBaseline ) - ]) - ) + ) + ]) ) ); } diff --git a/packages/flutter/lib/src/gestures/tap.dart b/packages/flutter/lib/src/gestures/tap.dart index 8c5683fbdf8..092874c3da8 100644 --- a/packages/flutter/lib/src/gestures/tap.dart +++ b/packages/flutter/lib/src/gestures/tap.dart @@ -14,11 +14,26 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { : super(router: router); GestureTapCallback onTap; + GestureTapCallback onTapDown; + GestureTapCallback onTapCancel; void handlePrimaryPointer(sky.PointerEvent event) { - if (event.type == 'pointerup') { + if (event.type == 'pointerdown') { + if (onTapDown != null) + onTapDown(); + } else if (event.type == 'pointerup') { resolve(GestureDisposition.accepted); - onTap(); + if (onTap != null) + onTap(); + } + } + + void rejectGesture(int pointer) { + super.rejectGesture(pointer); + if (pointer == primaryPointer) { + assert(state == GestureRecognizerState.defunct); + if (onTapCancel != null) + onTapCancel(); } } } diff --git a/packages/flutter/lib/src/rendering/toggleable.dart b/packages/flutter/lib/src/rendering/toggleable.dart index ad9d7bc1321..1a2bbae9ed4 100644 --- a/packages/flutter/lib/src/rendering/toggleable.dart +++ b/packages/flutter/lib/src/rendering/toggleable.dart @@ -51,7 +51,7 @@ abstract class RenderToggleable extends RenderConstrainedBox { ); } - void detatch() { + void detach() { _tap.dispose(); _tap = null; super.detach(); diff --git a/packages/flutter/lib/src/widgets/date_picker.dart b/packages/flutter/lib/src/widgets/date_picker.dart index ed3a91c8b7a..5773c0a2645 100644 --- a/packages/flutter/lib/src/widgets/date_picker.dart +++ b/packages/flutter/lib/src/widgets/date_picker.dart @@ -375,22 +375,20 @@ class YearPickerState extends ScrollableWidgetListState { for(int i = start; i < start + count; i++) { int year = config.firstDate.year + i; String label = year.toString(); - Widget item = new GestureDetector( + Widget item = new InkWell( key: new Key(label), onTap: () { DateTime result = new DateTime(year, config.selectedDate.month, config.selectedDate.day); config.onChanged(result); }, - child: new InkWell( - child: new Container( - height: config.itemExtent, - decoration: year == config.selectedDate.year ? new BoxDecoration( - backgroundColor: Theme.of(context).primarySwatch[100], - shape: Shape.circle - ) : null, - child: new Center( - child: new Text(label, style: style) - ) + child: new Container( + height: config.itemExtent, + decoration: year == config.selectedDate.year ? new BoxDecoration( + backgroundColor: Theme.of(context).primarySwatch[100], + shape: Shape.circle + ) : null, + child: new Center( + child: new Text(label, style: style) ) ) ); diff --git a/packages/flutter/lib/src/widgets/dialog.dart b/packages/flutter/lib/src/widgets/dialog.dart index 9b6ca201bf6..ef142d0baa2 100644 --- a/packages/flutter/lib/src/widgets/dialog.dart +++ b/packages/flutter/lib/src/widgets/dialog.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:sky/animation.dart'; +import 'package:sky/gestures.dart'; import 'package:sky/material.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/focus.dart'; @@ -52,7 +53,7 @@ class Dialog extends StatelessComponent { final List actions; /// An (optional) callback that is called when the dialog is dismissed. - final Function onDismiss; + final GestureTapCallback onDismiss; Color _getColor(BuildContext context) { switch (Theme.of(context).brightness) { diff --git a/packages/flutter/lib/src/widgets/drawer_item.dart b/packages/flutter/lib/src/widgets/drawer_item.dart index e7a609d06a5..2850d9418d8 100644 --- a/packages/flutter/lib/src/widgets/drawer_item.dart +++ b/packages/flutter/lib/src/widgets/drawer_item.dart @@ -10,7 +10,6 @@ import 'package:sky/painting.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/button_state.dart'; import 'package:sky/src/widgets/framework.dart'; -import 'package:sky/src/widgets/gesture_detector.dart'; import 'package:sky/src/widgets/icon.dart'; import 'package:sky/src/widgets/ink_well.dart'; import 'package:sky/src/widgets/theme.dart'; @@ -76,14 +75,12 @@ class DrawerItemState extends ButtonState { ) ); - return new GestureDetector( - onTap: config.onPressed, - child: new Container( - height: 48.0, - decoration: new BoxDecoration(backgroundColor: _getBackgroundColor(themeData)), - child: new InkWell( - child: new Row(flexChildren) - ) + return new Container( + height: 48.0, + decoration: new BoxDecoration(backgroundColor: _getBackgroundColor(themeData)), + child: new InkWell( + onTap: config.onPressed, + child: new Row(flexChildren) ) ); } diff --git a/packages/flutter/lib/src/widgets/flat_button.dart b/packages/flutter/lib/src/widgets/flat_button.dart index 501799005c5..02beead3551 100644 --- a/packages/flutter/lib/src/widgets/flat_button.dart +++ b/packages/flutter/lib/src/widgets/flat_button.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:sky/gestures.dart'; import 'package:sky/material.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/framework.dart'; @@ -13,7 +14,7 @@ class FlatButton extends MaterialButton { Key key, Widget child, bool enabled: true, - Function onPressed + GestureTapCallback onPressed }) : super(key: key, child: child, enabled: enabled, diff --git a/packages/flutter/lib/src/widgets/floating_action_button.dart b/packages/flutter/lib/src/widgets/floating_action_button.dart index 24f57bdc257..6b7d4b66c3c 100644 --- a/packages/flutter/lib/src/widgets/floating_action_button.dart +++ b/packages/flutter/lib/src/widgets/floating_action_button.dart @@ -6,7 +6,6 @@ import 'package:sky/gestures.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/button_state.dart'; import 'package:sky/src/widgets/framework.dart'; -import 'package:sky/src/widgets/gesture_detector.dart'; import 'package:sky/src/widgets/icon.dart'; import 'package:sky/src/widgets/ink_well.dart'; import 'package:sky/src/widgets/material.dart'; @@ -46,17 +45,15 @@ class FloatingActionButtonState extends ButtonState { type: MaterialType.circle, level: highlight ? 3 : 2, child: new ClipOval( - child: new GestureDetector( - onTap: config.onPressed, - child: new Container( - width: _kSize, - height: _kSize, - child: new InkWell( - child: new Center( - child: new IconTheme( - data: new IconThemeData(color: iconThemeColor), - child: config.child - ) + child: new Container( + width: _kSize, + height: _kSize, + child: new InkWell( + onTap: config.onPressed, + child: new Center( + child: new IconTheme( + data: new IconThemeData(color: iconThemeColor), + child: config.child ) ) ) diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index a479f5fc3f9..fb3407908b8 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -14,6 +14,8 @@ class GestureDetector extends StatefulComponent { Key key, this.child, this.onTap, + this.onTapDown, + this.onTapCancel, this.onShowPress, this.onLongPress, this.onVerticalDragStart, @@ -33,6 +35,9 @@ class GestureDetector extends StatefulComponent { final Widget child; final GestureTapCallback onTap; + final GestureTapCallback onTapDown; + final GestureTapCallback onTapCancel; + final GestureShowPressCallback onShowPress; final GestureLongPressCallback onLongPress; @@ -97,11 +102,14 @@ class GestureDetectorState extends State { } void _syncTap() { - if (config.onTap == null) { + if (config.onTap == null && config.onTapDown == null && config.onTapCancel == null) { _tap = _ensureDisposed(_tap); } else { _tap ??= new TapGestureRecognizer(router: _router); - _tap.onTap = config.onTap; + _tap + ..onTap = config.onTap + ..onTapDown = config.onTapDown + ..onTapCancel = config.onTapCancel; } } diff --git a/packages/flutter/lib/src/widgets/icon_button.dart b/packages/flutter/lib/src/widgets/icon_button.dart index fcb42fd3270..662ee5fb52c 100644 --- a/packages/flutter/lib/src/widgets/icon_button.dart +++ b/packages/flutter/lib/src/widgets/icon_button.dart @@ -4,6 +4,7 @@ import 'dart:sky' as sky; +import 'package:sky/gestures.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/icon.dart'; import 'package:sky/src/widgets/framework.dart'; @@ -13,8 +14,8 @@ class IconButton extends StatelessComponent { const IconButton({ Key key, this.icon, this.onPressed, this.color }) : super(key: key); final String icon; - final Function onPressed; final Color color; + final GestureTapCallback onPressed; Widget build(BuildContext context) { Widget child = new Icon(type: icon, size: 24); diff --git a/packages/flutter/lib/src/widgets/ink_well.dart b/packages/flutter/lib/src/widgets/ink_well.dart index 4e44e0517be..a0f36726d13 100644 --- a/packages/flutter/lib/src/widgets/ink_well.dart +++ b/packages/flutter/lib/src/widgets/ink_well.dart @@ -2,16 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; import 'dart:math' as math; import 'dart:sky' as sky; import 'package:sky/animation.dart'; +import 'package:sky/gestures.dart'; import 'package:sky/rendering.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/framework.dart'; const int _kSplashInitialOpacity = 0x30; -const double _kSplashCancelledVelocity = 0.7; +const double _kSplashCanceledVelocity = 0.7; const double _kSplashConfirmedVelocity = 0.7; const double _kSplashInitialSize = 0.0; const double _kSplashUnconfirmedVelocity = 0.2; @@ -25,7 +27,7 @@ double _getSplashTargetSize(Size bounds, Point position) { } class InkSplash { - InkSplash(this.pointer, this.position, this.well) { + InkSplash(this.position, this.well) { _targetRadius = _getSplashTargetSize(well.size, position); _radius = new AnimatedValue( _kSplashInitialSize, end: _targetRadius, curve: easeOut); @@ -33,11 +35,12 @@ class InkSplash { _performance = new ValueAnimation( variable: _radius, duration: new Duration(milliseconds: (_targetRadius / _kSplashUnconfirmedVelocity).floor()) - )..addListener(_handleRadiusChange) - ..play(); + )..addListener(_handleRadiusChange); + + // Wait kTapTimeout to avoid creating tiny splashes during scrolls. + _startTimer = new Timer(kTapTimeout, _play); } - final int pointer; final Point position; final RenderInkWell well; @@ -45,20 +48,39 @@ class InkSplash { double _pinnedRadius; AnimatedValue _radius; AnimationPerformance _performance; + Timer _startTimer; + + bool _cancelStartTimer() { + if (_startTimer != null) { + _startTimer.cancel(); + _startTimer = null; + return true; + } + return false; + } + + void _play() { + _cancelStartTimer(); + _performance.play(); + } void _updateVelocity(double velocity) { int duration = (_targetRadius / velocity).floor(); - _performance - ..duration = new Duration(milliseconds: duration) - ..play(); + _performance.duration = new Duration(milliseconds: duration); + _play(); } void confirm() { + if (_cancelStartTimer()) + return; _updateVelocity(_kSplashConfirmedVelocity); + _pinnedRadius = null; } void cancel() { - _updateVelocity(_kSplashCancelledVelocity); + if (_cancelStartTimer()) + return; + _updateVelocity(_kSplashCanceledVelocity); _pinnedRadius = _radius.value; } @@ -77,38 +99,95 @@ class InkSplash { } class RenderInkWell extends RenderProxyBox { - RenderInkWell({ RenderBox child }) : super(child); + RenderInkWell({ + RenderBox child, + GestureTapCallback onTap, + GestureLongPressCallback onLongPress + }) : super(child) { + this.onTap = onTap; + this.onLongPress = onLongPress; + } + + GestureTapCallback get onTap => _onTap; + GestureTapCallback _onTap; + void set onTap (GestureTapCallback value) { + _onTap = value; + _syncTapRecognizer(); + } + + GestureTapCallback get onLongPress => _onLongPress; + GestureTapCallback _onLongPress; + void set onLongPress (GestureTapCallback value) { + _onLongPress = value; + _syncLongPressRecognizer(); + } final List _splashes = new List(); + TapGestureRecognizer _tap; + LongPressGestureRecognizer _longPress; + void handleEvent(sky.Event event, BoxHitTestEntry entry) { - // TODO(abarth): We should trigger these effects based on gestures. - // https://github.com/flutter/engine/issues/1271 - if (event is sky.PointerEvent) { - switch (event.type) { - case 'pointerdown': - _startSplash(event.pointer, entry.localPosition); - break; - case 'pointerup': - _confirmSplash(event.pointer); - break; - } + if (event.type == 'pointerdown' && (_tap != null || _longPress != null)) { + _tap?.addPointer(event); + _longPress?.addPointer(event); + _splashes.add(new InkSplash(entry.localPosition, this)); } } - void _startSplash(int pointer, Point position) { - _splashes.add(new InkSplash(pointer, position, this)); - markNeedsPaint(); + void attach() { + super.attach(); + _syncTapRecognizer(); + _syncLongPressRecognizer(); } - void _forEachSplash(int pointer, Function callback) { - _splashes.where((splash) => splash.pointer == pointer) - .forEach(callback); + void detach() { + _disposeTapRecognizer(); + _disposeLongPressRecognizer(); + super.detach(); } - void _confirmSplash(int pointer) { - _forEachSplash(pointer, (splash) { splash.confirm(); }); - markNeedsPaint(); + void _syncTapRecognizer() { + if (onTap == null) { + _disposeTapRecognizer(); + } else { + _tap ??= new TapGestureRecognizer(router: FlutterBinding.instance.pointerRouter) + ..onTap = _handleTap + ..onTapCancel = _handleTapCancel; + } + } + + void _disposeTapRecognizer() { + _tap?.dispose(); + _tap = null; + } + + void _syncLongPressRecognizer() { + if (onLongPress == null) { + _disposeLongPressRecognizer(); + } else { + _longPress ??= new LongPressGestureRecognizer(router: FlutterBinding.instance.pointerRouter) + ..onLongPress = _handleLongPress; + } + } + + void _disposeLongPressRecognizer() { + _longPress?.dispose(); + _longPress = null; + } + + void _handleTap() { + _splashes.last?.confirm(); + onTap(); + } + + void _handleTapCancel() { + _splashes.last?.cancel(); + } + + void _handleLongPress() { + _splashes.last?.confirm(); + onLongPress(); } void paint(PaintingContext context, Offset offset) { @@ -126,7 +205,20 @@ class RenderInkWell extends RenderProxyBox { } class InkWell extends OneChildRenderObjectWidget { - InkWell({ Key key, Widget child }) : super(key: key, child: child); + InkWell({ + Key key, + Widget child, + this.onTap, + this.onLongPress + }) : super(key: key, child: child); - RenderInkWell createRenderObject() => new RenderInkWell(); + final GestureTapCallback onTap; + final GestureLongPressCallback onLongPress; + + RenderInkWell createRenderObject() => new RenderInkWell(onTap: onTap, onLongPress: onLongPress); + + void updateRenderObject(RenderInkWell renderObject, InkWell oldWidget) { + renderObject.onTap = onTap; + renderObject.onLongPress = onLongPress; + } } diff --git a/packages/flutter/lib/src/widgets/material_button.dart b/packages/flutter/lib/src/widgets/material_button.dart index c5dfb0a7be3..b18cc313c12 100644 --- a/packages/flutter/lib/src/widgets/material_button.dart +++ b/packages/flutter/lib/src/widgets/material_button.dart @@ -2,10 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:sky/gestures.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/button_state.dart'; import 'package:sky/src/widgets/framework.dart'; -import 'package:sky/src/widgets/gesture_detector.dart'; import 'package:sky/src/widgets/ink_well.dart'; import 'package:sky/src/widgets/material.dart'; @@ -22,7 +22,7 @@ abstract class MaterialButton extends StatefulComponent { final Widget child; final bool enabled; - final Function onPressed; + final GestureTapCallback onPressed; } abstract class MaterialButtonState extends ButtonState { @@ -37,17 +37,17 @@ abstract class MaterialButtonState extends ButtonState child: config.child // TODO(ianh): figure out a way to compell the child to have gray text when disabled... ) ); - return new GestureDetector( - onTap: config.enabled ? config.onPressed : null, - child: new Container( - height: 36.0, - constraints: new BoxConstraints(minWidth: 88.0), - margin: new EdgeDims.all(8.0), - child: new Material( - type: MaterialType.button, - child: config.enabled ? new InkWell(child: contents) : contents, - level: level, - color: getColor(context) + return new Container( + height: 36.0, + constraints: new BoxConstraints(minWidth: 88.0), + margin: new EdgeDims.all(8.0), + child: new Material( + type: MaterialType.button, + level: level, + color: getColor(context), + child: new InkWell( + onTap: config.enabled ? config.onPressed : null, + child: contents ) ) ); diff --git a/packages/flutter/lib/src/widgets/popup_menu.dart b/packages/flutter/lib/src/widgets/popup_menu.dart index f66f1007c78..c06e9539c82 100644 --- a/packages/flutter/lib/src/widgets/popup_menu.dart +++ b/packages/flutter/lib/src/widgets/popup_menu.dart @@ -11,7 +11,7 @@ import 'package:sky/painting.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/focus.dart'; import 'package:sky/src/widgets/framework.dart'; -import 'package:sky/src/widgets/gesture_detector.dart'; +import 'package:sky/src/widgets/ink_well.dart'; import 'package:sky/src/widgets/navigator.dart'; import 'package:sky/src/widgets/popup_menu_item.dart'; import 'package:sky/src/widgets/scrollable.dart'; @@ -94,7 +94,7 @@ class PopupMenuState extends State { children.add(new FadeTransition( performance: config.performance, opacity: new AnimatedValue(0.0, end: 1.0, interval: new Interval(start, end)), - child: new GestureDetector( + child: new InkWell( onTap: () { config.navigator.pop(config.items[i].value); }, child: config.items[i] )) diff --git a/packages/flutter/lib/src/widgets/popup_menu_item.dart b/packages/flutter/lib/src/widgets/popup_menu_item.dart index 1e44c23c2db..d52d8e26ab3 100644 --- a/packages/flutter/lib/src/widgets/popup_menu_item.dart +++ b/packages/flutter/lib/src/widgets/popup_menu_item.dart @@ -4,7 +4,6 @@ import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/framework.dart'; -import 'package:sky/src/widgets/ink_well.dart'; import 'package:sky/src/widgets/theme.dart'; const double _kMenuItemHeight = 48.0; @@ -21,15 +20,13 @@ class PopupMenuItem extends StatelessComponent { final dynamic value; Widget build(BuildContext context) { - return new InkWell( - child: new Container( - height: _kMenuItemHeight, - child: new DefaultTextStyle( - style: Theme.of(context).text.subhead, - child: new Baseline( - baseline: _kMenuItemHeight - _kBaselineOffsetFromBottom, - child: child - ) + return new Container( + height: _kMenuItemHeight, + child: new DefaultTextStyle( + style: Theme.of(context).text.subhead, + child: new Baseline( + baseline: _kMenuItemHeight - _kBaselineOffsetFromBottom, + child: child ) ) ); diff --git a/packages/flutter/lib/src/widgets/raised_button.dart b/packages/flutter/lib/src/widgets/raised_button.dart index bf666f27830..22c596511c2 100644 --- a/packages/flutter/lib/src/widgets/raised_button.dart +++ b/packages/flutter/lib/src/widgets/raised_button.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:sky/gestures.dart'; import 'package:sky/material.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/framework.dart'; @@ -13,7 +14,7 @@ class RaisedButton extends MaterialButton { Key key, Widget child, bool enabled: true, - Function onPressed + GestureTapCallback onPressed }) : super(key: key, child: child, enabled: enabled, diff --git a/packages/flutter/lib/src/widgets/snack_bar.dart b/packages/flutter/lib/src/widgets/snack_bar.dart index 82ff38feb3f..179e8320ce3 100644 --- a/packages/flutter/lib/src/widgets/snack_bar.dart +++ b/packages/flutter/lib/src/widgets/snack_bar.dart @@ -2,10 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. - import 'package:sky/animation.dart'; -import 'package:sky/painting.dart'; +import 'package:sky/gestures.dart'; import 'package:sky/material.dart'; +import 'package:sky/painting.dart'; import 'package:sky/src/widgets/animated_component.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/framework.dart'; @@ -28,7 +28,7 @@ class SnackBarAction extends StatelessComponent { } final String label; - final Function onPressed; + final GestureTapCallback onPressed; Widget build(BuildContext) { return new GestureDetector( diff --git a/packages/flutter/lib/src/widgets/tabs.dart b/packages/flutter/lib/src/widgets/tabs.dart index d65957c4333..c00bc8ba6fc 100644 --- a/packages/flutter/lib/src/widgets/tabs.dart +++ b/packages/flutter/lib/src/widgets/tabs.dart @@ -7,9 +7,10 @@ import 'dart:sky' as sky; import 'package:newton/newton.dart'; import 'package:sky/animation.dart'; +import 'package:sky/gestures.dart'; +import 'package:sky/material.dart'; import 'package:sky/painting.dart'; import 'package:sky/rendering.dart'; -import 'package:sky/material.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/gesture_detector.dart'; @@ -307,6 +308,7 @@ class TabLabel { class Tab extends StatelessComponent { Tab({ Key key, + this.onSelected, this.label, this.color, this.selected: false, @@ -315,6 +317,7 @@ class Tab extends StatelessComponent { assert(label.text != null || label.icon != null); } + final GestureTapCallback onSelected; final TabLabel label; final Color color; final bool selected; @@ -359,7 +362,10 @@ class Tab extends StatelessComponent { padding: _kTabLabelPadding ); - return new InkWell(child: centeredLabel); + return new InkWell( + onTap: onSelected, + child: centeredLabel + ); } } @@ -458,7 +464,7 @@ class TabBarState extends ScrollableState { .clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset); } - void _handleTap(int tabIndex) { + void _handleTabSelected(int tabIndex) { if (tabIndex != config.selectedIndex) { if (_tabWidths != null) { if (config.isScrollable) @@ -471,14 +477,12 @@ class TabBarState extends ScrollableState { } Widget _toTab(TabLabel label, int tabIndex, Color color, Color selectedColor) { - return new GestureDetector( - onTap: () => _handleTap(tabIndex), - child: new Tab( - label: label, - color: color, - selected: tabIndex == config.selectedIndex, - selectedColor: selectedColor - ) + return new Tab( + onSelected: () => _handleTabSelected(tabIndex), + label: label, + color: color, + selected: tabIndex == config.selectedIndex, + selectedColor: selectedColor ); }