Unverified Commit f8e9a4ff authored by gaaclarke's avatar gaaclarke Committed by GitHub

Added option to specify you want the keyboard to be dismissed when you scroll. (#52068)

parent 9186dfc3
...@@ -8,15 +8,30 @@ import 'package:flutter/rendering.dart'; ...@@ -8,15 +8,30 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'basic.dart'; import 'basic.dart';
import 'focus_manager.dart';
import 'focus_scope.dart';
import 'framework.dart'; import 'framework.dart';
import 'media_query.dart'; import 'media_query.dart';
import 'notification_listener.dart';
import 'primary_scroll_controller.dart'; import 'primary_scroll_controller.dart';
import 'scroll_controller.dart'; import 'scroll_controller.dart';
import 'scroll_notification.dart';
import 'scroll_physics.dart'; import 'scroll_physics.dart';
import 'scrollable.dart'; import 'scrollable.dart';
import 'sliver.dart'; import 'sliver.dart';
import 'viewport.dart'; import 'viewport.dart';
/// A representation of how a [ScrollView] should dismiss the on-screen
/// keyboard.
enum ScrollViewKeyboardDismissBehavior {
/// `manual` means there is no automatic dimissal of the on-screen keyboard.
/// It is up to the client to dismiss the keyboard.
manual,
/// `onDrag` means that the [ScrollView] will dismiss an on-screen keyboard
/// when a drag begins.
onDrag,
}
/// A widget that scrolls. /// A widget that scrolls.
/// ///
/// Scrollable widgets consist of three pieces: /// Scrollable widgets consist of three pieces:
...@@ -70,6 +85,7 @@ abstract class ScrollView extends StatelessWidget { ...@@ -70,6 +85,7 @@ abstract class ScrollView extends StatelessWidget {
this.cacheExtent, this.cacheExtent,
this.semanticChildCount, this.semanticChildCount,
this.dragStartBehavior = DragStartBehavior.start, this.dragStartBehavior = DragStartBehavior.start,
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
}) : assert(scrollDirection != null), }) : assert(scrollDirection != null),
assert(reverse != null), assert(reverse != null),
assert(shrinkWrap != null), assert(shrinkWrap != null),
...@@ -232,6 +248,10 @@ abstract class ScrollView extends StatelessWidget { ...@@ -232,6 +248,10 @@ abstract class ScrollView extends StatelessWidget {
/// {@macro flutter.widgets.scrollable.dragStartBehavior} /// {@macro flutter.widgets.scrollable.dragStartBehavior}
final DragStartBehavior dragStartBehavior; final DragStartBehavior dragStartBehavior;
/// [ScrollViewKeyboardDismissBehavior] the defines how this [ScrollView] will
/// dismiss the keyboard automatically.
final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
/// Returns the [AxisDirection] in which the scroll view scrolls. /// Returns the [AxisDirection] in which the scroll view scrolls.
/// ///
/// Combines the [scrollDirection] with the [reverse] boolean to obtain the /// Combines the [scrollDirection] with the [reverse] boolean to obtain the
...@@ -297,9 +317,8 @@ abstract class ScrollView extends StatelessWidget { ...@@ -297,9 +317,8 @@ abstract class ScrollView extends StatelessWidget {
final List<Widget> slivers = buildSlivers(context); final List<Widget> slivers = buildSlivers(context);
final AxisDirection axisDirection = getDirection(context); final AxisDirection axisDirection = getDirection(context);
final ScrollController scrollController = primary final ScrollController scrollController =
? PrimaryScrollController.of(context) primary ? PrimaryScrollController.of(context) : controller;
: controller;
final Scrollable scrollable = Scrollable( final Scrollable scrollable = Scrollable(
dragStartBehavior: dragStartBehavior, dragStartBehavior: dragStartBehavior,
axisDirection: axisDirection, axisDirection: axisDirection,
...@@ -310,9 +329,24 @@ abstract class ScrollView extends StatelessWidget { ...@@ -310,9 +329,24 @@ abstract class ScrollView extends StatelessWidget {
return buildViewport(context, offset, axisDirection, slivers); return buildViewport(context, offset, axisDirection, slivers);
}, },
); );
return primary && scrollController != null final Widget scrollableResult = primary && scrollController != null
? PrimaryScrollController.none(child: scrollable) ? PrimaryScrollController.none(child: scrollable)
: scrollable; : scrollable;
if (keyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag) {
return NotificationListener<ScrollUpdateNotification>(
child: scrollableResult,
onNotification: (ScrollUpdateNotification notification) {
final FocusScopeNode focusScope = FocusScope.of(context);
if (notification.dragDetails != null && focusScope.hasFocus) {
focusScope.unfocus();
}
return false;
},
);
} else {
return scrollableResult;
}
} }
@override @override
...@@ -504,6 +538,7 @@ abstract class BoxScrollView extends ScrollView { ...@@ -504,6 +538,7 @@ abstract class BoxScrollView extends ScrollView {
double cacheExtent, double cacheExtent,
int semanticChildCount, int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start, DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
}) : super( }) : super(
key: key, key: key,
scrollDirection: scrollDirection, scrollDirection: scrollDirection,
...@@ -515,6 +550,7 @@ abstract class BoxScrollView extends ScrollView { ...@@ -515,6 +550,7 @@ abstract class BoxScrollView extends ScrollView {
cacheExtent: cacheExtent, cacheExtent: cacheExtent,
semanticChildCount: semanticChildCount, semanticChildCount: semanticChildCount,
dragStartBehavior: dragStartBehavior, dragStartBehavior: dragStartBehavior,
keyboardDismissBehavior: keyboardDismissBehavior,
); );
/// The amount of space by which to inset the children. /// The amount of space by which to inset the children.
...@@ -877,6 +913,7 @@ class ListView extends BoxScrollView { ...@@ -877,6 +913,7 @@ class ListView extends BoxScrollView {
List<Widget> children = const <Widget>[], List<Widget> children = const <Widget>[],
int semanticChildCount, int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start, DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
}) : childrenDelegate = SliverChildListDelegate( }) : childrenDelegate = SliverChildListDelegate(
children, children,
addAutomaticKeepAlives: addAutomaticKeepAlives, addAutomaticKeepAlives: addAutomaticKeepAlives,
...@@ -895,6 +932,7 @@ class ListView extends BoxScrollView { ...@@ -895,6 +932,7 @@ class ListView extends BoxScrollView {
cacheExtent: cacheExtent, cacheExtent: cacheExtent,
semanticChildCount: semanticChildCount ?? children.length, semanticChildCount: semanticChildCount ?? children.length,
dragStartBehavior: dragStartBehavior, dragStartBehavior: dragStartBehavior,
keyboardDismissBehavior: keyboardDismissBehavior,
); );
/// Creates a scrollable, linear array of widgets that are created on demand. /// Creates a scrollable, linear array of widgets that are created on demand.
...@@ -1031,6 +1069,7 @@ class ListView extends BoxScrollView { ...@@ -1031,6 +1069,7 @@ class ListView extends BoxScrollView {
bool addRepaintBoundaries = true, bool addRepaintBoundaries = true,
bool addSemanticIndexes = true, bool addSemanticIndexes = true,
double cacheExtent, double cacheExtent,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
}) : assert(itemBuilder != null), }) : assert(itemBuilder != null),
assert(separatorBuilder != null), assert(separatorBuilder != null),
assert(itemCount != null && itemCount >= 0), assert(itemCount != null && itemCount >= 0),
...@@ -1071,6 +1110,7 @@ class ListView extends BoxScrollView { ...@@ -1071,6 +1110,7 @@ class ListView extends BoxScrollView {
padding: padding, padding: padding,
cacheExtent: cacheExtent, cacheExtent: cacheExtent,
semanticChildCount: itemCount, semanticChildCount: itemCount,
keyboardDismissBehavior: keyboardDismissBehavior,
); );
/// Creates a scrollable, linear array of widgets with a custom child model. /// Creates a scrollable, linear array of widgets with a custom child model.
......
...@@ -9,6 +9,51 @@ import 'package:flutter/material.dart'; ...@@ -9,6 +9,51 @@ import 'package:flutter/material.dart';
import 'states.dart'; import 'states.dart';
class MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
@override
bool isSupported(Locale locale) => true;
@override
Future<MaterialLocalizations> load(Locale locale) => DefaultMaterialLocalizations.load(locale);
@override
bool shouldReload(MaterialLocalizationsDelegate old) => false;
}
class WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
@override
bool isSupported(Locale locale) => true;
@override
Future<WidgetsLocalizations> load(Locale locale) => DefaultWidgetsLocalizations.load(locale);
@override
bool shouldReload(WidgetsLocalizationsDelegate old) => false;
}
Widget textFieldBoilerplate({ Widget child }) {
return MaterialApp(
home: Localizations(
locale: const Locale('en', 'US'),
delegates: <LocalizationsDelegate<dynamic>>[
WidgetsLocalizationsDelegate(),
MaterialLocalizationsDelegate(),
],
child: Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(size: Size(800.0, 600.0)),
child: Center(
child: Material(
child: child,
),
),
),
),
),
);
}
void main() { void main() {
testWidgets('ListView control test', (WidgetTester tester) async { testWidgets('ListView control test', (WidgetTester tester) async {
final List<String> log = <String>[]; final List<String> log = <String>[];
...@@ -52,6 +97,68 @@ void main() { ...@@ -52,6 +97,68 @@ void main() {
log.clear(); log.clear();
}); });
testWidgets('ListView dismiss keyboard onDrag test', (WidgetTester tester) async {
final List<FocusNode> focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
await tester.pumpWidget(textFieldBoilerplate(
child: ListView(
padding: const EdgeInsets.all(0),
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
children: focusNodes.map((FocusNode focusNode) {
return Container(
height: 50,
color: Colors.green,
child: TextField(
focusNode: focusNode,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
)),
);
}).toList(),
)));
final Finder finder = find.byType(TextField).first;
final TextField textField = tester.widget(finder);
await tester.showKeyboard(finder);
expect(textField.focusNode.hasFocus, isTrue);
await tester.drag(finder, const Offset(0.0, -40.0));
await tester.pumpAndSettle();
expect(textField.focusNode.hasFocus, isFalse);
});
testWidgets('ListView dismiss keyboard manual test', (WidgetTester tester) async {
final List<FocusNode> focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
await tester.pumpWidget(textFieldBoilerplate(
child: ListView(
padding: const EdgeInsets.all(0),
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.manual,
children: focusNodes.map((FocusNode focusNode) {
return Container(
height: 50,
color: Colors.green,
child: TextField(
focusNode: focusNode,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
)),
);
}).toList(),
)));
final Finder finder = find.byType(TextField).first;
final TextField textField = tester.widget(finder);
await tester.showKeyboard(finder);
expect(textField.focusNode.hasFocus, isTrue);
await tester.drag(finder, const Offset(0.0, -40.0));
await tester.pumpAndSettle();
expect(textField.focusNode.hasFocus, isTrue);
});
testWidgets('ListView restart ballistic activity out of range', (WidgetTester tester) async { testWidgets('ListView restart ballistic activity out of range', (WidgetTester tester) async {
Widget buildListView(int n) { Widget buildListView(int n) {
return Directionality( return Directionality(
......
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