I have a CustomScrollView with a SliverAppBar that hides on scroll.
On the app bar is a search button that, when pressed, puts a TextField into the app bar.
When the field gets focus, it causes the scroll view to scroll all the way to the top, and the app bar gets stuck up in the "unsafe" area:
The Scaffold docs mention that when the keyboard is shown the scaffold's insets change and the scaffold is rebuilt, causing the "focused widget will be scrolled into view if it's within a scrollable container".
This seems like the behavior I don't want. I looked but couldn't understand the mechanism or how to suppress it. Is doing so possible?
The source code for the view in the image is here.
Also I note that this problem didn't happen in my previous implementation with non-sliver, standard widgets. I suspect this is because the app bar was not in a scrollable view, whereas SliverAppBar is inside the CustomScrollView so that it can interact with the main body.
Edit: This issue was fixed by this PR which appears to have first landed in Flutter 1.22.0.
It took some sleuthing, but I eventually found the mechanism for this behavior.
TextField wraps EditableText. When the latter gains focus, it will invoke _showCaretOnScreen, which includes a call to renderEditable.showOnScreen. This bubbles up and eventually causes the scrolling behavior.
We can force _showCaretOnScreen to return early here if we supply to the TextField a hacked ScrollController that always returns false from hasClients:
class _HackScrollController extends ScrollController {
// Causes early return from EditableText._showCaretOnScreen, preventing focus
// gain from making the CustomScrollView jump to the top.
#override
bool get hasClients => false;
}
The behavior does not seem intentional, so I reported it as bug #60422.
Caveats
This workaround may not be very stable.
I don't know what adverse effects the hasClients override might have.
The docs for TextField say that the scrollController is used "when vertically scrolling the input". In this use case we don't want vertical scrolling anyway, so the workaround might not cause any problems. In my brief testing it didn't seem to cause problems with horizontal (overflow) scrolling.
Just like the documentation mentions try and use resizeToAvoidBottomInset parameter to false (defaults to true) in the scaffold widget https://api.flutter.dev/flutter/material/Scaffold/resizeToAvoidBottomInset.html
Also I would recommend creating ValueListenableBuilder after the scaffold (as the first widget in the body) to avoid rebuilding the whole scaffold and just the body widget
Related
Looking at the Flutter Hero Transition, it appears to move the tagged Widgets to an Overlay class that exists in all Navigator Widgets but sits above the main content in the stack.
If this is correct, it allows the Hero to widgets to still respond to the Route scope and its animators but exist above the actual route content. How is this actually done efficiently? Surely this involves taking an entire Widget and storing it in a state for the duration of the animation. That Widget still has to respond to intrinsic responses from its original position such as slivers responding to active scroll actions.
Recreating this could be done with state management but I wondered how the standard hero actually does this. It seems like Widgets are effectively duplicated and then conditionally rendered on the screen defaulting to the overlay during the route animation and swapping out the original widget with an Offstage or similar. Is this how it is done?
The reason for trying to understand it is the need to replicate this behaviour in situations where Navigator is not an effective use case for a transition taking place internally on a page. I built an accordion style navigator but still want a hero transition to take place on the AppBar / NavigationBar. I know that this could be done with Navigator but it doesn't suit the use case. I could also predefine the AppBar content for each internal navigator state of the accordion but that is a lot of additional code.
Widget Tree - https://i.imgur.com/xZjMRm2.png
So I have this widget tree, the TextField in question is inside the column where it shows a RenderFlex error(please ignore the error, it's only there because I animate the column to appear and disappear on button pressed).
The problem is every time I tap on the TextField my screen is pushed up from the bottom as the keyboard is pushing their way through.
To fix this issue I tried to put the Scaffold property resizeToAvoidBottomInset: false, but it did not fix the issue.
Also when I close the keyboard, the background of my screen changes.
If you need code snippets please let me know.
Thank you for your time :)
i need a code snippets, but you can add SingleChildScrollView at the top of the column.
and for when you close the keyboard, the background of your screen changes, my guess for that is when the keyboard opens the screen rebuilds and this will call the build function so if you initialized any variables or anything related to the background change inside the build function it will rebuild or re-initialized.
i do not know if my answer helps but it will be better to share a code snippest, have a great day.
I uses TextFormField with Scrollable parent, when the keyboard shows up, is there any way to have the Widgets to be above the keyboard? Is it FocusNode that I should be using?
Current Situation
From this, you can see that when my keyboard shows up, the Button will be covered.
If you can, just put all widgets which needs to stay over the keyboard in a SingleChildScrollView, and set reverse property of the SingleChildScrollView to true
I currently have a listview which contain some widgets that I collapse when they leave the viewport, but this causes everything below to move upwards which becomes a really strange experience for the user.
Is there a way to have the widgets collapse without moving below when the collapse happens outside the viewport?
I use collapsible and visibility_detector to handle the collapse and the check for whether the widget is in view or not.
I want users to scroll between pages in PageView, but I don't want to show them an animation when they try to scroll before first and after last page. I can switch between colorful animation, black animation and no scrolling, but I could not find any possibility to disable the animation at all.
If there is no such possibility, how can I change the color of that animation or make it transparent at least?
Based on your screenshot, I can say that you are using BouncingScrollPhysics for your PageView. This behavior is commonly used by iOS devices. Nonetheless, I have also reviewed the entire source code you have provided here.
What went wrong
You have added PageView without accompanying it with a Scaffold or Material widget at the top level that's why the background behind the children of the PageView is color black.
https://dartpad.dev/c709e410d0a68248ac5b387b3bc6af93
From the documentation:
Scaffold implements the basic material design visual layout structure.
Without this widget, you'll notice that your app may occupy the entire screen of your device, including the notification bar, because it (PageView) does not know where is the status bar is located in the screen.
What you can do
I noticed that all of the children added inside the PageView has individual Scaffold and AppBar, it's not really necessary to nest scaffolds and you may want to use TabBarView instead of PageView, and let the parent widget handle the AppBar changes via TabController.
But if you think it'll cost you too much effort to refactor, feel free to review the following options that require minimal changes which will suit your needs:
Option 1. You can wrap your widget inside a Scaffold widget.
https://dartpad.dev/4620ff91444353f5e000d2063594bd96
Option 2. Given that nesting Scaffold widgets is not a good practice, you can just use the plain Material widget to wrap your PageView with children wrapped with Scaffold widget.
https://dartpad.dev/43f8730e5592ce1f96193fc01f08a29c
These solutions will change the background color of the PageView from black to white.
Option 3. If you really want to get rid of the animation, the easiest way to hack it is changing your scroll physics:
physics: ClampingScrollPhysics(),
However, this still has a glowing or ripple effect when you try to swipe at the end of the screen.
To further get rid of this effect, I'll share with you these SO answers:
How to remove scroll glow? (works for Android)
How to remove overscroll on ios? (works for iOS)
Further reading
https://api.flutter.dev/flutter/widgets/ScrollPhysics-class.html
https://medium.com/flutter-community/custom-scroll-physics-in-flutter-3224dd9e9b41