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(){})