-
-
Save Qixingchen/ee1592e08ea4fcf4957da6d6e8742267 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:meta/meta.dart'; | |
import 'dart:async'; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter/rendering.dart'; | |
/// A widget that ensures it is always visible when focused. | |
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: 600)); | |
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; | |
} | |
class Demo extends StatefulWidget { | |
DemoState createState() => new DemoState(); | |
} | |
class DemoState extends State<Demo> { | |
FocusNode _usernameFocusNode = new FocusNode(); | |
FocusNode _passwordFocusNode = new FocusNode(); | |
@override | |
Widget build(BuildContext context) { | |
return new MaterialApp( | |
home: new Scaffold( | |
appBar: new AppBar( | |
title: new Text('Example App'), | |
), | |
body: new Container( | |
child: new ListView( | |
physics: new NeverScrollableScrollPhysics(), | |
key: new PageStorageKey("Divider 1"), | |
children: <Widget>[ | |
new Container( | |
constraints: new BoxConstraints.expand(height: 640.0), | |
decoration: new BoxDecoration( | |
image: new DecorationImage( | |
image: new NetworkImage( | |
'https://flutter.io/images/flutter-mark-square-100.png', | |
), | |
fit: BoxFit.cover, | |
), | |
), | |
child: new Column( | |
children: <Widget>[ | |
new Container( | |
height: 300.0, | |
), | |
new Center( | |
child: new EnsureVisibleWhenFocused( | |
focusNode: _usernameFocusNode, | |
child: new TextFormField( | |
focusNode: _usernameFocusNode, | |
decoration: new InputDecoration( | |
labelText: 'Username', | |
), | |
), | |
), | |
), | |
new Container(height: 8.0), | |
new Center( | |
child: new EnsureVisibleWhenFocused( | |
focusNode: _passwordFocusNode, | |
child: new TextFormField( | |
focusNode: _passwordFocusNode, | |
obscureText: true, | |
decoration: new InputDecoration( | |
labelText: 'Password', | |
), | |
), | |
), | |
), | |
new Container(), | |
new RaisedButton( | |
onPressed: () {}, | |
child: new Text('Log in'), | |
), | |
new Divider(), | |
new RaisedButton( | |
onPressed: () {}, | |
child: new Text('Sign up'), | |
), | |
], | |
), | |
), | |
], | |
), | |
), | |
), | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2017, the Flutter project authors. Please see the AUTHORS file | |
// for details. All rights reserved. Use of this source code is governed by a | |
// BSD-style license that can be found in the LICENSE file. | |
import 'package:flutter/material.dart'; | |
import 'demo.dart'; | |
void main() { | |
// debugPaintSizeEnabled = true; | |
// runApp(new IntroEdit()); | |
runApp(new Demo()); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment