TextField splash integration (#14055)
This commit is contained in:
parent
c09736bb58
commit
27eeb9722f
@ -14,11 +14,11 @@ import 'material.dart';
|
||||
const Duration _kUnconfirmedRippleDuration = const Duration(seconds: 1);
|
||||
const Duration _kFadeInDuration = const Duration(milliseconds: 75);
|
||||
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);
|
||||
|
||||
// The fade out begins 300ms after the _fadeOutController starts. See confirm().
|
||||
const double _kFadeOutIntervalStart = 300.0 / 450.0;
|
||||
// The fade out begins 225ms after the _fadeOutController starts. See confirm().
|
||||
const double _kFadeOutIntervalStart = 225.0 / 375.0;
|
||||
|
||||
RectCallback _getClipCallback(RenderBox referenceBox, bool containedInkWell, RectCallback rectCallback) {
|
||||
if (rectCallback != null) {
|
||||
@ -31,19 +31,10 @@ RectCallback _getClipCallback(RenderBox referenceBox, bool containedInkWell, Rec
|
||||
}
|
||||
|
||||
double _getTargetRadius(RenderBox referenceBox, bool containedInkWell, RectCallback rectCallback, Offset position) {
|
||||
if (containedInkWell) {
|
||||
final Size size = rectCallback != null ? rectCallback().size : referenceBox.size;
|
||||
return _getRippleRadiusForPositionInSize(size, position);
|
||||
}
|
||||
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();
|
||||
final double d1 = size.bottomRight(Offset.zero).distance;
|
||||
final double d2 = (size.topRight(Offset.zero) - size.bottomLeft(Offset.zero)).distance;
|
||||
return math.max(d1, d2) / 2.0;
|
||||
}
|
||||
|
||||
class _InkRippleFactory extends InteractiveInkFeatureFactory {
|
||||
@ -205,7 +196,9 @@ class InkRipple extends InteractiveInkFeature {
|
||||
@override
|
||||
void cancel() {
|
||||
_fadeInController.stop();
|
||||
_fadeOutController.animateTo(1.0, duration: _kCancelDuration);
|
||||
_fadeOutController
|
||||
..value = 1.0 - _fadeInController.value
|
||||
..animateTo(1.0, duration: _kCancelDuration);
|
||||
}
|
||||
|
||||
void _handleAlphaStatusChanged(AnimationStatus status) {
|
||||
|
@ -2,12 +2,16 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'feedback.dart';
|
||||
import 'ink_well.dart' show InteractiveInkFeature;
|
||||
import 'input_decorator.dart';
|
||||
import 'material.dart';
|
||||
import 'text_selection.dart';
|
||||
@ -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>();
|
||||
|
||||
Set<InteractiveInkFeature> _splashes;
|
||||
InteractiveInkFeature _currentSplash;
|
||||
|
||||
TextEditingController _controller;
|
||||
TextEditingController get _effectiveController => widget.controller ?? _controller;
|
||||
|
||||
@ -332,13 +339,104 @@ class _TextFieldState extends State<TextField> {
|
||||
_editableTextKey.currentState?.requestKeyboard();
|
||||
}
|
||||
|
||||
void _onSelectionChanged(BuildContext context, SelectionChangedCause cause) {
|
||||
void _handleSelectionChanged(TextSelection selection, SelectionChangedCause cause) {
|
||||
if (cause == SelectionChangedCause.longPress)
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context); // See AutomaticKeepAliveClientMixin.
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
final TextStyle style = widget.style ?? themeData.textTheme.subhead;
|
||||
final TextEditingController controller = _effectiveController;
|
||||
@ -366,8 +464,9 @@ class _TextFieldState extends State<TextField> {
|
||||
: materialTextSelectionControls,
|
||||
onChanged: widget.onChanged,
|
||||
onSubmitted: widget.onSubmitted,
|
||||
onSelectionChanged: (TextSelection _, SelectionChangedCause cause) => _onSelectionChanged(context, cause),
|
||||
onSelectionChanged: _handleSelectionChanged,
|
||||
inputFormatters: formatters,
|
||||
rendererIgnoresPointer: true,
|
||||
),
|
||||
);
|
||||
|
||||
@ -395,10 +494,13 @@ class _TextFieldState extends State<TextField> {
|
||||
_requestKeyboard();
|
||||
},
|
||||
child: new GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: _requestKeyboard,
|
||||
child: child,
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTapDown: _handleTapDown,
|
||||
onTap: _handleTap,
|
||||
onTapCancel: _handleTapCancel,
|
||||
onLongPress: _handleLongPress,
|
||||
excludeFromSemantics: true,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -131,11 +131,13 @@ class RenderEditable extends RenderBox {
|
||||
@required ViewportOffset offset,
|
||||
this.onSelectionChanged,
|
||||
this.onCaretChanged,
|
||||
this.ignorePointer: false,
|
||||
}) : assert(textAlign != null),
|
||||
assert(textDirection != null, 'RenderEditable created without a textDirection.'),
|
||||
assert(maxLines == null || maxLines > 0),
|
||||
assert(textScaleFactor != null),
|
||||
assert(offset != null),
|
||||
assert(ignorePointer != null),
|
||||
_textPainter = new TextPainter(
|
||||
text: text,
|
||||
textAlign: textAlign,
|
||||
@ -167,6 +169,13 @@ class RenderEditable extends RenderBox {
|
||||
/// Called during the paint phase when the caret location changes.
|
||||
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;
|
||||
|
||||
/// Marks the render object as needing to be laid out again and have its text
|
||||
@ -550,6 +559,8 @@ class RenderEditable extends RenderBox {
|
||||
|
||||
@override
|
||||
void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
|
||||
if (ignorePointer)
|
||||
return;
|
||||
assert(debugHandleEvent(event, entry));
|
||||
if (event is PointerDownEvent && onSelectionChanged != null) {
|
||||
_tap.addPointer(event);
|
||||
@ -559,11 +570,15 @@ class RenderEditable extends RenderBox {
|
||||
|
||||
Offset _lastTapDownPosition;
|
||||
Offset _longPressPosition;
|
||||
void _handleTapDown(TapDownDetails details) {
|
||||
void handleTapDown(TapDownDetails details) {
|
||||
_lastTapDownPosition = details.globalPosition + -_paintOffset;
|
||||
}
|
||||
void _handleTapDown(TapDownDetails details) {
|
||||
assert(!ignorePointer);
|
||||
handleTapDown(details);
|
||||
}
|
||||
|
||||
void _handleTap() {
|
||||
void handleTap() {
|
||||
_layoutText(constraints.maxWidth);
|
||||
assert(_lastTapDownPosition != null);
|
||||
final Offset globalPosition = _lastTapDownPosition;
|
||||
@ -573,14 +588,22 @@ class RenderEditable extends RenderBox {
|
||||
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.
|
||||
_longPressPosition = _lastTapDownPosition;
|
||||
_lastTapDownPosition = null;
|
||||
}
|
||||
void _handleTapCancel() {
|
||||
assert(!ignorePointer);
|
||||
handleTapCancel();
|
||||
}
|
||||
|
||||
void _handleLongPress() {
|
||||
void handleLongPress() {
|
||||
_layoutText(constraints.maxWidth);
|
||||
final Offset globalPosition = _longPressPosition;
|
||||
_longPressPosition = null;
|
||||
@ -589,6 +612,10 @@ class RenderEditable extends RenderBox {
|
||||
onSelectionChanged(_selectWordAtOffset(position), this, SelectionChangedCause.longPress);
|
||||
}
|
||||
}
|
||||
void _handleLongPress() {
|
||||
assert(!ignorePointer);
|
||||
handleLongPress();
|
||||
}
|
||||
|
||||
TextSelection _selectWordAtOffset(TextPosition position) {
|
||||
assert(_textLayoutLastWidth == constraints.maxWidth);
|
||||
|
@ -149,8 +149,8 @@ class EditableText extends StatefulWidget {
|
||||
/// [TextInputType.text] unless [maxLines] is greater than one, when it will
|
||||
/// default to [TextInputType.multiline].
|
||||
///
|
||||
/// The [controller], [focusNode], [style], [cursorColor], and [textAlign]
|
||||
/// arguments must not be null.
|
||||
/// The [controller], [focusNode], [style], [cursorColor], [textAlign],
|
||||
/// and [rendererIgnoresPointer], arguments must not be null.
|
||||
EditableText({
|
||||
Key key,
|
||||
@required this.controller,
|
||||
@ -171,6 +171,7 @@ class EditableText extends StatefulWidget {
|
||||
this.onSubmitted,
|
||||
this.onSelectionChanged,
|
||||
List<TextInputFormatter> inputFormatters,
|
||||
this.rendererIgnoresPointer: false,
|
||||
}) : assert(controller != null),
|
||||
assert(focusNode != null),
|
||||
assert(obscureText != null),
|
||||
@ -180,6 +181,7 @@ class EditableText extends StatefulWidget {
|
||||
assert(textAlign != null),
|
||||
assert(maxLines == null || maxLines > 0),
|
||||
assert(autofocus != null),
|
||||
assert(rendererIgnoresPointer != null),
|
||||
keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
|
||||
inputFormatters = maxLines == 1
|
||||
? (
|
||||
@ -279,6 +281,12 @@ class EditableText extends StatefulWidget {
|
||||
/// in the provided order when the text input changes.
|
||||
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
|
||||
EditableTextState createState() => new EditableTextState();
|
||||
|
||||
@ -303,6 +311,7 @@ class EditableText extends StatefulWidget {
|
||||
class EditableTextState extends State<EditableText> with AutomaticKeepAliveClientMixin implements TextInputClient {
|
||||
Timer _cursorTimer;
|
||||
final ValueNotifier<bool> _showCursor = new ValueNotifier<bool>(false);
|
||||
final GlobalKey _editableKey = new GlobalKey();
|
||||
|
||||
TextInputConnection _textInputConnection;
|
||||
TextSelectionOverlay _selectionOverlay;
|
||||
@ -628,6 +637,12 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
FocusScope.of(context).reparentIfNeeded(widget.focusNode);
|
||||
@ -640,6 +655,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
return new CompositedTransformTarget(
|
||||
link: _layerLink,
|
||||
child: new _Editable(
|
||||
key: _editableKey,
|
||||
value: _value,
|
||||
style: widget.style,
|
||||
cursorColor: widget.cursorColor,
|
||||
@ -656,6 +672,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
offset: offset,
|
||||
onSelectionChanged: _handleSelectionChanged,
|
||||
onCaretChanged: _handleCaretChanged,
|
||||
rendererIgnoresPointer: widget.rendererIgnoresPointer,
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -682,7 +699,9 @@ class _Editable extends LeafRenderObjectWidget {
|
||||
this.offset,
|
||||
this.onSelectionChanged,
|
||||
this.onCaretChanged,
|
||||
this.rendererIgnoresPointer: false,
|
||||
}) : assert(textDirection != null),
|
||||
assert(rendererIgnoresPointer != null),
|
||||
super(key: key);
|
||||
|
||||
final TextEditingValue value;
|
||||
@ -701,6 +720,7 @@ class _Editable extends LeafRenderObjectWidget {
|
||||
final ViewportOffset offset;
|
||||
final SelectionChangedHandler onSelectionChanged;
|
||||
final CaretChangedHandler onCaretChanged;
|
||||
final bool rendererIgnoresPointer;
|
||||
|
||||
@override
|
||||
RenderEditable createRenderObject(BuildContext context) {
|
||||
@ -718,6 +738,7 @@ class _Editable extends LeafRenderObjectWidget {
|
||||
offset: offset,
|
||||
onSelectionChanged: onSelectionChanged,
|
||||
onCaretChanged: onCaretChanged,
|
||||
ignorePointer: rendererIgnoresPointer,
|
||||
);
|
||||
}
|
||||
|
||||
@ -736,7 +757,8 @@ class _Editable extends LeafRenderObjectWidget {
|
||||
..selection = value.selection
|
||||
..offset = offset
|
||||
..onSelectionChanged = onSelectionChanged
|
||||
..onCaretChanged = onCaretChanged;
|
||||
..onCaretChanged = onCaretChanged
|
||||
..ignorePointer = rendererIgnoresPointer;
|
||||
}
|
||||
|
||||
TextSpan get _styledTextSpan {
|
||||
|
@ -140,7 +140,8 @@ void main() {
|
||||
// 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
|
||||
// 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) {
|
||||
if (method != #drawCircle)
|
||||
return false;
|
||||
|
194
packages/flutter/test/material/text_field_splash_test.dart
Normal file
194
packages/flutter/test/material/text_field_splash_test.dart
Normal file
@ -0,0 +1,194 @@
|
||||
// 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);
|
||||
});
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user