feat: Change default value of keyboardDismissBehavior (#158580)

This PR aim to let developer choose the default
ScrollViewKeyboardDismissBehavior value.

I removed all default values of keyboardDismissBehavior and instead use
the one from `ScrollConfiguration`, which use as default value
`ScrollViewKeyboardDismissBehavior.manual`.

This PR try to fix : https://github.com/flutter/flutter/issues/158566

<details/> 

<summary> Code Example </summary>

```dart
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      scrollBehavior: const MaterialScrollBehavior().copyWith(
        keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
      ),
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: TextField(),
      ),
      body: ListView.builder(
        itemCount: 100,
        itemBuilder: (context, index) => ListTile(title: Text('Item $index')),
      ),
    );
  }
}
```

</details>


https://github.com/user-attachments/assets/8341c3da-2685-4f55-b8e9-11d2aae907db



## Pre-launch Checklist

- [X] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [X] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [X] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [X] I signed the [CLA].
- [X] I listed at least one issue that this PR fixes in the description
above.
- [X] I updated/added relevant documentation (doc comments with `///`).
- [X] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [X] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [X] All existing and new tests are passing.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

---------

Co-authored-by: Kate Lovett <katelovett@google.com>
This commit is contained in:
Enguerrand ARMINJON
2025-01-15 22:17:29 +01:00
committed by GitHub
parent 36eb78b1b4
commit 5517cc9b3b
8 changed files with 152 additions and 17 deletions

View File

@@ -98,7 +98,7 @@ class ReorderableListView extends StatefulWidget {
this.anchor = 0.0,
this.cacheExtent,
this.dragStartBehavior = DragStartBehavior.start,
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
this.keyboardDismissBehavior,
this.restorationId,
this.clipBehavior = Clip.hardEdge,
this.autoScrollerVelocityScalar,
@@ -169,7 +169,7 @@ class ReorderableListView extends StatefulWidget {
this.anchor = 0.0,
this.cacheExtent,
this.dragStartBehavior = DragStartBehavior.start,
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
this.keyboardDismissBehavior,
this.restorationId,
this.clipBehavior = Clip.hardEdge,
this.autoScrollerVelocityScalar,
@@ -271,8 +271,9 @@ class ReorderableListView extends StatefulWidget {
/// {@macro flutter.widgets.scroll_view.keyboardDismissBehavior}
///
/// The default is [ScrollViewKeyboardDismissBehavior.manual]
final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
/// If [keyboardDismissBehavior] is null then it will fallback to the inherited
/// [ScrollBehavior.getKeyboardDismissBehavior].
final ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior;
/// {@macro flutter.widgets.scrollable.restorationId}
final String? restorationId;

View File

@@ -168,7 +168,7 @@ class ReorderableList extends StatefulWidget {
this.anchor = 0.0,
this.cacheExtent,
this.dragStartBehavior = DragStartBehavior.start,
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
this.keyboardDismissBehavior,
this.restorationId,
this.clipBehavior = Clip.hardEdge,
this.autoScrollerVelocityScalar,
@@ -280,8 +280,9 @@ class ReorderableList extends StatefulWidget {
/// {@macro flutter.widgets.scroll_view.keyboardDismissBehavior}
///
/// The default is [ScrollViewKeyboardDismissBehavior.manual]
final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
/// If [keyboardDismissBehavior] is null then it will fallback to the inherited
/// [ScrollBehavior.getKeyboardDismissBehavior].
final ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior;
/// {@macro flutter.widgets.scrollable.restorationId}
final String? restorationId;

View File

@@ -18,6 +18,7 @@ import 'package:flutter/services.dart' show LogicalKeyboardKey;
import 'framework.dart';
import 'overscroll_indicator.dart';
import 'scroll_physics.dart';
import 'scroll_view.dart';
import 'scrollable.dart';
import 'scrollable_helpers.dart';
import 'scrollbar.dart';
@@ -89,6 +90,7 @@ class ScrollBehavior {
Set<LogicalKeyboardKey>? pointerAxisModifiers,
ScrollPhysics? physics,
TargetPlatform? platform,
ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior,
}) {
return _WrappedScrollBehavior(
delegate: this,
@@ -99,6 +101,7 @@ class ScrollBehavior {
pointerAxisModifiers: pointerAxisModifiers,
physics: physics,
platform: platform,
keyboardDismissBehavior: keyboardDismissBehavior,
);
}
@@ -264,6 +267,12 @@ class ScrollBehavior {
/// method returns false, the rebuilds might be optimized away.
bool shouldNotify(covariant ScrollBehavior oldDelegate) => false;
/// The default keyboard dismissal behavior for [ScrollView] widgets.
///
/// Defaults to [ScrollViewKeyboardDismissBehavior.manual].
ScrollViewKeyboardDismissBehavior getKeyboardDismissBehavior(BuildContext context) =>
ScrollViewKeyboardDismissBehavior.manual;
@override
String toString() => objectRuntimeType(this, 'ScrollBehavior');
}
@@ -278,6 +287,7 @@ class _WrappedScrollBehavior implements ScrollBehavior {
Set<LogicalKeyboardKey>? pointerAxisModifiers,
this.physics,
this.platform,
this.keyboardDismissBehavior,
}) : _dragDevices = dragDevices,
_pointerAxisModifiers = pointerAxisModifiers;
@@ -286,6 +296,7 @@ class _WrappedScrollBehavior implements ScrollBehavior {
final bool overscroll;
final ScrollPhysics? physics;
final TargetPlatform? platform;
final ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior;
final Set<PointerDeviceKind>? _dragDevices;
final MultitouchDragStrategy? multitouchDragStrategy;
final Set<LogicalKeyboardKey>? _pointerAxisModifiers;
@@ -327,6 +338,7 @@ class _WrappedScrollBehavior implements ScrollBehavior {
Set<LogicalKeyboardKey>? pointerAxisModifiers,
ScrollPhysics? physics,
TargetPlatform? platform,
ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior,
}) {
return delegate.copyWith(
scrollbars: scrollbars ?? this.scrollbars,
@@ -336,6 +348,7 @@ class _WrappedScrollBehavior implements ScrollBehavior {
pointerAxisModifiers: pointerAxisModifiers ?? this.pointerAxisModifiers,
physics: physics ?? this.physics,
platform: platform ?? this.platform,
keyboardDismissBehavior: keyboardDismissBehavior ?? this.keyboardDismissBehavior,
);
}
@@ -349,6 +362,11 @@ class _WrappedScrollBehavior implements ScrollBehavior {
return physics ?? delegate.getScrollPhysics(context);
}
@override
ScrollViewKeyboardDismissBehavior getKeyboardDismissBehavior(BuildContext context) {
return keyboardDismissBehavior ?? delegate.getKeyboardDismissBehavior(context);
}
@override
bool shouldNotify(_WrappedScrollBehavior oldDelegate) {
return oldDelegate.delegate.runtimeType != delegate.runtimeType ||

View File

@@ -118,7 +118,7 @@ abstract class ScrollView extends StatelessWidget {
this.cacheExtent,
this.semanticChildCount,
this.dragStartBehavior = DragStartBehavior.start,
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
this.keyboardDismissBehavior,
this.restorationId,
this.clipBehavior = Clip.hardEdge,
this.hitTestBehavior = HitTestBehavior.opaque,
@@ -374,10 +374,14 @@ abstract class ScrollView extends StatelessWidget {
final DragStartBehavior dragStartBehavior;
/// {@template flutter.widgets.scroll_view.keyboardDismissBehavior}
/// [ScrollViewKeyboardDismissBehavior] the defines how this [ScrollView] will
/// The [ScrollViewKeyboardDismissBehavior] defines how this [ScrollView] will
/// dismiss the keyboard automatically.
/// {@endtemplate}
final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
///
/// If [keyboardDismissBehavior] is null then it will fallback to
/// [scrollBehavior]. If that is also null, the inherited
/// [ScrollBehavior.getKeyboardDismissBehavior] will be used.
final ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior;
/// {@macro flutter.widgets.scrollable.restorationId}
final String? restorationId;
@@ -505,7 +509,12 @@ abstract class ScrollView extends StatelessWidget {
? PrimaryScrollController.none(child: scrollable)
: scrollable;
if (keyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag) {
final ScrollViewKeyboardDismissBehavior effectiveKeyboardDismissBehavior =
keyboardDismissBehavior ??
scrollBehavior?.getKeyboardDismissBehavior(context) ??
ScrollConfiguration.of(context).getKeyboardDismissBehavior(context);
if (effectiveKeyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag) {
return NotificationListener<ScrollUpdateNotification>(
child: scrollableResult,
onNotification: (ScrollUpdateNotification notification) {

View File

@@ -18,6 +18,7 @@ import 'focus_scope.dart';
import 'framework.dart';
import 'notification_listener.dart';
import 'primary_scroll_controller.dart';
import 'scroll_configuration.dart';
import 'scroll_controller.dart';
import 'scroll_notification.dart';
import 'scroll_physics.dart';
@@ -158,7 +159,7 @@ class SingleChildScrollView extends StatelessWidget {
this.clipBehavior = Clip.hardEdge,
this.hitTestBehavior = HitTestBehavior.opaque,
this.restorationId,
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
this.keyboardDismissBehavior,
}) : assert(
!(controller != null && (primary ?? false)),
'Primary ScrollViews obtain their ScrollController via inheritance '
@@ -233,7 +234,10 @@ class SingleChildScrollView extends StatelessWidget {
final String? restorationId;
/// {@macro flutter.widgets.scroll_view.keyboardDismissBehavior}
final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
///
/// If [keyboardDismissBehavior] is null then it will fallback to the inherited
/// [ScrollBehavior.getKeyboardDismissBehavior].
final ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior;
AxisDirection _getDirection(BuildContext context) {
return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
@@ -271,7 +275,11 @@ class SingleChildScrollView extends StatelessWidget {
},
);
if (keyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag) {
final ScrollViewKeyboardDismissBehavior effectiveKeyboardDismissBehavior =
keyboardDismissBehavior ??
ScrollConfiguration.of(context).getKeyboardDismissBehavior(context);
if (effectiveKeyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag) {
scrollable = NotificationListener<ScrollUpdateNotification>(
child: scrollable,
onNotification: (ScrollUpdateNotification notification) {

View File

@@ -13,6 +13,7 @@ import 'focus_scope.dart';
import 'framework.dart';
import 'notification_listener.dart';
import 'primary_scroll_controller.dart';
import 'scroll_configuration.dart';
import 'scroll_controller.dart';
import 'scroll_delegate.dart';
import 'scroll_notification.dart';
@@ -63,7 +64,7 @@ abstract class TwoDimensionalScrollView extends StatelessWidget {
this.cacheExtent,
this.diagonalDragBehavior = DiagonalDragBehavior.none,
this.dragStartBehavior = DragStartBehavior.start,
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
this.keyboardDismissBehavior,
this.clipBehavior = Clip.hardEdge,
this.hitTestBehavior = HitTestBehavior.opaque,
});
@@ -109,7 +110,10 @@ abstract class TwoDimensionalScrollView extends StatelessWidget {
final DragStartBehavior dragStartBehavior;
/// {@macro flutter.widgets.scroll_view.keyboardDismissBehavior}
final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior;
///
/// If [keyboardDismissBehavior] is null then it will fallback to the inherited
/// [ScrollBehavior.getKeyboardDismissBehavior].
final ScrollViewKeyboardDismissBehavior? keyboardDismissBehavior;
/// {@macro flutter.widgets.scrollable.hitTestBehavior}
///
@@ -187,7 +191,11 @@ abstract class TwoDimensionalScrollView extends StatelessWidget {
? PrimaryScrollController.none(child: scrollable)
: scrollable;
if (keyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag) {
final ScrollViewKeyboardDismissBehavior effectiveKeyboardDismissBehavior =
keyboardDismissBehavior ??
ScrollConfiguration.of(context).getKeyboardDismissBehavior(context);
if (effectiveKeyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag) {
return NotificationListener<ScrollUpdateNotification>(
child: scrollableResult,
onNotification: (ScrollUpdateNotification notification) {

View File

@@ -392,6 +392,51 @@ void main() {
);
});
testWidgets('CupertinoApp has correct default KeyboardDismissBehavior', (
WidgetTester tester,
) async {
late BuildContext capturedContext;
await tester.pumpWidget(
CupertinoApp(
home: Builder(
builder: (BuildContext context) {
capturedContext = context;
return const Placeholder();
},
),
),
);
expect(
ScrollConfiguration.of(capturedContext).getKeyboardDismissBehavior(capturedContext),
ScrollViewKeyboardDismissBehavior.manual,
);
});
testWidgets('CupertinoApp can override default KeyboardDismissBehavior', (
WidgetTester tester,
) async {
late BuildContext capturedContext;
await tester.pumpWidget(
CupertinoApp(
scrollBehavior: const CupertinoScrollBehavior().copyWith(
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
),
home: Builder(
builder: (BuildContext context) {
capturedContext = context;
return const Placeholder();
},
),
),
);
expect(
ScrollConfiguration.of(capturedContext).getKeyboardDismissBehavior(capturedContext),
ScrollViewKeyboardDismissBehavior.onDrag,
);
});
testWidgets('A ScrollBehavior can be set for CupertinoApp', (WidgetTester tester) async {
late BuildContext capturedContext;
await tester.pumpWidget(

View File

@@ -1248,6 +1248,51 @@ void main() {
expect(ScrollConfiguration.of(capturedContext).runtimeType, MaterialScrollBehavior);
});
testWidgets('MaterialApp has correct default KeyboardDismissBehavior', (
WidgetTester tester,
) async {
late BuildContext capturedContext;
await tester.pumpWidget(
MaterialApp(
home: Builder(
builder: (BuildContext context) {
capturedContext = context;
return const Placeholder();
},
),
),
);
expect(
ScrollConfiguration.of(capturedContext).getKeyboardDismissBehavior(capturedContext),
ScrollViewKeyboardDismissBehavior.manual,
);
});
testWidgets('MaterialApp can override default KeyboardDismissBehavior', (
WidgetTester tester,
) async {
late BuildContext capturedContext;
await tester.pumpWidget(
MaterialApp(
scrollBehavior: const MaterialScrollBehavior().copyWith(
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
),
home: Builder(
builder: (BuildContext context) {
capturedContext = context;
return const Placeholder();
},
),
),
);
expect(
ScrollConfiguration.of(capturedContext).getKeyboardDismissBehavior(capturedContext),
ScrollViewKeyboardDismissBehavior.onDrag,
);
});
testWidgets('A ScrollBehavior can be set for MaterialApp', (WidgetTester tester) async {
late BuildContext capturedContext;
await tester.pumpWidget(