Last active
November 29, 2022 06:38
-
-
Save collinjackson/50172e3547e959cba77e2938f2fe5ff5 to your computer and use it in GitHub Desktop.
Demonstrates scrolling a focused widget into view
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 'dart:async'; | |
import 'package:meta/meta.dart'; | |
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: 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; | |
} | |
class MyHomePage extends StatefulWidget { | |
MyHomePage({Key key}) : super(key: key); | |
@override | |
_MyHomePageState createState() => new _MyHomePageState(); | |
} | |
class _MyHomePageState extends State<MyHomePage> { | |
FocusNode _focusNode = new FocusNode(); | |
@override | |
Widget build(BuildContext context) { | |
return new Scaffold( | |
appBar: new AppBar( | |
title: new Text("Focus Example"), | |
), | |
body: new Center( | |
child: new ListView( | |
padding: new EdgeInsets.all(20.0), | |
children: <Widget>[ | |
new Container(height: 800.0, color: Colors.blue.shade200), | |
new EnsureVisibleWhenFocused( | |
focusNode: _focusNode, | |
child: new TextFormField( | |
focusNode: _focusNode, | |
decoration: new InputDecoration( | |
hintText: 'Focus me!', | |
), | |
), | |
), | |
new Container(height: 800.0, color: Colors.blue.shade200), | |
], | |
), | |
), | |
); | |
} | |
} | |
void main() { | |
runApp(new MyApp()); | |
} | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
return new MaterialApp( | |
title: 'Flutter Demo', | |
home: new MyHomePage(), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Flutter 3.3.x
This is simply possible with this trick: