Commit 7c8e504e authored by Adam Barth's avatar Adam Barth

Merge pull request #1337 from abarth/scroll_focus_into_view

Scroll focused input widgets into view
parents 869d13c1 dd5df79e
......@@ -145,13 +145,22 @@ class AnimationController extends Animation<double>
}
Future animateTo(double target, { Duration duration, Curve curve: Curves.linear }) {
Duration remainingDuration = (duration ?? this.duration) * (target - _value).abs();
Duration simulationDuration = duration;
if (simulationDuration == null) {
double range = upperBound - lowerBound;
if (range.isFinite) {
double remainingFraction = (target - _value).abs() / range;
simulationDuration = this.duration * remainingFraction;
}
}
stop();
if (remainingDuration == Duration.ZERO)
if (simulationDuration == Duration.ZERO) {
assert(value == target);
return new Future.value();
assert(remainingDuration > Duration.ZERO);
}
assert(simulationDuration > Duration.ZERO);
assert(!isAnimating);
return _startSimulation(new _TweenSimulation(_value, target, remainingDuration, curve));
return _startSimulation(new _TweenSimulation(_value, target, simulationDuration, curve));
}
Future _startSimulation(Simulation simulation) {
......
......@@ -366,7 +366,7 @@ class ScaffoldState extends State<Scaffold> {
}
Widget build(BuildContext context) {
EdgeDims padding = MediaQuery.of(context).padding;
EdgeDims padding = MediaQuery.of(context)?.padding ?? EdgeDims.zero;
if (_snackBars.length > 0) {
ModalRoute route = ModalRoute.of(context);
......
......@@ -2,7 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'basic.dart';
import 'framework.dart';
import 'media_query.dart';
import 'scrollable.dart';
// _noFocusedScope is used by Focus to track the case where none of the Focus
// component's subscopes (e.g. dialogs) are focused. This is distinct from the
......@@ -130,10 +135,13 @@ class Focus extends StatefulComponent {
/// Don't call moveTo() from your build() functions, it's intended to be
/// called from event listeners, e.g. in response to a finger tap or tab key.
static void moveTo(GlobalKey key) {
assert(key.currentContext != null);
BuildContext focusedContext = key.currentContext;
assert(focusedContext != null);
_FocusScope focusScope = key.currentContext.ancestorWidgetOfExactType(_FocusScope);
if (focusScope != null)
if (focusScope != null) {
focusScope.focusState._setFocusedWidget(key);
Scrollable.ensureVisible(focusedContext);
}
}
/// Focuses a particular focus scope, identified by its GlobalKey. The widget
......@@ -239,7 +247,30 @@ class FocusState extends State<Focus> {
super.dispose();
}
Size _mediaSize;
EdgeDims _mediaPadding;
void _ensureVisibleIfFocused() {
if (!Focus._atScope(context))
return;
BuildContext focusedContext = _focusedWidget?.currentContext;
if (focusedContext == null)
return;
Scrollable.ensureVisible(focusedContext);
}
Widget build(BuildContext context) {
MediaQueryData data = MediaQuery.of(context);
if (data != null) {
Size newMediaSize = data.size;
EdgeDims newMediaPadding = data.padding;
if (newMediaSize != _mediaSize || newMediaPadding != _mediaPadding) {
_mediaSize = newMediaSize;
_mediaPadding = newMediaPadding;
scheduleMicrotask(_ensureVisibleIfFocused);
}
}
return new _FocusScope(
focusState: this,
scopeFocused: Focus._atScope(context),
......
......@@ -64,7 +64,7 @@ abstract class Scrollable extends StatefulComponent {
}
/// Scrolls the closest enclosing scrollable to make the given context visible.
static Future ensureVisible(BuildContext context, { Duration duration, Curve curve }) {
static Future ensureVisible(BuildContext context, { Duration duration, Curve curve: Curves.ease }) {
assert(context.findRenderObject() is RenderBox);
// TODO(abarth): This function doesn't handle nested scrollable widgets.
......@@ -80,20 +80,43 @@ abstract class Scrollable extends StatefulComponent {
assert(scrollableBox.attached);
Size scrollableSize = scrollableBox.size;
double scrollOffsetDelta;
double targetMin;
double targetMax;
double scrollableMin;
double scrollableMax;
switch (scrollable.config.scrollDirection) {
case Axis.vertical:
Point targetCenter = targetBox.localToGlobal(new Point(0.0, targetSize.height / 2.0));
Point scrollableCenter = scrollableBox.localToGlobal(new Point(0.0, scrollableSize.height / 2.0));
scrollOffsetDelta = targetCenter.y - scrollableCenter.y;
targetMin = targetBox.localToGlobal(Point.origin).y;
targetMax = targetBox.localToGlobal(new Point(0.0, targetSize.height)).y;
scrollableMin = scrollableBox.localToGlobal(Point.origin).y;
scrollableMax = scrollableBox.localToGlobal(new Point(0.0, scrollableSize.height)).y;
break;
case Axis.horizontal:
Point targetCenter = targetBox.localToGlobal(new Point(targetSize.width / 2.0, 0.0));
Point scrollableCenter = scrollableBox.localToGlobal(new Point(scrollableSize.width / 2.0, 0.0));
scrollOffsetDelta = targetCenter.x - scrollableCenter.x;
targetMin = targetBox.localToGlobal(Point.origin).x;
targetMax = targetBox.localToGlobal(new Point(targetSize.width, 0.0)).x;
scrollableMin = scrollableBox.localToGlobal(Point.origin).x;
scrollableMax = scrollableBox.localToGlobal(new Point(scrollableSize.width, 0.0)).x;
break;
}
double scrollOffsetDelta;
if (targetMin < scrollableMin) {
if (targetMax > scrollableMax) {
// The target is to big to fit inside the scrollable. The best we can do
// is to center the target.
double targetCenter = (targetMin + targetMax) / 2.0;
double scrollableCenter = (scrollableMin + scrollableMax) / 2.0;
scrollOffsetDelta = targetCenter - scrollableCenter;
} else {
scrollOffsetDelta = targetMin - scrollableMin;
}
} else if (targetMax > scrollableMax) {
scrollOffsetDelta = targetMax - scrollableMax;
} else {
return new Future.value();
}
ExtentScrollBehavior scrollBehavior = scrollable.scrollBehavior;
double scrollOffset = (scrollable.scrollOffset + scrollOffsetDelta)
.clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset);
......@@ -281,7 +304,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
return _animateTo(newScrollOffset, duration, curve);
}
Future scrollBy(double scrollDelta, { Duration duration, Curve curve }) {
Future scrollBy(double scrollDelta, { Duration duration, Curve curve: Curves.ease }) {
double newScrollOffset = scrollBehavior.applyCurve(_scrollOffset, scrollDelta);
return scrollTo(newScrollOffset, duration: duration, curve: curve);
}
......
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