Plumbs scrollBehavior into SelectableText so that the scrollbar may be hidden (#158887)

## Description

This adds a `scrollBehavior` attribute to `SelectableText` so that the
scrolling can be controlled more directly.

I added this specifically because it's not possible to turn off the
scrollbar on a selectable text, even if you set the scroll physics to be
`NeverScrollableScrollPhysics`. We had a UI where we needed to have a
clipped, multi-line selectable text field, but have it not be scrollable
unless it was expanded in size, and it wasn't possible to hide the
scrollbar, but still wanted it to be selectable.

## Tests
- Added a test that makes sure that the scroll behavior makes it down to
the `Scrollable` in the `EditableText`.
This commit is contained in:
Greg Spencer 2024-11-18 15:59:28 -08:00 committed by GitHub
parent 3a17b67353
commit e68ed3d1f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 51 additions and 27 deletions

View File

@ -190,6 +190,7 @@ class SelectableText extends StatefulWidget {
this.selectionControls, this.selectionControls,
this.onTap, this.onTap,
this.scrollPhysics, this.scrollPhysics,
this.scrollBehavior,
this.semanticsLabel, this.semanticsLabel,
this.textHeightBehavior, this.textHeightBehavior,
this.textWidthBasis, this.textWidthBasis,
@ -247,6 +248,7 @@ class SelectableText extends StatefulWidget {
this.selectionControls, this.selectionControls,
this.onTap, this.onTap,
this.scrollPhysics, this.scrollPhysics,
this.scrollBehavior,
this.semanticsLabel, this.semanticsLabel,
this.textHeightBehavior, this.textHeightBehavior,
this.textWidthBasis, this.textWidthBasis,
@ -412,6 +414,9 @@ class SelectableText extends StatefulWidget {
/// {@macro flutter.widgets.editableText.scrollPhysics} /// {@macro flutter.widgets.editableText.scrollPhysics}
final ScrollPhysics? scrollPhysics; final ScrollPhysics? scrollPhysics;
/// {@macro flutter.widgets.editableText.scrollBehavior}
final ScrollBehavior? scrollBehavior;
/// {@macro flutter.widgets.Text.semanticsLabel} /// {@macro flutter.widgets.Text.semanticsLabel}
final String? semanticsLabel; final String? semanticsLabel;
@ -467,6 +472,7 @@ class SelectableText extends StatefulWidget {
properties.add(FlagProperty('selectionEnabled', value: selectionEnabled, defaultValue: true, ifFalse: 'selection disabled')); properties.add(FlagProperty('selectionEnabled', value: selectionEnabled, defaultValue: true, ifFalse: 'selection disabled'));
properties.add(DiagnosticsProperty<TextSelectionControls>('selectionControls', selectionControls, defaultValue: null)); properties.add(DiagnosticsProperty<TextSelectionControls>('selectionControls', selectionControls, defaultValue: null));
properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null)); properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null));
properties.add(DiagnosticsProperty<ScrollBehavior>('scrollBehavior', scrollBehavior, defaultValue: null));
properties.add(DiagnosticsProperty<TextHeightBehavior>('textHeightBehavior', textHeightBehavior, defaultValue: null)); properties.add(DiagnosticsProperty<TextHeightBehavior>('textHeightBehavior', textHeightBehavior, defaultValue: null));
} }
} }
@ -738,6 +744,7 @@ class _SelectableTextState extends State<SelectableText> implements TextSelectio
magnifierConfiguration: widget.magnifierConfiguration ?? TextMagnifier.adaptiveMagnifierConfiguration, magnifierConfiguration: widget.magnifierConfiguration ?? TextMagnifier.adaptiveMagnifierConfiguration,
dragStartBehavior: widget.dragStartBehavior, dragStartBehavior: widget.dragStartBehavior,
scrollPhysics: widget.scrollPhysics, scrollPhysics: widget.scrollPhysics,
scrollBehavior: widget.scrollBehavior,
autofillHints: null, autofillHints: null,
contextMenuBuilder: widget.contextMenuBuilder, contextMenuBuilder: widget.contextMenuBuilder,
), ),

View File

@ -1847,17 +1847,17 @@ class EditableText extends StatefulWidget {
/// Flutter. /// Flutter.
final String? restorationId; final String? restorationId;
/// {@template flutter.widgets.shadow.scrollBehavior} /// {@template flutter.widgets.editableText.scrollBehavior}
/// A [ScrollBehavior] that will be applied to this widget individually. /// A [ScrollBehavior] that will be applied to this widget individually.
/// ///
/// Defaults to null, wherein the inherited [ScrollBehavior] is copied and /// Defaults to null, wherein the inherited [ScrollBehavior] is copied and
/// modified to alter the viewport decoration, like [Scrollbar]s. /// modified to alter the viewport decoration, like [Scrollbar]s.
/// {@endtemplate}
/// ///
/// [ScrollBehavior]s also provide [ScrollPhysics]. If an explicit /// [ScrollBehavior]s also provide [ScrollPhysics]. If an explicit
/// [ScrollPhysics] is provided in [scrollPhysics], it will take precedence, /// [ScrollPhysics] is provided in [scrollPhysics], it will take precedence,
/// followed by [scrollBehavior], and then the inherited ancestor /// followed by [scrollBehavior], and then the inherited ancestor
/// [ScrollBehavior]. /// [ScrollBehavior].
/// {@endtemplate}
/// ///
/// The [ScrollBehavior] of the inherited [ScrollConfiguration] will be /// The [ScrollBehavior] of the inherited [ScrollConfiguration] will be
/// modified by default to only apply a [Scrollbar] if [maxLines] is greater /// modified by default to only apply a [Scrollbar] if [maxLines] is greater

View File

@ -710,12 +710,7 @@ class ListWheelScrollView extends StatefulWidget {
/// {@macro flutter.widgets.scrollable.restorationId} /// {@macro flutter.widgets.scrollable.restorationId}
final String? restorationId; final String? restorationId;
/// {@macro flutter.widgets.shadow.scrollBehavior} /// {@macro flutter.widgets.scrollable.scrollBehavior}
///
/// [ScrollBehavior]s also provide [ScrollPhysics]. If an explicit
/// [ScrollPhysics] is provided in [physics], it will take precedence,
/// followed by [scrollBehavior], and then the inherited ancestor
/// [ScrollBehavior].
/// ///
/// The [ScrollBehavior] of the inherited [ScrollConfiguration] will be /// The [ScrollBehavior] of the inherited [ScrollConfiguration] will be
/// modified by default to not apply a [Scrollbar]. /// modified by default to not apply a [Scrollbar].

View File

@ -313,12 +313,7 @@ class NestedScrollView extends StatefulWidget {
/// {@macro flutter.widgets.scrollable.restorationId} /// {@macro flutter.widgets.scrollable.restorationId}
final String? restorationId; final String? restorationId;
/// {@macro flutter.widgets.shadow.scrollBehavior} /// {@macro flutter.widgets.scrollable.scrollBehavior}
///
/// [ScrollBehavior]s also provide [ScrollPhysics]. If an explicit
/// [ScrollPhysics] is provided in [physics], it will take precedence,
/// followed by [scrollBehavior], and then the inherited ancestor
/// [ScrollBehavior].
/// ///
/// The [ScrollBehavior] of the inherited [ScrollConfiguration] will be /// The [ScrollBehavior] of the inherited [ScrollConfiguration] will be
/// modified by default to not apply a [Scrollbar]. This is because the /// modified by default to not apply a [Scrollbar]. This is because the

View File

@ -839,12 +839,7 @@ class PageView extends StatefulWidget {
/// Defaults to [HitTestBehavior.opaque]. /// Defaults to [HitTestBehavior.opaque].
final HitTestBehavior hitTestBehavior; final HitTestBehavior hitTestBehavior;
/// {@macro flutter.widgets.shadow.scrollBehavior} /// {@macro flutter.widgets.scrollable.scrollBehavior}
///
/// [ScrollBehavior]s also provide [ScrollPhysics]. If an explicit
/// [ScrollPhysics] is provided in [physics], it will take precedence,
/// followed by [scrollBehavior], and then the inherited ancestor
/// [ScrollBehavior].
/// ///
/// The [ScrollBehavior] of the inherited [ScrollConfiguration] will be /// The [ScrollBehavior] of the inherited [ScrollConfiguration] will be
/// modified by default to not apply a [Scrollbar]. /// modified by default to not apply a [Scrollbar].

View File

@ -256,12 +256,7 @@ abstract class ScrollView extends StatelessWidget {
/// [physics]. /// [physics].
final ScrollPhysics? physics; final ScrollPhysics? physics;
/// {@macro flutter.widgets.shadow.scrollBehavior} /// {@macro flutter.widgets.scrollable.scrollBehavior}
///
/// [ScrollBehavior]s also provide [ScrollPhysics]. If an explicit
/// [ScrollPhysics] is provided in [physics], it will take precedence,
/// followed by [scrollBehavior], and then the inherited ancestor
/// [ScrollBehavior].
final ScrollBehavior? scrollBehavior; final ScrollBehavior? scrollBehavior;
/// {@template flutter.widgets.scroll_view.shrinkWrap} /// {@template flutter.widgets.scroll_view.shrinkWrap}

View File

@ -304,12 +304,17 @@ class Scrollable extends StatefulWidget {
/// {@endtemplate} /// {@endtemplate}
final String? restorationId; final String? restorationId;
/// {@macro flutter.widgets.shadow.scrollBehavior} /// {@template flutter.widgets.scrollable.scrollBehavior}
/// A [ScrollBehavior] that will be applied to this widget individually.
///
/// Defaults to null, wherein the inherited [ScrollBehavior] is copied and
/// modified to alter the viewport decoration, like [Scrollbar]s.
/// ///
/// [ScrollBehavior]s also provide [ScrollPhysics]. If an explicit /// [ScrollBehavior]s also provide [ScrollPhysics]. If an explicit
/// [ScrollPhysics] is provided in [physics], it will take precedence, /// [ScrollPhysics] is provided in [physics], it will take precedence,
/// followed by [scrollBehavior], and then the inherited ancestor /// followed by [scrollBehavior], and then the inherited ancestor
/// [ScrollBehavior]. /// [ScrollBehavior].
/// {@endtemplate}
final ScrollBehavior? scrollBehavior; final ScrollBehavior? scrollBehavior;
/// {@macro flutter.material.Material.clipBehavior} /// {@macro flutter.material.Material.clipBehavior}

View File

@ -1391,6 +1391,36 @@ void main() {
expect(inputBox.hitTest(BoxHitTestResult(), position: inputBox.globalToLocal(newFourthPos)), isFalse); expect(inputBox.hitTest(BoxHitTestResult(), position: inputBox.globalToLocal(newFourthPos)), isFalse);
}); });
testWidgets('ScrollBehavior can be overridden', (WidgetTester tester) async {
await tester.pumpWidget(
boilerplate(
child: const SelectableText(
kMoreThanFourLines,
dragStartBehavior: DragStartBehavior.down,
style: TextStyle(color: Colors.black, fontSize: 34.0),
maxLines: 2,
),
),
);
expect(tester.widget<EditableText>(find.byType(EditableText)).scrollBehavior, isNull);
expect(tester.widget<Scrollable>(find.byType(Scrollable)).scrollBehavior, isNotNull);
final ScrollBehavior behavior = const ScrollBehavior()..copyWith(scrollbars: false);
await tester.pumpWidget(
boilerplate(
child: SelectableText(
kMoreThanFourLines,
dragStartBehavior: DragStartBehavior.down,
style: const TextStyle(color: Colors.black, fontSize: 34.0),
maxLines: 2,
scrollBehavior: behavior,
),
),
);
expect(tester.widget<EditableText>(find.byType(EditableText)).scrollBehavior, equals(behavior));
expect(tester.widget<Scrollable>(find.byType(Scrollable)).scrollBehavior, equals(behavior));
});
testWidgets('minLines cannot be greater than maxLines', (WidgetTester tester) async { testWidgets('minLines cannot be greater than maxLines', (WidgetTester tester) async {
expect( expect(
() async { () async {
@ -4578,6 +4608,7 @@ void main() {
cursorRadius: Radius.zero, cursorRadius: Radius.zero,
cursorColor: Color(0xff00ff00), cursorColor: Color(0xff00ff00),
scrollPhysics: ClampingScrollPhysics(), scrollPhysics: ClampingScrollPhysics(),
scrollBehavior: ScrollBehavior(),
semanticsLabel: 'something else', semanticsLabel: 'something else',
enableInteractiveSelection: false, enableInteractiveSelection: false,
).debugFillProperties(builder); ).debugFillProperties(builder);
@ -4603,6 +4634,7 @@ void main() {
'cursorColor: ${const Color(0xff00ff00)}', 'cursorColor: ${const Color(0xff00ff00)}',
'selection disabled', 'selection disabled',
'scrollPhysics: ClampingScrollPhysics', 'scrollPhysics: ClampingScrollPhysics',
'scrollBehavior: ScrollBehavior',
]); ]);
}); });