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';
import 'package:flutter/gestures.dart';
import 'basic.dart';
import 'focus_manager.dart';
import 'focus_scope.dart';
import 'framework.dart';
import 'media_query.dart';
import 'notification_listener.dart';
import 'primary_scroll_controller.dart';
import 'scroll_controller.dart';
import 'scroll_notification.dart';
import 'scroll_physics.dart';
import 'scrollable.dart';
import 'sliver.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.
///
/// Scrollable widgets consist of three pieces:
......@@ -70,6 +85,7 @@ abstract class ScrollView extends StatelessWidget {
this.cacheExtent,
this.semanticChildCount,
this.dragStartBehavior = DragStartBehavior.start,
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
}) : assert(scrollDirection != null),
assert(reverse != null),
assert(shrinkWrap != null),
......@@ -232,6 +248,10 @@ abstract class ScrollView extends StatelessWidget {
/// {@macro flutter.widgets.scrollable.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.
///
/// Combines the [scrollDirection] with the [reverse] boolean to obtain the
......@@ -297,9 +317,8 @@ abstract class ScrollView extends StatelessWidget {
final List<Widget> slivers = buildSlivers(context);
final AxisDirection axisDirection = getDirection(context);
final ScrollController scrollController = primary
? PrimaryScrollController.of(context)
: controller;
final ScrollController scrollController =
primary ? PrimaryScrollController.of(context) : controller;
final Scrollable scrollable = Scrollable(
dragStartBehavior: dragStartBehavior,
axisDirection: axisDirection,
......@@ -310,9 +329,24 @@ abstract class ScrollView extends StatelessWidget {
return buildViewport(context, offset, axisDirection, slivers);
},
);
return primary && scrollController != null
? PrimaryScrollController.none(child: scrollable)
: scrollable;
final Widget scrollableResult = primary && scrollController != null
? PrimaryScrollController.none(child: 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
......@@ -504,6 +538,7 @@ abstract class BoxScrollView extends ScrollView {
double cacheExtent,
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
}) : super(
key: key,
scrollDirection: scrollDirection,
......@@ -515,6 +550,7 @@ abstract class BoxScrollView extends ScrollView {
cacheExtent: cacheExtent,
semanticChildCount: semanticChildCount,
dragStartBehavior: dragStartBehavior,
keyboardDismissBehavior: keyboardDismissBehavior,
);
/// The amount of space by which to inset the children.
......@@ -877,6 +913,7 @@ class ListView extends BoxScrollView {
List<Widget> children = const <Widget>[],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.start,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
}) : childrenDelegate = SliverChildListDelegate(
children,
addAutomaticKeepAlives: addAutomaticKeepAlives,
......@@ -895,6 +932,7 @@ class ListView extends BoxScrollView {
cacheExtent: cacheExtent,
semanticChildCount: semanticChildCount ?? children.length,
dragStartBehavior: dragStartBehavior,
keyboardDismissBehavior: keyboardDismissBehavior,
);
/// Creates a scrollable, linear array of widgets that are created on demand.
......@@ -1031,6 +1069,7 @@ class ListView extends BoxScrollView {
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
}) : assert(itemBuilder != null),
assert(separatorBuilder != null),
assert(itemCount != null && itemCount >= 0),
......@@ -1071,6 +1110,7 @@ class ListView extends BoxScrollView {
padding: padding,
cacheExtent: cacheExtent,
semanticChildCount: itemCount,
keyboardDismissBehavior: keyboardDismissBehavior,
);
/// Creates a scrollable, linear array of widgets with a custom child model.
......
......@@ -9,6 +9,51 @@ import 'package:flutter/material.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() {
testWidgets('ListView control test', (WidgetTester tester) async {
final List<String> log = <String>[];
......@@ -52,6 +97,68 @@ void main() {
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 {
Widget buildListView(int n) {
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