Reputation: 1095
I have two screens with different size of text fields. I am trying to make a hero animation for them. I want the end text field to auto focus, but the keyboard get dismissed at the second screen.
I tried to use flightShuttleBuilder to check if the animation is completed and request focus. But it is not working.
Here is the code for the second screen
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class Search extends ConsumerStatefulWidget {
@override
ConsumerState createState() => _SearchState();
}
class _SearchState extends ConsumerState<Search> {
FocusNode focusNode = FocusNode();
@override
void initState() {
// TODO: implement initState
super.initState();
// focusNode = FocusNode();
// focusNode?.requestFocus()
}
@override
void dispose() {
// TODO: implement dispose
focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) => () => focusNode.requestFocus());
Size size = MediaQuery.of(context).size;
focusNode.requestFocus();
return Scaffold(
appBar: AppBar(
title: Container(
child: Hero(
tag:'searchBar',
flightShuttleBuilder: ((flightContext, animation, flightDirection, fromHeroContext, toHeroContext){
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
focusNode.requestFocus();
}
});
return toHeroContext.widget;
}),
child: Material(
type: MaterialType.transparency,
child: SizedBox(
height:size.height * 0.05,
child: TextField(
focusNode: focusNode,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.search, size: 25.0,),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(50.0),
borderSide: const BorderSide(
width: 0,
style: BorderStyle.none,
),
),
filled: true,
hintStyle: TextStyle(color: Colors.grey[800]),
hintText: 'Search a word',
fillColor: Colors.white,
// isDense: true,
contentPadding: EdgeInsets.all(0.0),
),
),
),
),
),
),
),
body: Container(),
);
}
}
Upvotes: 3
Views: 880
Reputation: 5601
My first advice is avoid doing calculations or calling methods in the build method, this will cause to be called each time the widget tree needs to refresh (unless you use Flutter Hooks which holds the state of what is currently doing, but thats a different story)
class Search extends ConsumerStatefulWidget {
@override
ConsumerState createState() => _SearchState();
}
class _SearchState extends ConsumerState<Search> {
FocusNode focusNode = FocusNode();
@override
void initState() {
// TODO: implement initState
super.initState();
// focusNode = FocusNode();
// focusNode?.requestFocus()
}
@override
void dispose() {
// TODO: implement dispose
focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
/// no Widget WidgetsBinding.instance.addPostFrameCallback nor requestFocus call here
Size size = MediaQuery.of(context).size;
return Scaffold(
appBar: AppBar(
title: Container(
child: Hero(
tag: 'searchBar',
flightShuttleBuilder: ((flightContext, animation, flightDirection,
fromHeroContext, toHeroContext) {
/// Don't try to add a Listener here, use a StatefulWidget that uses that logic in its initState
return _FocustWidget(
animation: animation,
child: toHeroContext.widget,
focusNode: focusNode,
);
}),
child: Material(
type: MaterialType.transparency,
child: SizedBox(
height: size.height * 0.05,
child: TextField(
focusNode: focusNode,
decoration: InputDecoration(
prefixIcon: const Icon(
Icons.search,
size: 25.0,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(50.0),
borderSide: const BorderSide(
width: 0,
style: BorderStyle.none,
),
),
filled: true,
hintStyle: TextStyle(color: Colors.grey[800]),
hintText: 'Search a word',
fillColor: Colors.white,
// isDense: true,
contentPadding: EdgeInsets.all(0.0),
),
),
),
),
),
),
),
body: Container(),
);
}
}
class _FocustWidget extends StatefulWidget {
final Widget child;
final Animation<double> animation;
final FocusNode focusNode;
const _FocustWidget({
Key? key,
required this.focusNode,
required this.animation,
required this.child,
}) : super(key: key);
@override
State<_FocustWidget> createState() => __FocustWidgetState();
}
class __FocustWidgetState extends State<_FocustWidget> {
@override
void initState() {
super.initState();
widget.animation.addStatusListener(_status); ///Check for the animation state
}
void _status(AnimationStatus status) {
if (status == AnimationStatus.completed) {
Future.microtask(() => mounted ? widget.focusNode.requestFocus() : null);
}
}
@override
void dispose() {
widget.animation.removeStatusListener(_status); /// dispose the listener
super.dispose();
}
@override
Widget build(BuildContext context) => widget.child;
}
Now I talked about hooks because I see you using Consumer so I believe you use riverpod (and therefore maybe you are familiar with hooks if you saw hooks_riverpod package). If you're still grasping the concept of riverpod without hook then this is it, else you could try using hooks to reduce the statefulWidget:
class Search extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final focusNode = useFocusNode(); /// created once with hook
Size size = MediaQuery.of(context).size;
return Scaffold(
appBar: AppBar(
title: Container(
child: Hero(
tag: 'searchBar',
flightShuttleBuilder: ((flightContext, animation, flightDirection,
fromHeroContext, toHeroContext) {
return HookBuilder(
builder: (context) {
final mounted = useIsMounted();
useEffect(() {
if (animation.status == AnimationStatus.completed) {
Future.microtask(
() => mounted() ? focusNode.requestFocus() : null,
);
return null;
}
void _status(AnimationStatus status) {
if (status == AnimationStatus.completed) {
Future.microtask(
() => mounted() ? focusNode.requestFocus() : null,
);
}
}
animation.addStatusListener(_status);
return () => animation.removeStatusListener(_status);
}, const []);
return toHeroContext.widget;
},
);
}),
child: Material(
type: MaterialType.transparency,
child: SizedBox(
height: size.height * 0.05,
child: TextField(
focusNode: focusNode,
decoration: InputDecoration(
prefixIcon: const Icon(
Icons.search,
size: 25.0,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(50.0),
borderSide: const BorderSide(
width: 0,
style: BorderStyle.none,
),
),
filled: true,
hintStyle: TextStyle(color: Colors.grey[800]),
hintText: 'Search a word',
fillColor: Colors.white,
// isDense: true,
contentPadding: EdgeInsets.all(0.0),
),
),
),
),
),
),
),
body: Container(),
);
}
}
Upvotes: 1