Reputation: 3359
I want to achieve this:
But when Hero animation starts keyboard is forced to dismiss:
I tried to use widgets callback which is triggered after layout. But this callback is fired whenever hero animation starts. I also tried to use Future.delayed(Duration(seconds: 2)
, but it does not help. Everything is working as expected if I only remove Hero widget from the widget tree.
Here is my first Screen:
import 'package:flutter/material.dart';
import 'package:move_me_delivery/components/rounded_app_bar.dart';
import 'package:move_me_delivery/components/search_field.dart';
import '../screens.dart';
class HomeTab extends StatelessWidget {
const HomeTab({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: RoundedAppBar(title: ""),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
child: Column(
children: [
SearchTextField(
onFocusChange: (val) async {
if(val){
await Navigator.push(context, PageRouteBuilder(
transitionDuration: Duration(milliseconds: 400),
pageBuilder: (_, __, ___) => SearchScreen()));
}
},
)
],
),
)
);
}
}
Here is my second screen:
import 'package:flutter/material.dart';
import 'package:line_awesome_flutter/line_awesome_flutter.dart';
import 'package:move_me_delivery/components/search_field.dart';
class SearchScreen extends StatefulWidget {
const SearchScreen({Key? key}) : super(key: key);
@override
_SearchScreenState createState() => _SearchScreenState();
}
class _SearchScreenState extends State<SearchScreen> {
final _focusNode = FocusNode();
@override
void initState() {
super.initState();
_focusNode.requestFocus();
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: SafeArea(
child: Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
child: Column(
children: [
SearchTextField(
focus: _focusNode,
onCancel: (){
FocusScope.of(context).unfocus();
Navigator.pop(context);
},
inputDecoration: InputDecoration(
prefixIcon: Icon(LineAwesomeIcons.search, color: Colors.black,),
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.blue, width: 1))
),
),
],
),
),
),
),
);
}
}
And finally here is my SearchField screen with Hero animation:
import 'package:flutter/material.dart';
import 'package:line_awesome_flutter/line_awesome_flutter.dart';
import 'package:move_me_delivery/data/styles.dart';
class SearchTextField extends StatefulWidget {
const SearchTextField({Key? key,
this.onFocusChange,
this.focus,
this.onCancel,
this.inputDecoration
}) : super(key: key);
final void Function(bool hasFocus)? onFocusChange;
final FocusNode? focus;
final VoidCallback? onCancel;
final InputDecoration? inputDecoration;
@override
_SearchTextFieldState createState() => _SearchTextFieldState();
}
class _SearchTextFieldState extends State<SearchTextField>{
late FocusNode _focus;
@override
void initState() {
super.initState();
_focus = widget.focus ?? new FocusNode();
_focus.addListener(
(){
if(widget.onFocusChange != null){
widget.onFocusChange!(_focus.hasFocus);
}
}
);
}
@override
Widget build(BuildContext context) {
return Hero(
tag: "search",
child: Material(
type: MaterialType.card,
child: Row(
children: [
Expanded(
child: TextField(style: AppTextStyles.body2,
focusNode: _focus,
decoration: InputDecoration(
prefixIcon: Icon(LineAwesomeIcons.search, color: Colors.black,),
// suffixIcon: Text("Cancel"),
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.blue, width: 1))
))),
if(widget.onCancel != null)
GestureDetector(
onTap: widget.onCancel,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text("Cancel"),
),
)
],
),
),
);
}
}
Upvotes: 6
Views: 2310
Reputation: 843
I solved this exact same issue by creating a FocusNode
that'll requestFocus
at the end of the hero animation. However, it is also imperative that as a return for the flightShuttleBuilder
function you return a widget similar to the one on the destination, except for the fact that it won't include this FocusNode
.
Here's how it looks (on the destination page):
child: Hero(
tag: 'your_tag',
flightShuttleBuilder: (_, animation, __, ___, ____) {
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_focusNode.requestFocus();
}
});
return TextField();
},
child: TextField(focusNode: _focusNode),
),
Upvotes: 1
Reputation: 259
The reason of keyboard dimiss is when hero animation start flight TextField unmounted so its focus loss and then keyboard dismiss.
And why TextFiled become to be unmounted, you need to understand how Hero Animaiton work, refer this https://docs.flutter.dev/development/ui/animations/hero-animations.
do something at the end of hero, you can do like below:
child: Hero(
tag: "hero_tag",
flightShuttleBuilder: ((flightContext, animation, flightDirection, fromHeroContext, toHeroContext) {
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
// the end of hero animation end
_focusNode.requestFocus();
}
});
Upvotes: 3
Reputation: 944
The interface you want to achieve doesn't necessarily use Hero
widgets. It can be done with other animations. But, if you wan't to use Hero
, you can try a rather hacky solution:
On your Screen 1, set these two properties in your Hero
's TextField
:
Hero(
tag: 'search',
child: Material(
type: MaterialType.transparency,
child: TextField(
readOnly: true,
showCursor: true,
onTap: () {
Navigator.push() //to SearchScreen()
}
),
),
),
Then, on Screen 2:
Hero(
tag: 'search',
child: Material(
type: MaterialType.transparency,
child: TextField(
autofocus: true,
),
),
),
You'll have to avoid using the same SearchTextField
on both screens; they each need their own as I showed. Also, you can probably remove all of that FocusNode
code if you use this method.
Disclaimer: I haven't tested this code. It's just something to try
Upvotes: 0