Skip to content

Instantly share code, notes, and snippets.

@b-cancel
Last active June 28, 2019 00:49
Show Gist options
  • Select an option

  • Save b-cancel/99638f51a7659c35918894e59f4807b8 to your computer and use it in GitHub Desktop.

Select an option

Save b-cancel/99638f51a7659c35918894e59f4807b8 to your computer and use it in GitHub Desktop.
FLUTTER => Ensures that "TextFormFields" are visible when focused in Flutter
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:meta/meta.dart';
/// Note:
/// [*] some functions are async simply because that schedules them after something else that should happen first for everything to work properly
/// [*] you really should not use "ensureVisible" or "ensureErrorVisible" or "KeyboardListener" unless that field is already wrapped in "EnsureVisibleWhenFocused"
/// you will get very unusual behavior
///
/// Feature Set:
/// [1] Wrap any field with "EnsureVisibleWhenFocused" if you want that field to be automatically ensure visible on the conditions indicated
/// 1. ensure a field is visible when its toggled to focused from unfocused
/// 2. ensure a field is visible when the keyboard opens (after being dismissed manually)
/// [2] Wrap any field that is already wrapped with "EnsureVisibleWhenFocused" with an "AnimatedBuilder"
/// that updates when the error in "focusNodeToError" changes to have errors found during validation show up on screen
/// [3] Before you return the main widget in the "AnimatedBuilder" run "ensureErrorVisible" to ensure the error is still in view once it pops up
/// [4] Add a "KeyboardListener" to each field that you want to automatically scroll into focus if the keyboard is tapped
/// [5] Use "clearIsPossible" to the be able to listen to changes with an animated builder and allow a user to clear the entire text field when the user is focused on that field
///
/// Other Modifications Written By: Bryan Cancel
/// 1. Removed un needed delay that is now covered by checking keyboard metrics
/// 2. Added the Code that only makes us wait for the keyboard to be in view IF it isn't already in view
/// 3. Made "ensureVisible" an external function that can be accessible by other functions so we can refocus properly on certain edge cases
/// [+] this became relevant with the implementation of per field input validation (instead of validating the form all at once)
/// 4. Added "ensureErrorVisible" to simplify the code required to use [3] properly
/// [*] must be async to delay execution and schedule it after the error is built
/// 5. Added "KeyboardListener" that can be added as an input formatter in the "TextFormField.inputFormatters" property so that you automatically refocus when typing
/// 6. Added "keyboardWait" or the quantity of time before we check if the keyboard is open, If it isn't open we wat that quantity again until it is open
/// [*] this is flexible because it might take some phone keyboards longer to open up
/// 7. Function renames
/// 8. Added "clearIsPossible" that can then be watched by an animated builder to show or hide the icon that allows you to clear the field
/// Modified Source Code Added By: Peter Yuen
/// 1. Makes sure that if the user closes the keyboard, we wait for it to pop up, and then we ensure the field is visible
/// Modified Source Code Added By: boeledi
/// [*] https://www.didierboelens.com/2018/04/hint-4-ensure-a-textfield-or-textformfield-is-visible-in-the-viewport-when-has-the-focus/
/// 1. Added code to ensure visible when the metrics change or when the keyboard opens or closes
/// Initial Source Code Written By: Collen Jackson
/// [*] Code Can Be Found At: https://gist.github.com/collinjackson/50172e3547e959cba77e2938f2fe5ff5
/// 1. Ensure a field is visible when its focused by scrolling to it
class EnsureVisible extends StatefulWidget {
final FocusNode focusNode;
final Duration duration; //time it takes us to scroll ourselves into view
final Curve curve; //the curve we use to scroll ourselves into view
final Duration keyboardWait; //the time we wait to once again check if the keyboard is finally open
final ValueNotifier<bool> clearIsPossible; //if we pass this value notifier its implicit that we want this to be true, when the node is focused
final Widget child;
const EnsureVisible({
Key key,
@required this.focusNode,
this.duration: const Duration(milliseconds: 100),
this.curve: Curves.ease,
//what most humans consider instant is .1 seconds, so we want to check if the keyboard is finally open a little bit more often than that .05 seconds
this.keyboardWait: const Duration(milliseconds: 50), //.05 seconds = 50 milliseconds
this.clearIsPossible,
@required this.child,
}) : super(key: key);
@override
_EnsureVisibleWhenFocusedState createState() => new _EnsureVisibleWhenFocusedState();
}
class _EnsureVisibleWhenFocusedState extends State<EnsureVisible> with WidgetsBindingObserver {
@override
void initState(){
super.initState();
widget.focusNode.addListener(_listenForFocusNodeChanges);
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose(){
WidgetsBinding.instance.removeObserver(this);
widget.focusNode.removeListener(_listenForFocusNodeChanges);
super.dispose();
}
@override
void didChangeMetrics(){
if(widget.focusNode.hasFocus) _waitForKeyboardToOpenAndEnsureVisible();
}
_listenForFocusNodeChanges() async{
//used if you want a clear field option to show up per field only when its focused
if(widget.clearIsPossible != null){
if(widget.focusNode.hasFocus) widget.clearIsPossible.value = true;
else widget.clearIsPossible.value = false;
}
//wait until keyboard is open
_waitForKeyboardToOpen();
}
Future<Null> _waitForKeyboardToOpen() async {
if (mounted){
EdgeInsets closedInsets = MediaQuery.of(context).viewInsets;
//this works because MediaQuery.of(context).viewInsets only changes ONCE when the keyboard is FULLY open
while (mounted && MediaQuery.of(context).viewInsets == closedInsets) {
await new Future.delayed(widget.keyboardWait);
}
}
return;
}
Future<Null> _waitForKeyboardToOpenAndEnsureVisible() async {
// Wait for the keyboard to come into view (if it isn't in view already)
if(MediaQuery.of(context).viewInsets == EdgeInsets.zero) await _waitForKeyboardToOpen();
// ensure our focusNode is visible
ensureVisible(context, widget.focusNode, duration: widget.duration, curve: widget.curve);
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
ensureVisible(BuildContext context, FocusNode focusNode, {Duration duration: const Duration(milliseconds: 100), Curve curve: Curves.ease}){
// No need to go any further if the node has not the focus
if (focusNode.hasFocus){
// Find the object which has the focus
final RenderObject object = context.findRenderObject();
final RenderAbstractViewport viewport = RenderAbstractViewport.of(object);
assert(viewport != null);
// Get the Scrollable state (in order to retrieve its offset)
ScrollableState scrollableState = Scrollable.of(context);
assert(scrollableState != null);
// Get its offset
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;
}
if(alignment != null){
position.ensureVisible(
object,
alignment: alignment,
duration: duration,
curve: curve,
);
}
}
}
ensureErrorVisible( context, focusNode, {Duration duration: const Duration(milliseconds: 100), Curve curve: Curves.ease,}) async => ensureVisible(context, focusNode, duration: duration, curve: curve);
class KeyboardListener extends TextInputFormatter {
final BuildContext context;
final FocusNode focusNode;
final Duration duration;
final Curve curve;
KeyboardListener(this.context, this.focusNode, {this.duration: const Duration(milliseconds: 100), this.curve: Curves.ease});
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
){
ensureVisible(context, focusNode, duration: duration, curve: curve);
return newValue;
}
}
@baeharam

Copy link
Copy Markdown

Typo in 138 and 141 lines, add .offset

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