Home > OS >  Flutter: How can I animate a widget's width when the width is based on screen size?
Flutter: How can I animate a widget's width when the width is based on screen size?

Time:09-01

I am trying to animate a TextField's width for a search bar based on a bool isSearching. I initially used AnimatedContainer with the width set to 350 if isSearching is false and 300 if isSearching is true. I need to have a "cancel" button appear to the right of the TextField when searching. Using 350 and 300 works well when on a bigger phone size (iPhone 13), but when testing on a smaller screen size (iPhone 13 mini) there is a render overflow.

Here is my class setup:

class _ExplorePageState extends ConsumerState<ExplorePage> {
  bool _isSearching = false;
  bool _cancelVisibility = false;
  late TextEditingController _searchController;
  late FocusNode _focusNode;
  double _searchFieldSize = 350;

  Future<void> toggleCancelButton() async {
    await Future.delayed(
      const Duration(milliseconds: 400),
      () {
        setState(() {
          _cancelVisibility = true;
        });
      },
    );
  }

  @override
  void initState() {
    _searchController = TextEditingController();
    _focusNode = FocusNode();
    _focusNode.addListener(() {
      if (_focusNode.hasFocus || _isSearching) {
        _searchFieldSize = 300;
        setState(() {
          _isSearching = true;
          toggleCancelButton();
        });
      } else {
        _searchFieldSize = 350;
        setState(() {
          _isSearching = false;
          _cancelVisibility = false;
          _focusNode.unfocus();
        });
      }
    });
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
    _focusNode.dispose();
    _searchController.dispose();
  }

And here is the Row where the AnimatedContainer in which I want to animate is located:

SizedBox(
                    width: MediaQuery.of(context).size.width,
                    child: Row(
                      children: [
                        const SizedBox(width: defaultMargin),
                        AnimatedContainer(
                          duration: const Duration(milliseconds: 350),
                          width: _searchFieldSize,
                          child: TextField(
                            focusNode: _focusNode,
                            controller: _searchController,
                            autofocus: false,
                            autocorrect: false,
                            style:
                                Theme.of(context).textTheme.bodySmall!.copyWith(
                                      color: isDark
                                          ? darkThemeDefaultTextColor
                                          : lightThemeDefaultTextColor,
                                    ),
                            decoration: InputDecoration(
                              prefixIcon: Icon(
                                Ionicons.search_outline,
                                color: isDark
                                    ? darkThemeSubTextColor
                                    : lightThemeSubTextColor,
                                size: 16,
                              ),
                              suffixIcon: _cancelVisibility
                                  ? IconButton(
                                      icon: Icon(
                                        Ionicons.close_circle_outline,
                                        size: 18,
                                        color: isDark
                                            ? darkThemeSubTextColor
                                            : lightThemeSubTextColor,
                                      ),
                                      onPressed: () {
                                        _searchController.clear();
                                        ref
                                            .read(explorePageControllerProvider
                                                .notifier)
                                            .clearAll();
                                        setState(() {});
                                      },
                                    )
                                  : null,
                              hintStyle: Theme.of(context)
                                  .textTheme
                                  .bodySmall!
                                  .copyWith(
                                    color: isDark
                                        ? darkThemeSubTextColor
                                        : lightThemeSubTextColor,
                                  ),
                              hintText: 'Search (character, village)',
                              contentPadding: const EdgeInsets.only(
                                  left: 14.0, bottom: 8.0, top: 8.0),
                              focusedBorder: OutlineInputBorder(
                                borderSide: BorderSide(
                                  color: isDark
                                      ? darkThemeSubTextColor
                                      : lightThemeSubTextColor,
                                ),
                                borderRadius: BorderRadius.circular(15),
                              ),
                              enabledBorder: OutlineInputBorder(
                                borderSide: BorderSide(
                                  color: isDark
                                      ? darkThemeSubTextColor
                                      : lightThemeSubTextColor,
                                ),
                                borderRadius: BorderRadius.circular(15),
                              ),
                            ),
                            onChanged: (value) async {
                              if (_searchController.value.text.isEmpty) {
                                ref
                                    .read(
                                        explorePageControllerProvider.notifier)
                                    .clearAll();
                              } else {
                                await ref
                                    .read(
                                        explorePageControllerProvider.notifier)
                                    .getSearchResults(value);
                              }
                              setState(() {});
                            },
                          ),
                        ),
                        Visibility(
                          visible: _cancelVisibility,
                          child: TextButton(
                            style: ButtonStyle(
                              overlayColor:
                                  MaterialStateProperty.all(Colors.transparent),
                            ),
                            onPressed: () {
                              _searchController.clear();
                              _focusNode.unfocus();
                              setState(() {
                                _isSearching = false;
                                _cancelVisibility = false;
                                _searchFieldSize = 350;
                              });
                            },
                            child: Text(
                              'cancel',
                              style: Theme.of(context).textTheme.bodySmall,
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),

To accommodate smaller phone sizes I would usually use MediaQuery to set the width but I need to deal with the width inside initState before build is run.

Is there a way to do this with AnimatedContainer or should I use a different approach for animating the search bar?

CodePudding user response:

I am not sure this will work but could you try this:

// You should try to estimate the width of the cancel button (Space it will take) 
//As i think it is constant across mobile devices.

Say the width of your Cancel button is 50,

var initialWidth = MediaQuery.of(context).size.width; // without cancel button

AnimatedContainer(
duration: const Duration(milliseconds: 350),
width: !isSearching ? initialWidth: initialWidth - 50  ,
child: TextField()), TextButton(child:Text("Cancel"),onPressed(){})
  • Related