如何在Flutter中实现标题旁的搜索按钮(非AppBar场景)
Got it, let's build that smooth expandable search effect you're after! Your initial Stack approach is perfect—let's add animations to make the search field slide in and cover the title when the magnifying glass is tapped. Here's a complete, working implementation:
Step-by-Step Implementation
First, we'll use a StatefulWidget with an AnimationController to handle the smooth transitions. We'll animate three key elements:
- The search field's width (from 0 to full width)
- The title's opacity (from fully visible to hidden)
- The magnifying glass button's position (sliding left as the search field expands)
Full Code Example
import 'package:flutter/material.dart'; class ExpandableSearchHeader extends StatefulWidget { const ExpandableSearchHeader({super.key}); @override State<ExpandableSearchHeader> createState() => _ExpandableSearchHeaderState(); } class _ExpandableSearchHeaderState extends State<ExpandableSearchHeader> with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation<double> _searchWidthAnim; late Animation<double> _titleOpacityAnim; late Animation<double> _iconPositionAnim; bool _isSearchActive = false; @override void initState() { super.initState(); // Initialize animation controller with a smooth 300ms duration _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), ); // Animate search field width from 0 to full available space _searchWidthAnim = Tween<double>(begin: 0, end: double.infinity).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), ); // Fade out title as search field expands _titleOpacityAnim = Tween<double>(begin: 1, end: 0).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeOut), ); // Slide icon button left to sit next to the expanded search field _iconPositionAnim = Tween<double>(begin: 0, end: 16).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), ); } @override void dispose() { _animationController.dispose(); super.dispose(); } void _toggleSearch() { setState(() => _isSearchActive = !_isSearchActive); // Play or reverse the animation based on search state _isSearchActive ? _animationController.forward() : _animationController.reverse(); } Widget _buildTitle(BuildContext context) { return FadeTransition( opacity: _titleOpacityAnim, child: const Text( "Your Page Title", style: TextStyle(fontSize: 20, fontWeight: FontWeight.w600), ), ); } Widget _buildSearchField(BuildContext context) { return AnimatedBuilder( animation: _searchWidthAnim, builder: (context, child) { return SizedBox( width: _searchWidthAnim.value, child: TextField( decoration: const InputDecoration( hintText: "Search...", border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 12), ), autofocus: _isSearchActive, // Auto-focus when search opens onSubmitted: (value) { // Add your search logic here _toggleSearch(); // Optional: Close search after submission }, ), ); }, ); } @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), color: Theme.of(context).scaffoldBackgroundColor, child: Stack( alignment: Alignment.centerLeft, children: [ // Title sits at the bottom, fades out when search expands _buildTitle(context), // Search field overlays the title, slides in from the right _buildSearchField(context), // Magnifying glass button stays on top, slides left with animation AnimatedBuilder( animation: _iconPositionAnim, builder: (context, child) { return Positioned( right: _iconPositionAnim.value, child: IconButton( icon: Icon(_isSearchActive ? Icons.close : Icons.search), onPressed: _toggleSearch, ), ); }, ), ], ), ); } } // How to use it in your app void main() { runApp(const MaterialApp(home: Scaffold(body: ExpandableSearchHeader()))); }
Key Details to Note
- AnimationController: Manages the timing and direction of all animations, using
SingleTickerProviderStateMixinto sync with the app's refresh rate. - CurvedAnimation: Adds natural easing to the transitions, so the animation doesn't feel robotic. You can swap
Curves.easeInOutwith others likeCurves.fastOutSlowInfor different feels. - AnimatedBuilder: Efficiently rebuilds only the animated parts of the widget, avoiding unnecessary full-widget rebuilds.
- Auto-focus: The search field automatically gets focus when expanded, making it seamless for users to start typing.
Quick Optimizations You Can Add
- Wrap the entire header in a
GestureDetectorto close the search field when tapping outside the search area. - Add a subtle border to the search field when expanded for better visual separation.
- Adjust the animation duration or curves to match your app's design system.
内容的提问来源于stack exchange,提问作者Leonidas Yopan
相关产品推荐
相关产品推荐

