Scroll to textfield on focus

This commit is contained in:
Ivan Murashov
2018-03-09 10:57:11 +03:00
parent 19f1b7993d
commit 86e3f4ba4c
2 changed files with 92 additions and 5 deletions

View File

@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:checker/resources.dart'; import 'package:checker/resources.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -7,6 +9,8 @@ import 'package:checker/screens/settings.dart';
import 'package:checker/screens/faq.dart'; import 'package:checker/screens/faq.dart';
import 'package:checker/strings.dart'; import 'package:checker/strings.dart';
import 'package:checker/db.dart'; import 'package:checker/db.dart';
import 'package:flutter/rendering.dart';
import 'package:meta/meta.dart';
abstract class BaseState<T extends StatefulWidget> extends State<T> { abstract class BaseState<T extends StatefulWidget> extends State<T> {
@@ -249,7 +253,7 @@ abstract class BaseState<T extends StatefulWidget> extends State<T> {
EdgeInsets getInputFieldContainerPadding() { EdgeInsets getInputFieldContainerPadding() {
const double verticalPadding = 12.0; const double verticalPadding = 12.0;
const double horizontalPadding = 16.0; const double horizontalPadding = 16.0;
return new EdgeInsets.only(top: verticalPadding, return new EdgeInsets.only(top: 256.0,
bottom: verticalPadding, bottom: verticalPadding,
left: horizontalPadding, left: horizontalPadding,
right: horizontalPadding); right: horizontalPadding);
@@ -277,8 +281,6 @@ abstract class BaseState<T extends StatefulWidget> extends State<T> {
return new Container(height: containerHeight, child: new Image.asset(Resources.getLogo(app), width: imageWidth)); return new Container(height: containerHeight, child: new Image.asset(Resources.getLogo(app), width: imageWidth));
} }
/// Возвращает текстовое поле, с однострочным пояснением над ним. /// Возвращает текстовое поле, с однострочным пояснением над ним.
Widget getValueWithDescription(String title, String value) { Widget getValueWithDescription(String title, String value) {
return new Container(padding: new EdgeInsets.only(left: verticalMargin, right: verticalMargin, top: 18.0), return new Container(padding: new EdgeInsets.only(left: verticalMargin, right: verticalMargin, top: 18.0),
@@ -303,3 +305,84 @@ abstract class BaseState<T extends StatefulWidget> extends State<T> {
return new Container(margin: margin, height: buttonHeight, child: new Row(children: <Widget>[new Expanded(child: widget)])); return new Container(margin: margin, height: buttonHeight, child: new Row(children: <Widget>[new Expanded(child: widget)]));
} }
} }
class EnsureVisibleWhenFocused extends StatefulWidget {
const EnsureVisibleWhenFocused({
Key key,
@required this.child,
@required this.focusNode,
this.curve: Curves.ease,
this.duration: const Duration(milliseconds: 100),
}) : super(key: key);
/// The node we will monitor to determine if the child is focused
final FocusNode focusNode;
/// The child widget that we are wrapping
final Widget child;
/// The curve we will use to scroll ourselves into view.
///
/// Defaults to Curves.ease.
final Curve curve;
/// The duration we will use to scroll ourselves into view
///
/// Defaults to 100 milliseconds.
final Duration duration;
EnsureVisibleWhenFocusedState createState() => new EnsureVisibleWhenFocusedState();
}
class EnsureVisibleWhenFocusedState extends State<EnsureVisibleWhenFocused> {
@override
void initState() {
super.initState();
widget.focusNode.addListener(_ensureVisible);
}
@override
void dispose() {
super.dispose();
widget.focusNode.removeListener(_ensureVisible);
}
Future<Null> _ensureVisible() async {
// Wait for the keyboard to come into view
// TODO: position doesn't seem to notify listeners when metrics change,
// perhaps a NotificationListener around the scrollable could avoid
// the need insert a delay here.
await new Future.delayed(const Duration(milliseconds: 300));
if (!widget.focusNode.hasFocus)
return;
final RenderObject object = context.findRenderObject();
final RenderAbstractViewport viewport = RenderAbstractViewport.of(object);
assert(viewport != null);
ScrollableState scrollableState = Scrollable.of(context);
assert(scrollableState != null);
ScrollPosition position = scrollableState.position;
double alignment;
if (position.pixels > viewport.getOffsetToReveal(object, 0.0)) {
// Move down to the top of the viewport
alignment = 0.0;
} else if (position.pixels < viewport.getOffsetToReveal(object, 1.0)) {
// Move up to the bottom of the viewport
alignment = 1.0;
} else {
// No scrolling is necessary to reveal the child
return;
}
position.ensureVisible(
object,
alignment: alignment,
duration: widget.duration,
curve: widget.curve,
);
}
Widget build(BuildContext context) => widget.child;
}

View File

@@ -22,6 +22,7 @@ class RegistrationScreen extends BaseScreen {
class RegistrationScreenState extends BaseState<RegistrationScreen> { class RegistrationScreenState extends BaseState<RegistrationScreen> {
RegistrationScreenState(SqliteHelper helper, String app) : super(helper, app); RegistrationScreenState(SqliteHelper helper, String app) : super(helper, app);
FocusNode _focusNode = new FocusNode();
@override @override
Widget build(BuildContext ctx) { Widget build(BuildContext ctx) {
@@ -54,12 +55,15 @@ class RegistrationScreenState extends BaseState<RegistrationScreen> {
@override @override
getTextWidget() { getTextWidget() {
return new TextField( return new EnsureVisibleWhenFocused(
focusNode: _focusNode,
child: new TextField(
focusNode: _focusNode,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
decoration: new InputDecoration.collapsed( decoration: new InputDecoration.collapsed(
hintText: getHintString(), hintText: getHintString(),
hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0)), hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0)),
onChanged: (text) => handleUserInput(text)); onChanged: (text) => handleUserInput(text)));
} }
/// Возвращает кнопку регистрации. /// Возвращает кнопку регистрации.