Scroll to textfield on focus
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Возвращает кнопку регистрации.
|
/// Возвращает кнопку регистрации.
|
||||||
|
|||||||
Reference in New Issue
Block a user