Unverified Commit 0d27fdce authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Scroll scrollable to keep focused control visible. (#44965)

Before this change, it was possible to move the focus onto a control that was no longer in the view using focus traversal. This changes that, so that when a control is focused, it makes sure that if it is the child of a scrollable, that the scrollable attempts to keep it in view. If it is already in view, then nothing scrolls.

When asked to move in a direction, the focus traversal code tries to find a control to move to inside the scrollable first, and then looks for things outside of the scrollable only once the scrollable has reached its limit in that direction.
parent 7a0911b4
...@@ -97,7 +97,6 @@ abstract class Action extends Diagnosticable { ...@@ -97,7 +97,6 @@ abstract class Action extends Diagnosticable {
/// null `node`. If the information available from a focus node is /// null `node`. If the information available from a focus node is
/// needed in the action, use [ActionDispatcher.invokeFocusedAction] instead. /// needed in the action, use [ActionDispatcher.invokeFocusedAction] instead.
@protected @protected
@mustCallSuper
void invoke(FocusNode node, covariant Intent intent); void invoke(FocusNode node, covariant Intent intent);
@override @override
......
...@@ -22,6 +22,32 @@ import 'scroll_physics.dart'; ...@@ -22,6 +22,32 @@ import 'scroll_physics.dart';
export 'scroll_activity.dart' show ScrollHoldController; export 'scroll_activity.dart' show ScrollHoldController;
/// The policy to use when applying the `alignment` parameter of
/// [ScrollPosition.ensureVisible].
enum ScrollPositionAlignmentPolicy {
/// Use the `alignment` property of [ScrollPosition.ensureVisible] to decide
/// where to align the visible object.
explicit,
/// Find the bottom edge of the scroll container, and scroll the container, if
/// necessary, to show the bottom of the object.
///
/// For example, find the bottom edge of the scroll container. If the bottom
/// edge of the item is below the bottom edge of the scroll container, scroll
/// the item so that the bottom of the item is just visible. If the entire
/// item is already visible, then do nothing.
keepVisibleAtEnd,
/// Find the top edge of the scroll container, and scroll the container if
/// necessary to show the top of the object.
///
/// For example, find the top edge of the scroll container. If the top edge of
/// the item is above the top edge of the scroll container, scroll the item so
/// that the top of the item is just visible. If the entire item is already
/// visible, then do nothing.
keepVisibleAtStart,
}
/// Determines which portion of the content is visible in a scroll view. /// Determines which portion of the content is visible in a scroll view.
/// ///
/// The [pixels] value determines the scroll offset that the scroll view uses to /// The [pixels] value determines the scroll offset that the scroll view uses to
...@@ -497,17 +523,41 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { ...@@ -497,17 +523,41 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
/// Animates the position such that the given object is as visible as possible /// Animates the position such that the given object is as visible as possible
/// by just scrolling this position. /// by just scrolling this position.
///
/// See also:
///
/// * [ScrollPositionAlignmentPolicy] for the way in which `alignment` is
/// applied, and the way the given `object` is aligned.
Future<void> ensureVisible( Future<void> ensureVisible(
RenderObject object, { RenderObject object, {
double alignment = 0.0, double alignment = 0.0,
Duration duration = Duration.zero, Duration duration = Duration.zero,
Curve curve = Curves.ease, Curve curve = Curves.ease,
ScrollPositionAlignmentPolicy alignmentPolicy = ScrollPositionAlignmentPolicy.explicit,
}) { }) {
assert(alignmentPolicy != null);
assert(object.attached); assert(object.attached);
final RenderAbstractViewport viewport = RenderAbstractViewport.of(object); final RenderAbstractViewport viewport = RenderAbstractViewport.of(object);
assert(viewport != null); assert(viewport != null);
final double target = viewport.getOffsetToReveal(object, alignment).offset.clamp(minScrollExtent, maxScrollExtent); double target;
switch (alignmentPolicy) {
case ScrollPositionAlignmentPolicy.explicit:
target = viewport.getOffsetToReveal(object, alignment).offset.clamp(minScrollExtent, maxScrollExtent);
break;
case ScrollPositionAlignmentPolicy.keepVisibleAtEnd:
target = viewport.getOffsetToReveal(object, 1.0).offset.clamp(minScrollExtent, maxScrollExtent);
if (target < pixels) {
target = pixels;
}
break;
case ScrollPositionAlignmentPolicy.keepVisibleAtStart:
target = viewport.getOffsetToReveal(object, 0.0).offset.clamp(minScrollExtent, maxScrollExtent);
if (target > pixels) {
target = pixels;
}
break;
}
if (target == pixels) if (target == pixels)
return Future<void>.value(); return Future<void>.value();
......
...@@ -240,6 +240,7 @@ class Scrollable extends StatefulWidget { ...@@ -240,6 +240,7 @@ class Scrollable extends StatefulWidget {
double alignment = 0.0, double alignment = 0.0,
Duration duration = Duration.zero, Duration duration = Duration.zero,
Curve curve = Curves.ease, Curve curve = Curves.ease,
ScrollPositionAlignmentPolicy alignmentPolicy = ScrollPositionAlignmentPolicy.explicit,
}) { }) {
final List<Future<void>> futures = <Future<void>>[]; final List<Future<void>> futures = <Future<void>>[];
...@@ -250,6 +251,7 @@ class Scrollable extends StatefulWidget { ...@@ -250,6 +251,7 @@ class Scrollable extends StatefulWidget {
alignment: alignment, alignment: alignment,
duration: duration, duration: duration,
curve: curve, curve: curve,
alignmentPolicy: alignmentPolicy,
)); ));
context = scrollable.context; context = scrollable.context;
scrollable = Scrollable.of(context); scrollable = Scrollable.of(context);
......
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