Autoscroll when keyboard open on purchase screen

This commit is contained in:
Ivan Murashov
2018-03-26 00:33:24 +03:00
parent b6abdac78b
commit 1f5838ce16
4 changed files with 49 additions and 96 deletions

View File

@@ -318,84 +318,3 @@ 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)]));
}
}
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: 100));
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

@@ -45,6 +45,8 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
void initState() {
loading = true;
requestAsyncData(user);
buildFocusNode();
scrollController = new ScrollController();
super.initState();
}
@@ -54,12 +56,14 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
}
bool purchaseInProgress = false;
ScrollController scrollController;
Map user;
String card = '';
String loyalityType = '';
String loyalty = '';
String bonus = '';
List<Map> coupons = [];
ListView listView;
@override
Widget getScreenContent() {
@@ -119,8 +123,8 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
)
));
return new Container(child: new ListView(reverse: true,
children: <Widget>[new Column(children: widgetList)].reversed.toList()));
listView = new ListView(children: widgetList, controller: scrollController);
return listView;
}
getBonusInputField() {
@@ -130,6 +134,7 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
hintText: (app != 'crypto') ? StringsLocalization.bonusHint() : StringsLocalization.joysHint(),
hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0)
),
focusNode: bonusFocusNode,
controller: bonusController,
onSubmitted: (String text) {
setState(() {
@@ -206,13 +211,13 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
hintText: getHintString(),
hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0)),
controller: controller,
focusNode: sumFocusNode,
onSubmitted: (String text) {
setState(() {
controller.text = _parseSum(text);
});
},
textAlign: TextAlign.center,
autofocus: true);
textAlign: TextAlign.center);
}
requestAsyncData(Map user) async {
@@ -436,4 +441,39 @@ class PurchaseScreenState<T> extends BaseState<PurchaseScreen> {
Navigator.of(context).pop(token);
});
}
FocusNode bonusFocusNode = new FocusNode();
FocusNode sumFocusNode = new FocusNode();
// TODO: Удалить дублирующийся код.
void buildFocusNode() {
sumFocusNode.addListener(() {
setState(() {
if (sumFocusNode.hasFocus && bonusFocusNode.hasFocus) {
bonusFocusNode.unfocus();
}
if (sumFocusNode.hasFocus) {
scrollController.animateTo(100.0, duration: new Duration(seconds: 1), curve: Curves.ease);
}
});
});
bonusFocusNode.addListener(() {
setState(() {
if (bonusFocusNode.hasFocus && sumFocusNode.hasFocus) {
sumFocusNode.unfocus();
}
if (bonusFocusNode.hasFocus) {
scrollController.animateTo(120.0, duration: new Duration(seconds: 1), curve: Curves.ease);
}
});
});
}
}

View File

@@ -57,15 +57,13 @@ class RegistrationScreenState extends BaseState<RegistrationScreen> {
@override
getTextWidget() {
return new EnsureVisibleWhenFocused(
focusNode: _focusNode,
child: new TextField(
return new TextField(
focusNode: _focusNode,
keyboardType: TextInputType.number,
decoration: new InputDecoration.collapsed(
hintText: getHintString(),
hintStyle: new TextStyle(color: greyTextColor, fontSize: 16.0)),
onChanged: (text) => handleUserInput(text)));
onChanged: (text) => handleUserInput(text));
}
/// Возвращает кнопку регистрации.