Skip to content

Instantly share code, notes, and snippets.

@collinjackson
Last active November 29, 2022 06:38
Show Gist options
  • Save collinjackson/50172e3547e959cba77e2938f2fe5ff5 to your computer and use it in GitHub Desktop.
Save collinjackson/50172e3547e959cba77e2938f2fe5ff5 to your computer and use it in GitHub Desktop.
Demonstrates scrolling a focused widget into view
// 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(),
);
}
}
@ncuillery-youitv
Copy link

That was helpful 🙇‍♂️ Thank you

@pishguy
Copy link

pishguy commented Jun 20, 2019

simple screen shot to know what happen with this source code

device-2019-06-20-115600

device-2019-06-20-121137

@rayworks
Copy link

rayworks commented Jan 8, 2020

Two calls on viewport.getOffsetToReveal(object, alignment) should be updated to
viewport.getOffsetToReveal(object, alignment).offset for Flutter v1.12.13+hotfix.5.

@liumengchun
Copy link

"Hello, I just used this, but it didn't work

@clayharris
Copy link

@liumengchun A lot has changed over the last couple of years. You can easily track focus states using the Focus widget now.

@MaMrEzO
Copy link

MaMrEzO commented Nov 27, 2022

Flutter 3.3.x
This is simply possible with this trick:

scrollController.animateTo( // or simpler with the jumpTo method
  focusNode.offset.dy,
  duration: const Duration(milliseconds: 100),
  curve: Curves.ease,
);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment