Unverified Commit 27eeb972 authored by Hans Muller's avatar Hans Muller Committed by GitHub

TextField splash integration (#14055)

parent c09736bb
...@@ -14,11 +14,11 @@ import 'material.dart'; ...@@ -14,11 +14,11 @@ import 'material.dart';
const Duration _kUnconfirmedRippleDuration = const Duration(seconds: 1); const Duration _kUnconfirmedRippleDuration = const Duration(seconds: 1);
const Duration _kFadeInDuration = const Duration(milliseconds: 75); const Duration _kFadeInDuration = const Duration(milliseconds: 75);
const Duration _kRadiusDuration = const Duration(milliseconds: 225); const Duration _kRadiusDuration = const Duration(milliseconds: 225);
const Duration _kFadeOutDuration = const Duration(milliseconds: 450); const Duration _kFadeOutDuration = const Duration(milliseconds: 375);
const Duration _kCancelDuration = const Duration(milliseconds: 75); const Duration _kCancelDuration = const Duration(milliseconds: 75);
// The fade out begins 300ms after the _fadeOutController starts. See confirm(). // The fade out begins 225ms after the _fadeOutController starts. See confirm().
const double _kFadeOutIntervalStart = 300.0 / 450.0; const double _kFadeOutIntervalStart = 225.0 / 375.0;
RectCallback _getClipCallback(RenderBox referenceBox, bool containedInkWell, RectCallback rectCallback) { RectCallback _getClipCallback(RenderBox referenceBox, bool containedInkWell, RectCallback rectCallback) {
if (rectCallback != null) { if (rectCallback != null) {
...@@ -31,19 +31,10 @@ RectCallback _getClipCallback(RenderBox referenceBox, bool containedInkWell, Rec ...@@ -31,19 +31,10 @@ RectCallback _getClipCallback(RenderBox referenceBox, bool containedInkWell, Rec
} }
double _getTargetRadius(RenderBox referenceBox, bool containedInkWell, RectCallback rectCallback, Offset position) { double _getTargetRadius(RenderBox referenceBox, bool containedInkWell, RectCallback rectCallback, Offset position) {
if (containedInkWell) { final Size size = rectCallback != null ? rectCallback().size : referenceBox.size;
final Size size = rectCallback != null ? rectCallback().size : referenceBox.size; final double d1 = size.bottomRight(Offset.zero).distance;
return _getRippleRadiusForPositionInSize(size, position); final double d2 = (size.topRight(Offset.zero) - size.bottomLeft(Offset.zero)).distance;
} return math.max(d1, d2) / 2.0;
return Material.defaultSplashRadius;
}
double _getRippleRadiusForPositionInSize(Size bounds, Offset position) {
final double d1 = (position - bounds.topLeft(Offset.zero)).distance;
final double d2 = (position - bounds.topRight(Offset.zero)).distance;
final double d3 = (position - bounds.bottomLeft(Offset.zero)).distance;
final double d4 = (position - bounds.bottomRight(Offset.zero)).distance;
return math.max(math.max(d1, d2), math.max(d3, d4)).ceilToDouble();
} }
class _InkRippleFactory extends InteractiveInkFeatureFactory { class _InkRippleFactory extends InteractiveInkFeatureFactory {
...@@ -205,7 +196,9 @@ class InkRipple extends InteractiveInkFeature { ...@@ -205,7 +196,9 @@ class InkRipple extends InteractiveInkFeature {
@override @override
void cancel() { void cancel() {
_fadeInController.stop(); _fadeInController.stop();
_fadeOutController.animateTo(1.0, duration: _kCancelDuration); _fadeOutController
..value = 1.0 - _fadeInController.value
..animateTo(1.0, duration: _kCancelDuration);
} }
void _handleAlphaStatusChanged(AnimationStatus status) { void _handleAlphaStatusChanged(AnimationStatus status) {
......
...@@ -2,12 +2,16 @@ ...@@ -2,12 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:collection';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'feedback.dart'; import 'feedback.dart';
import 'ink_well.dart' show InteractiveInkFeature;
import 'input_decorator.dart'; import 'input_decorator.dart';
import 'material.dart'; import 'material.dart';
import 'text_selection.dart'; import 'text_selection.dart';
...@@ -275,9 +279,12 @@ class TextField extends StatefulWidget { ...@@ -275,9 +279,12 @@ class TextField extends StatefulWidget {
} }
} }
class _TextFieldState extends State<TextField> { class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixin {
final GlobalKey<EditableTextState> _editableTextKey = new GlobalKey<EditableTextState>(); final GlobalKey<EditableTextState> _editableTextKey = new GlobalKey<EditableTextState>();
Set<InteractiveInkFeature> _splashes;
InteractiveInkFeature _currentSplash;
TextEditingController _controller; TextEditingController _controller;
TextEditingController get _effectiveController => widget.controller ?? _controller; TextEditingController get _effectiveController => widget.controller ?? _controller;
...@@ -332,13 +339,104 @@ class _TextFieldState extends State<TextField> { ...@@ -332,13 +339,104 @@ class _TextFieldState extends State<TextField> {
_editableTextKey.currentState?.requestKeyboard(); _editableTextKey.currentState?.requestKeyboard();
} }
void _onSelectionChanged(BuildContext context, SelectionChangedCause cause) { void _handleSelectionChanged(TextSelection selection, SelectionChangedCause cause) {
if (cause == SelectionChangedCause.longPress) if (cause == SelectionChangedCause.longPress)
Feedback.forLongPress(context); Feedback.forLongPress(context);
} }
InteractiveInkFeature _createInkFeature(TapDownDetails details) {
final MaterialInkController inkController = Material.of(context);
final RenderBox referenceBox = InputDecorator.containerOf(_editableTextKey.currentContext);
final Offset position = referenceBox.globalToLocal(details.globalPosition);
final Color color = Theme.of(context).splashColor;
InteractiveInkFeature splash;
void handleRemoved() {
if (_splashes != null) {
assert(_splashes.contains(splash));
_splashes.remove(splash);
if (_currentSplash == splash)
_currentSplash = null;
updateKeepAlive();
} // else we're probably in deactivate()
}
splash = Theme.of(context).splashFactory.create(
controller: inkController,
referenceBox: referenceBox,
position: position,
color: color,
containedInkWell: true,
// TODO(hansmuller): splash clip borderRadius should match the input decorator's border.
borderRadius: BorderRadius.zero,
onRemoved: handleRemoved,
);
return splash;
}
RenderEditable get _renderEditable => _editableTextKey.currentState.renderEditable;
void _handleTapDown(TapDownDetails details) {
_renderEditable.handleTapDown(details);
_startSplash(details);
}
void _handleTap() {
_renderEditable.handleTap();
_requestKeyboard();
_confirmCurrentSplash();
}
void _handleTapCancel() {
_renderEditable.handleTapCancel();
_cancelCurrentSplash();
}
void _handleLongPress() {
_renderEditable.handleLongPress();
_confirmCurrentSplash();
}
void _startSplash(TapDownDetails details) {
if (_effectiveFocusNode.hasFocus)
return;
final InteractiveInkFeature splash = _createInkFeature(details);
_splashes ??= new HashSet<InteractiveInkFeature>();
_splashes.add(splash);
_currentSplash = splash;
updateKeepAlive();
}
void _confirmCurrentSplash() {
_currentSplash?.confirm();
_currentSplash = null;
}
void _cancelCurrentSplash() {
_currentSplash?.cancel();
_currentSplash = null;
}
@override
bool get wantKeepAlive => _splashes != null && _splashes.isNotEmpty;
@override
void deactivate() {
if (_splashes != null) {
final Set<InteractiveInkFeature> splashes = _splashes;
_splashes = null;
for (InteractiveInkFeature splash in splashes)
splash.dispose();
_currentSplash = null;
}
assert(_currentSplash == null);
super.deactivate();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); // See AutomaticKeepAliveClientMixin.
final ThemeData themeData = Theme.of(context); final ThemeData themeData = Theme.of(context);
final TextStyle style = widget.style ?? themeData.textTheme.subhead; final TextStyle style = widget.style ?? themeData.textTheme.subhead;
final TextEditingController controller = _effectiveController; final TextEditingController controller = _effectiveController;
...@@ -366,8 +464,9 @@ class _TextFieldState extends State<TextField> { ...@@ -366,8 +464,9 @@ class _TextFieldState extends State<TextField> {
: materialTextSelectionControls, : materialTextSelectionControls,
onChanged: widget.onChanged, onChanged: widget.onChanged,
onSubmitted: widget.onSubmitted, onSubmitted: widget.onSubmitted,
onSelectionChanged: (TextSelection _, SelectionChangedCause cause) => _onSelectionChanged(context, cause), onSelectionChanged: _handleSelectionChanged,
inputFormatters: formatters, inputFormatters: formatters,
rendererIgnoresPointer: true,
), ),
); );
...@@ -395,10 +494,13 @@ class _TextFieldState extends State<TextField> { ...@@ -395,10 +494,13 @@ class _TextFieldState extends State<TextField> {
_requestKeyboard(); _requestKeyboard();
}, },
child: new GestureDetector( child: new GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.translucent,
onTap: _requestKeyboard, onTapDown: _handleTapDown,
child: child, onTap: _handleTap,
onTapCancel: _handleTapCancel,
onLongPress: _handleLongPress,
excludeFromSemantics: true, excludeFromSemantics: true,
child: child,
), ),
); );
} }
......
...@@ -131,11 +131,13 @@ class RenderEditable extends RenderBox { ...@@ -131,11 +131,13 @@ class RenderEditable extends RenderBox {
@required ViewportOffset offset, @required ViewportOffset offset,
this.onSelectionChanged, this.onSelectionChanged,
this.onCaretChanged, this.onCaretChanged,
this.ignorePointer: false,
}) : assert(textAlign != null), }) : assert(textAlign != null),
assert(textDirection != null, 'RenderEditable created without a textDirection.'), assert(textDirection != null, 'RenderEditable created without a textDirection.'),
assert(maxLines == null || maxLines > 0), assert(maxLines == null || maxLines > 0),
assert(textScaleFactor != null), assert(textScaleFactor != null),
assert(offset != null), assert(offset != null),
assert(ignorePointer != null),
_textPainter = new TextPainter( _textPainter = new TextPainter(
text: text, text: text,
textAlign: textAlign, textAlign: textAlign,
...@@ -167,6 +169,13 @@ class RenderEditable extends RenderBox { ...@@ -167,6 +169,13 @@ class RenderEditable extends RenderBox {
/// Called during the paint phase when the caret location changes. /// Called during the paint phase when the caret location changes.
CaretChangedHandler onCaretChanged; CaretChangedHandler onCaretChanged;
/// If true [handleEvent] does nothing and it's assumed that this
/// renderer will be notified of input gestures via [handleTapDown],
/// [handleTap], [handleTapCancel], and [handleLongPress].
///
/// The default value of this property is false.
bool ignorePointer;
Rect _lastCaretRect; Rect _lastCaretRect;
/// Marks the render object as needing to be laid out again and have its text /// Marks the render object as needing to be laid out again and have its text
...@@ -550,6 +559,8 @@ class RenderEditable extends RenderBox { ...@@ -550,6 +559,8 @@ class RenderEditable extends RenderBox {
@override @override
void handleEvent(PointerEvent event, BoxHitTestEntry entry) { void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
if (ignorePointer)
return;
assert(debugHandleEvent(event, entry)); assert(debugHandleEvent(event, entry));
if (event is PointerDownEvent && onSelectionChanged != null) { if (event is PointerDownEvent && onSelectionChanged != null) {
_tap.addPointer(event); _tap.addPointer(event);
...@@ -559,11 +570,15 @@ class RenderEditable extends RenderBox { ...@@ -559,11 +570,15 @@ class RenderEditable extends RenderBox {
Offset _lastTapDownPosition; Offset _lastTapDownPosition;
Offset _longPressPosition; Offset _longPressPosition;
void _handleTapDown(TapDownDetails details) { void handleTapDown(TapDownDetails details) {
_lastTapDownPosition = details.globalPosition + -_paintOffset; _lastTapDownPosition = details.globalPosition + -_paintOffset;
} }
void _handleTapDown(TapDownDetails details) {
assert(!ignorePointer);
handleTapDown(details);
}
void _handleTap() { void handleTap() {
_layoutText(constraints.maxWidth); _layoutText(constraints.maxWidth);
assert(_lastTapDownPosition != null); assert(_lastTapDownPosition != null);
final Offset globalPosition = _lastTapDownPosition; final Offset globalPosition = _lastTapDownPosition;
...@@ -573,14 +588,22 @@ class RenderEditable extends RenderBox { ...@@ -573,14 +588,22 @@ class RenderEditable extends RenderBox {
onSelectionChanged(new TextSelection.fromPosition(position), this, SelectionChangedCause.tap); onSelectionChanged(new TextSelection.fromPosition(position), this, SelectionChangedCause.tap);
} }
} }
void _handleTap() {
assert(!ignorePointer);
handleTap();
}
void _handleTapCancel() { void handleTapCancel() {
// longPress arrives after tapCancel, so remember the tap position. // longPress arrives after tapCancel, so remember the tap position.
_longPressPosition = _lastTapDownPosition; _longPressPosition = _lastTapDownPosition;
_lastTapDownPosition = null; _lastTapDownPosition = null;
} }
void _handleTapCancel() {
assert(!ignorePointer);
handleTapCancel();
}
void _handleLongPress() { void handleLongPress() {
_layoutText(constraints.maxWidth); _layoutText(constraints.maxWidth);
final Offset globalPosition = _longPressPosition; final Offset globalPosition = _longPressPosition;
_longPressPosition = null; _longPressPosition = null;
...@@ -589,6 +612,10 @@ class RenderEditable extends RenderBox { ...@@ -589,6 +612,10 @@ class RenderEditable extends RenderBox {
onSelectionChanged(_selectWordAtOffset(position), this, SelectionChangedCause.longPress); onSelectionChanged(_selectWordAtOffset(position), this, SelectionChangedCause.longPress);
} }
} }
void _handleLongPress() {
assert(!ignorePointer);
handleLongPress();
}
TextSelection _selectWordAtOffset(TextPosition position) { TextSelection _selectWordAtOffset(TextPosition position) {
assert(_textLayoutLastWidth == constraints.maxWidth); assert(_textLayoutLastWidth == constraints.maxWidth);
......
...@@ -149,8 +149,8 @@ class EditableText extends StatefulWidget { ...@@ -149,8 +149,8 @@ class EditableText extends StatefulWidget {
/// [TextInputType.text] unless [maxLines] is greater than one, when it will /// [TextInputType.text] unless [maxLines] is greater than one, when it will
/// default to [TextInputType.multiline]. /// default to [TextInputType.multiline].
/// ///
/// The [controller], [focusNode], [style], [cursorColor], and [textAlign] /// The [controller], [focusNode], [style], [cursorColor], [textAlign],
/// arguments must not be null. /// and [rendererIgnoresPointer], arguments must not be null.
EditableText({ EditableText({
Key key, Key key,
@required this.controller, @required this.controller,
...@@ -171,6 +171,7 @@ class EditableText extends StatefulWidget { ...@@ -171,6 +171,7 @@ class EditableText extends StatefulWidget {
this.onSubmitted, this.onSubmitted,
this.onSelectionChanged, this.onSelectionChanged,
List<TextInputFormatter> inputFormatters, List<TextInputFormatter> inputFormatters,
this.rendererIgnoresPointer: false,
}) : assert(controller != null), }) : assert(controller != null),
assert(focusNode != null), assert(focusNode != null),
assert(obscureText != null), assert(obscureText != null),
...@@ -180,6 +181,7 @@ class EditableText extends StatefulWidget { ...@@ -180,6 +181,7 @@ class EditableText extends StatefulWidget {
assert(textAlign != null), assert(textAlign != null),
assert(maxLines == null || maxLines > 0), assert(maxLines == null || maxLines > 0),
assert(autofocus != null), assert(autofocus != null),
assert(rendererIgnoresPointer != null),
keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline), keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
inputFormatters = maxLines == 1 inputFormatters = maxLines == 1
? ( ? (
...@@ -279,6 +281,12 @@ class EditableText extends StatefulWidget { ...@@ -279,6 +281,12 @@ class EditableText extends StatefulWidget {
/// in the provided order when the text input changes. /// in the provided order when the text input changes.
final List<TextInputFormatter> inputFormatters; final List<TextInputFormatter> inputFormatters;
/// If true, the [RenderEditable] created by this widget will not handle
/// pointer events, see [renderEditable] and [RenderEditable.ignorePointer].
///
/// This property is false by default.
final bool rendererIgnoresPointer;
@override @override
EditableTextState createState() => new EditableTextState(); EditableTextState createState() => new EditableTextState();
...@@ -303,6 +311,7 @@ class EditableText extends StatefulWidget { ...@@ -303,6 +311,7 @@ class EditableText extends StatefulWidget {
class EditableTextState extends State<EditableText> with AutomaticKeepAliveClientMixin implements TextInputClient { class EditableTextState extends State<EditableText> with AutomaticKeepAliveClientMixin implements TextInputClient {
Timer _cursorTimer; Timer _cursorTimer;
final ValueNotifier<bool> _showCursor = new ValueNotifier<bool>(false); final ValueNotifier<bool> _showCursor = new ValueNotifier<bool>(false);
final GlobalKey _editableKey = new GlobalKey();
TextInputConnection _textInputConnection; TextInputConnection _textInputConnection;
TextSelectionOverlay _selectionOverlay; TextSelectionOverlay _selectionOverlay;
...@@ -628,6 +637,12 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -628,6 +637,12 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
return result; return result;
} }
/// The renderer for this widget's [Editable] descendant.
///
/// This property is typically used to notify the renderer of input gestures
/// when [ignorePointer] is true. See [RenderEditable.ignorePointer].
RenderEditable get renderEditable => _editableKey.currentContext.findRenderObject();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
FocusScope.of(context).reparentIfNeeded(widget.focusNode); FocusScope.of(context).reparentIfNeeded(widget.focusNode);
...@@ -640,6 +655,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -640,6 +655,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
return new CompositedTransformTarget( return new CompositedTransformTarget(
link: _layerLink, link: _layerLink,
child: new _Editable( child: new _Editable(
key: _editableKey,
value: _value, value: _value,
style: widget.style, style: widget.style,
cursorColor: widget.cursorColor, cursorColor: widget.cursorColor,
...@@ -656,6 +672,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -656,6 +672,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
offset: offset, offset: offset,
onSelectionChanged: _handleSelectionChanged, onSelectionChanged: _handleSelectionChanged,
onCaretChanged: _handleCaretChanged, onCaretChanged: _handleCaretChanged,
rendererIgnoresPointer: widget.rendererIgnoresPointer,
), ),
); );
}, },
...@@ -682,7 +699,9 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -682,7 +699,9 @@ class _Editable extends LeafRenderObjectWidget {
this.offset, this.offset,
this.onSelectionChanged, this.onSelectionChanged,
this.onCaretChanged, this.onCaretChanged,
this.rendererIgnoresPointer: false,
}) : assert(textDirection != null), }) : assert(textDirection != null),
assert(rendererIgnoresPointer != null),
super(key: key); super(key: key);
final TextEditingValue value; final TextEditingValue value;
...@@ -701,6 +720,7 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -701,6 +720,7 @@ class _Editable extends LeafRenderObjectWidget {
final ViewportOffset offset; final ViewportOffset offset;
final SelectionChangedHandler onSelectionChanged; final SelectionChangedHandler onSelectionChanged;
final CaretChangedHandler onCaretChanged; final CaretChangedHandler onCaretChanged;
final bool rendererIgnoresPointer;
@override @override
RenderEditable createRenderObject(BuildContext context) { RenderEditable createRenderObject(BuildContext context) {
...@@ -718,6 +738,7 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -718,6 +738,7 @@ class _Editable extends LeafRenderObjectWidget {
offset: offset, offset: offset,
onSelectionChanged: onSelectionChanged, onSelectionChanged: onSelectionChanged,
onCaretChanged: onCaretChanged, onCaretChanged: onCaretChanged,
ignorePointer: rendererIgnoresPointer,
); );
} }
...@@ -736,7 +757,8 @@ class _Editable extends LeafRenderObjectWidget { ...@@ -736,7 +757,8 @@ class _Editable extends LeafRenderObjectWidget {
..selection = value.selection ..selection = value.selection
..offset = offset ..offset = offset
..onSelectionChanged = onSelectionChanged ..onSelectionChanged = onSelectionChanged
..onCaretChanged = onCaretChanged; ..onCaretChanged = onCaretChanged
..ignorePointer = rendererIgnoresPointer;
} }
TextSpan get _styledTextSpan { TextSpan get _styledTextSpan {
......
...@@ -140,7 +140,8 @@ void main() { ...@@ -140,7 +140,8 @@ void main() {
// At this point the splash radius has expanded to its limit: 5 past the // At this point the splash radius has expanded to its limit: 5 past the
// ink well's radius parameter. The splash center has moved to its final // ink well's radius parameter. The splash center has moved to its final
// location at the inkwell's center and the fade-out is about to start. // location at the inkwell's center and the fade-out is about to start.
await tester.pump(const Duration(milliseconds: 225)); // The fade-out begins at 225ms = 50ms + 25ms + 150ms.
await tester.pump(const Duration(milliseconds: 150));
expect(box, paints..something((Symbol method, List<dynamic> arguments) { expect(box, paints..something((Symbol method, List<dynamic> arguments) {
if (method != #drawCircle) if (method != #drawCircle)
return false; return false;
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
int confirmCount = 0;
int cancelCount = 0;
class TestInkSplash extends InkSplash {
TestInkSplash({
MaterialInkController controller,
RenderBox referenceBox,
Offset position,
Color color,
bool containedInkWell: false,
RectCallback rectCallback,
BorderRadius borderRadius,
double radius,
VoidCallback onRemoved,
}) : super(
controller: controller,
referenceBox: referenceBox,
position: position,
color: color,
containedInkWell: containedInkWell,
rectCallback: rectCallback,
borderRadius: borderRadius,
radius: radius,
onRemoved: onRemoved,
);
@override
void confirm() {
confirmCount += 1;
super.confirm();
}
@override
void cancel() {
cancelCount += 1;
super.cancel();
}
}
class TestInkSplashFactory extends InteractiveInkFeatureFactory {
const TestInkSplashFactory();
@override
InteractiveInkFeature create({
MaterialInkController controller,
RenderBox referenceBox,
Offset position,
Color color,
bool containedInkWell: false,
RectCallback rectCallback,
BorderRadius borderRadius,
double radius,
VoidCallback onRemoved,
}) {
return new TestInkSplash(
controller: controller,
referenceBox: referenceBox,
position: position,
color: color,
containedInkWell: containedInkWell,
rectCallback: rectCallback,
borderRadius: borderRadius,
radius: radius,
onRemoved: onRemoved,
);
}
}
void main() {
testWidgets('Tap and no focus causes a splash', (WidgetTester tester) async {
final Key textField1 = new UniqueKey();
final Key textField2 = new UniqueKey();
await tester.pumpWidget(
new MaterialApp(
home: new Theme(
data: new ThemeData.light().copyWith(splashFactory: const TestInkSplashFactory()),
child: new Material(
child: new Container(
alignment: Alignment.topLeft,
child: new Column(
children: <Widget>[
new TextField(
key: textField1,
decoration: const InputDecoration(
labelText: 'label',
),
),
new TextField(
key: textField2,
decoration: const InputDecoration(
labelText: 'label',
),
),
],
),
),
),
),
)
);
confirmCount = 0;
cancelCount = 0;
await tester.tap(find.byKey(textField1));
await tester.pumpAndSettle();
expect(confirmCount, 1);
expect(cancelCount, 0);
// textField1 already has the focus, no new splash
await tester.tap(find.byKey(textField1));
await tester.pumpAndSettle();
expect(confirmCount, 1);
expect(cancelCount, 0);
// textField2 gets the focus and a splash
await tester.tap(find.byKey(textField2));
await tester.pumpAndSettle();
expect(confirmCount, 2);
expect(cancelCount, 0);
// Tap outside of textField1's editable. It still gets focus and splash.
await tester.tapAt(tester.getTopLeft(find.byKey(textField1)));
await tester.pumpAndSettle();
expect(confirmCount, 3);
expect(cancelCount, 0);
// Tap in the center of textField2's editable. It still gets the focus
// and the splash. There is no splash cancel.
await tester.tap(find.byKey(textField2));
await tester.pumpAndSettle();
expect(confirmCount, 4);
expect(cancelCount, 0);
});
testWidgets('Splash cancel', (WidgetTester tester) async {
await tester.pumpWidget(
new MaterialApp(
home: new Theme(
data: new ThemeData.light().copyWith(splashFactory: const TestInkSplashFactory()),
child: new Material(
child: new ListView(
children: <Widget>[
const TextField(
decoration: const InputDecoration(
labelText: 'label1',
),
),
const TextField(
decoration: const InputDecoration(
labelText: 'label2',
),
),
new Container(
height: 1000.0,
color: const Color(0xFF00FF00),
),
],
),
),
),
)
);
confirmCount = 0;
cancelCount = 0;
// Pointer is dragged below the textfield, splash is canceled.
final TestGesture gesture1 = await tester.startGesture(tester.getCenter(find.text('label1')));
await tester.pumpAndSettle();
await gesture1.moveTo(const Offset(400.0, 300.0));
await gesture1.up();
expect(confirmCount, 0);
expect(cancelCount, 1);
// Pointer is dragged upwards causing a scroll, splash is canceled.
final TestGesture gesture2 = await tester.startGesture(tester.getCenter(find.text('label2')));
await tester.pumpAndSettle();
await gesture2.moveBy(const Offset(0.0, -200.0), timeStamp: const Duration(milliseconds: 32));
await gesture2.up();
expect(confirmCount, 0);
expect(cancelCount, 2);
});
}
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