mirror of
https://github.com/flutter/flutter
synced 2024-09-13 05:11:45 +00:00
Feat: TextField can scroll when disabled (#140922)
This PR is adding a flag parameter to the `TextField` widget. This flag controls whether the TextField ignores pointers. The flag takes priority over other TextField behaviors such as enabled, so it can be useful when trying to have a disabled TextField that can be scrolled (behavior observed using TextArea on the web). Adding a flag parameter to `TextField` helps with more customization and flexibility to the widget which can improve user experience. I am open to other ideas. Fixes issue #140147 Before: https://github.com/flutter/flutter/assets/66151079/293e5b4e-3126-4a00-824d-1530aeaa494b After: https://github.com/flutter/flutter/assets/66151079/08c1af09-3bf9-4b49-b684-dda4dd920503 Usage: ```dart child: TextField( ignorePointer: false, enabled: false, ), ```
This commit is contained in:
parent
21ca59e72c
commit
1d5c2c5118
|
@ -285,6 +285,7 @@ class TextField extends StatefulWidget {
|
|||
this.onAppPrivateCommand,
|
||||
this.inputFormatters,
|
||||
this.enabled,
|
||||
this.ignorePointers,
|
||||
this.cursorWidth = 2.0,
|
||||
this.cursorHeight,
|
||||
this.cursorRadius,
|
||||
|
@ -598,6 +599,11 @@ class TextField extends StatefulWidget {
|
|||
/// [InputDecoration.enabled] property.
|
||||
final bool? enabled;
|
||||
|
||||
/// Determines whether this widget ignores pointer events.
|
||||
///
|
||||
/// Defaults to null, and when null, does nothing.
|
||||
final bool? ignorePointers;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.cursorWidth}
|
||||
final double cursorWidth;
|
||||
|
||||
|
@ -979,7 +985,7 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
|
|||
final GlobalKey<EditableTextState> editableTextKey = GlobalKey<EditableTextState>();
|
||||
|
||||
@override
|
||||
bool get selectionEnabled => widget.selectionEnabled;
|
||||
bool get selectionEnabled => widget.selectionEnabled && _isEnabled;
|
||||
// End of API for TextSelectionGestureDetectorBuilderDelegate.
|
||||
|
||||
bool get _isEnabled => widget.enabled ?? widget.decoration?.enabled ?? true;
|
||||
|
@ -1559,7 +1565,7 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
|
|||
onExit: (PointerExitEvent event) => _handleHover(false),
|
||||
child: TextFieldTapRegion(
|
||||
child: IgnorePointer(
|
||||
ignoring: !_isEnabled,
|
||||
ignoring: widget.ignorePointers ?? !_isEnabled,
|
||||
child: AnimatedBuilder(
|
||||
animation: controller, // changes the _currentLength
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
|
|
|
@ -142,6 +142,7 @@ class TextFormField extends FormField<String> {
|
|||
super.validator,
|
||||
List<TextInputFormatter>? inputFormatters,
|
||||
bool? enabled,
|
||||
bool? ignorePointers,
|
||||
double cursorWidth = 2.0,
|
||||
double? cursorHeight,
|
||||
Radius? cursorRadius,
|
||||
|
@ -238,6 +239,7 @@ class TextFormField extends FormField<String> {
|
|||
onSubmitted: onFieldSubmitted,
|
||||
inputFormatters: inputFormatters,
|
||||
enabled: enabled ?? decoration?.enabled ?? true,
|
||||
ignorePointers: ignorePointers,
|
||||
cursorWidth: cursorWidth,
|
||||
cursorHeight: cursorHeight,
|
||||
cursorRadius: cursorRadius,
|
||||
|
|
|
@ -6535,6 +6535,55 @@ void main() {
|
|||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Can scroll multiline input when disabled', (WidgetTester tester) async {
|
||||
final Key textFieldKey = UniqueKey();
|
||||
final TextEditingController controller = _textEditingController(
|
||||
text: kMoreThanFourLines,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
overlay(
|
||||
child: TextField(
|
||||
dragStartBehavior: DragStartBehavior.down,
|
||||
key: textFieldKey,
|
||||
controller: controller,
|
||||
ignorePointers: false,
|
||||
enabled: false,
|
||||
style: const TextStyle(color: Colors.black, fontSize: 34.0),
|
||||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
RenderBox findInputBox() => tester.renderObject(find.byKey(textFieldKey));
|
||||
final RenderBox inputBox = findInputBox();
|
||||
|
||||
// Check that the last line of text is not displayed.
|
||||
final Offset firstPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('First'));
|
||||
final Offset fourthPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('Fourth'));
|
||||
expect(firstPos.dx, fourthPos.dx);
|
||||
expect(firstPos.dy, lessThan(fourthPos.dy));
|
||||
expect(inputBox.hitTest(BoxHitTestResult(), position: inputBox.globalToLocal(firstPos)), isTrue);
|
||||
expect(inputBox.hitTest(BoxHitTestResult(), position: inputBox.globalToLocal(fourthPos)), isFalse);
|
||||
|
||||
final TestGesture gesture = await tester.startGesture(firstPos, pointer: 7);
|
||||
await tester.pump();
|
||||
await gesture.moveBy(const Offset(0.0, -1000.0));
|
||||
await tester.pumpAndSettle();
|
||||
await gesture.moveBy(const Offset(0.0, -1000.0));
|
||||
await tester.pumpAndSettle();
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Now the first line is scrolled up, and the fourth line is visible.
|
||||
final Offset finalFirstPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('First'));
|
||||
final Offset finalFourthPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('Fourth'));
|
||||
|
||||
expect(finalFirstPos.dy, lessThan(firstPos.dy));
|
||||
expect(inputBox.hitTest(BoxHitTestResult(), position: inputBox.globalToLocal(finalFirstPos)), isFalse);
|
||||
expect(inputBox.hitTest(BoxHitTestResult(), position: inputBox.globalToLocal(finalFourthPos)), isTrue);
|
||||
});
|
||||
|
||||
testWidgets('Disabled text field does not have tap action', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
await tester.pumpWidget(
|
||||
|
|
Loading…
Reference in a new issue