You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Material 3实现类似Google Play Books的全屏展开搜索栏方案

实现类似Google Play Books的全屏搜索展开效果(Material 3)

需求:实现Google Play Books中那种点击搜索栏后,搜索区域从AppBar内的小卡片平滑展开为全屏搜索界面的效果,当前已在AppBar的Card组件内嵌套了TextField,基于Material 3开发。

现有代码片段:

appBar: AppBar(
  title: Card(
    elevation: 2,
    child: TextField(
      autocorrect: false,
      controller: _textEditingController,
      onChanged: (value) {
        setState(() {});
      },
      decoration: InputDecoration(
        hintText: "Search",
        border: InputBorder.none,
        prefixIcon: const Icon(
          Icons.search,
        ),
        suffixIcon: _textEditingController.text.isEmpty
            ? null
            : IconButton(
                icon: const Icon(Icons.clear),
                onPressed: _clearTextField,
              ),
      ),
    ),
  ),
)

实现步骤与完整代码

核心思路是通过状态变量控制搜索展开状态,结合动画切换AppBar和全屏搜索界面的显示:

  1. 添加状态变量管理展开状态
  2. 为搜索栏添加点击事件触发展开
  3. 构建自适应的AppBar和全屏搜索区域
  4. 添加平滑过渡动画提升体验

完整示例代码:

import 'package:flutter/material.dart';

class SearchScreen extends StatefulWidget {
  const SearchScreen({super.key});

  @override
  State<SearchScreen> createState() => _SearchScreenState();
}

class _SearchScreenState extends State<SearchScreen> with SingleTickerProviderStateMixin {
  final TextEditingController _textEditingController = TextEditingController();
  bool _isSearchExpanded = false;
  late AnimationController _animationController;
  late Animation<double> _opacityAnimation;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    );
    _opacityAnimation = Tween<double>(begin: 1, end: 0).animate(
      CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
    );
  }

  void _clearTextField() {
    _textEditingController.clear();
    setState(() {});
  }

  void _toggleSearchExpansion() {
    setState(() {
      _isSearchExpanded = !_isSearchExpanded;
      if (_isSearchExpanded) {
        _animationController.forward();
        // 延迟获取焦点,确保动画流畅
        Future.delayed(const Duration(milliseconds: 200), () {
          FocusScope.of(context).requestFocus(FocusNode());
        });
      } else {
        _animationController.reverse();
        FocusScope.of(context).unfocus();
      }
    });
  }

  @override
  void dispose() {
    _textEditingController.dispose();
    _animationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: _isSearchExpanded
          ? AppBar(
              leading: IconButton(
                icon: const Icon(Icons.arrow_back),
                onPressed: _toggleSearchExpansion,
              ),
              title: TextField(
                autocorrect: false,
                controller: _textEditingController,
                autofocus: true,
                onChanged: (value) {
                  setState(() {});
                },
                decoration: const InputDecoration(
                  hintText: "Search",
                  border: InputBorder.none,
                ),
              ),
              actions: [
                if (_textEditingController.text.isNotEmpty)
                  IconButton(
                    icon: const Icon(Icons.clear),
                    onPressed: _clearTextField,
                  ),
              ],
            )
          : AppBar(
              title: GestureDetector(
                onTap: _toggleSearchExpansion,
                child: Card(
                  elevation: 2,
                  child: TextField(
                    autocorrect: false,
                    controller: _textEditingController,
                    enabled: false, // 非展开状态仅允许点击触发展开
                    decoration: InputDecoration(
                      hintText: "Search",
                      border: InputBorder.none,
                      prefixIcon: const Icon(Icons.search),
                      suffixIcon: _textEditingController.text.isEmpty
                          ? null
                          : IconButton(
                              icon: const Icon(Icons.clear),
                              onPressed: _clearTextField,
                            ),
                    ),
                  ),
                ),
              ),
            ),
      body: Stack(
        children: [
          // 原页面内容,展开时渐隐
          FadeTransition(
            opacity: _opacityAnimation,
            child: const Center(
              child: Text("原页面内容区域"),
            ),
          ),
          // 全屏搜索结果区域,展开时显示
          if (_isSearchExpanded)
            Positioned.fill(
              child: Container(
                color: Theme.of(context).scaffoldBackgroundColor,
                child: ListView(
                  padding: const EdgeInsets.all(16),
                  children: [
                    // 搜索结果示例
                    ListTile(
                      leading: const Icon(Icons.book),
                      title: const Text("搜索结果1"),
                      subtitle: const Text("书籍描述内容"),
                      onTap: () {},
                    ),
                    ListTile(
                      leading: const Icon(Icons.book),
                      title: const Text("搜索结果2"),
                      subtitle: const Text("书籍描述内容"),
                      onTap: () {},
                    ),
                  ],
                ),
              ),
            ),
        ],
      ),
    );
  }
}

关键说明

  • 非展开状态下,TextField设置为enabled: false,确保只能通过点击Card触发展开,避免误操作
  • 使用AnimationController实现原页面内容的渐隐动画,让过渡更自然
  • 展开时AppBar切换为带返回按钮的搜索栏,同时全屏显示搜索结果列表
  • 处理焦点逻辑,确保展开时自动弹出键盘,收起时关闭键盘

内容的提问来源于stack exchange,提问作者graduationchampion

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 02:20:43