简体   繁体   中英

Flutter Hero Animation dismissing keyboard on textfield

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(),
);
 }
 }

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(),
    );
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM