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

Flutter实现点击箭头展开AppBar的方案咨询

Flutter 实现可点击展开/收起的自定义AppBar

问题描述

我想在Flutter中实现一个点击箭头图标可向下展开的AppBar,当前已有基础的AppBar实现代码,但不知道如何添加展开功能:

AppBar homeAppbar(HomeProvider homeprovider) {
    return AppBar(
      leading: Builder(builder: (context) {
        return IconButton(
          icon: ImageUtils.imageUtilsInstance
              .showSVGIcons(menuIcon, height: 30.h),
          onPressed: () {
            Scaffold.of(context).openDrawer();
          },
        );
      }),
      title: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(homeprovider.currentLocation.city!,
              style: textSemiBold(size: 18, color: colortextBlack)),
          IconButton(
              onPressed: () {
                homeprovider.getCurrentCity();
              },
              icon: homeprovider.gettingCurrentcity
                  ? SizedBox(
                      height: 20.h,
                      width: 20.h,
                      child: const CircularProgressIndicator(
                        strokeWidth: 1,
                      ))
                  : const Icon(
                      Icons.keyboard_arrow_down_rounded,
                      color: colorGrey74797D,
                    ))
        ],
      ),
      actions: [
        AuthService().getUserUID != null
            ? homeprovider.gettingProfile
                ? SizedBox(
                    height: 20.h,
                    width: 20.h,
                    child: const CircularProgressIndicator(strokeWidth: 1))
                : IconButton(
                    onPressed: () {
                      homeprovider.profilePressed();
                    },
                    icon: const Icon(
                      Icons.person,
                      color: colortextBlack,
                    ))
            : IconButton(
                onPressed: () {
                  changeScreenReplacement(routeName: signInRoute);
                },
                icon: const Icon(
                  Icons.person,
                  color: colortextBlack,
                ),
              ),
        Padding(
          padding: EdgeInsets.only(right: 15.w),
        )
      ],
    );
}

初步尝试方案

通过AppBar的bottom属性结合状态变量控制展开/收起,实现了基础展开效果:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter AppBar 演示',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // 控制展开状态
  bool isOpen = false;

  // 模拟演示用变量
  final String city = '巴黎';
  final Color colortextBlack = Colors.black;
  final bool isLoadingCity = false;
  final bool isLoadingProfile = false;
  final bool isUserLoggedIn = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: Builder(
          builder: (context) {
            return IconButton(
              icon: Icon(Icons.menu), // 演示用图标替换原SVG
              onPressed: () {
                Scaffold.of(context).openDrawer();
              },
            );
          },
        ),
        title: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text("应用标题"),
            IconButton(
                icon: Icon(isOpen
                    ? Icons.keyboard_arrow_up
                    : Icons.keyboard_arrow_down),
                onPressed: () {
                  setState(() {
                    isOpen = !isOpen;
                  });
                })
          ],
        ),
        bottom: PreferredSize(
          preferredSize: Size.fromHeight(isOpen ? 120 : 0),
          child: ClipRRect(
            borderRadius: BorderRadius.only(
              bottomLeft: Radius.circular(24.0),
              bottomRight: Radius.circular(24.0),
            ),
            child: isOpen
                ? Container(
                    height: 120,
                    padding: EdgeInsets.symmetric(horizontal: 16.0),
                    child: Column(
                      children: [
                        TextField(
                          decoration: InputDecoration(
                            hintText: "输入内容...",
                          ),
                        ),
                        SizedBox(height: 8.0),
                        TextField(
                          decoration: InputDecoration(
                            hintText: "继续输入...",
                          ),
                        ),
                      ],
                    ),
                    decoration: BoxDecoration(
                      color: Colors.white,
                    ),
                  )
                : Container(),
          ),
        ),
        actions: [
          isUserLoggedIn
              ? isLoadingProfile
                  ? SizedBox(
                      height: 20,
                      width: 20,
                      child: CircularProgressIndicator(strokeWidth: 1))
                  : IconButton(
                      onPressed: () {
                        // 个人页逻辑
                      },
                      icon: Icon(Icons.person, color: colortextBlack),
                    )
              : IconButton(
                  onPressed: () {
                    // 跳转登录页
                  },
                  icon: Icon(Icons.person, color: colortextBlack),
                ),
          Padding(
            padding: EdgeInsets.only(right: 15),
          )
        ],
      ),
      body: Center(child: Text('页面主体内容')),
    );
  }
}

最终解决方案

通过自定义AppBar组件结合动画控制器,实现带平滑过渡和遮罩的展开效果:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '自定义AppBar',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller =
        AnimationController(vsync: this, duration: Duration(milliseconds: 250));
    _animation = CurvedAnimation(parent: _controller, curve: Curves.easeInOut);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Stack(
          children: [
            ListView.builder(
              itemCount: 50,
              itemBuilder: (context, index) {
                return ListTile(
                  title: Text('条目 $index'),
                );
              },
            ),
            AnimatedBuilder(
              animation: _controller,
              builder: (context, child) {
                return Stack(
                  children: [
                    if (_controller.value > 0)
                      GestureDetector(
                        onTap: () {
                          if (_controller.isCompleted) {
                            _controller.reverse();
                          }
                        },
                        child: Container(
                          color:
                              Colors.black.withOpacity(0.3 * _controller.value),
                          width: double.infinity,
                          height: MediaQuery.of(context).size.height,
                        ),
                      ),
                    child!,
                  ],
                );
              },
              child: CustomAppBar(
                animation: _animation,
                onArrowTap: () {
                  if (_controller.isCompleted) {
                    _controller.reverse();
                  } else {
                    _controller.forward();
                  }
                },
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

class CustomAppBar extends StatelessWidget {
  final Animation<double> animation;
  final VoidCallback onArrowTap;

  CustomAppBar({required this.animation, required this.onArrowTap});

  @override
  Widget build(BuildContext context) {
    return Positioned(
      top: 0,
      left: 0,
      right: 0,
      child: Container(
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.only(
            bottomLeft: Radius.circular(24),
            bottomRight: Radius.circular(24),
          ),
        ),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Icon(Icons.menu, color: Colors.black),
                  GestureDetector(
                    onTap: onArrowTap,
                    child: Row(
                      children: [
                        Text("菜单"),
                        SizedBox(width: 8),
                        RotationTransition(
                          turns: animation,
                          child:
                              Icon(Icons.arrow_drop_down, color: Colors.black),
                        ),
                      ],
                    ),
                  ),
                  Icon(Icons.person, color: Colors.black),
                ],
              ),
            ),
            ClipRRect(
              borderRadius: BorderRadius.only(
                bottomLeft: Radius.circular(24),
                bottomRight: Radius.circular(24),
              ),
              child: SizeTransition(
                sizeFactor: animation,
                child: Container(
                  color: Colors.white,
                  height: 100.0,
                  child: Center(child: Text("展开内容")),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

关键实现点

  • 用AnimationController和CurvedAnimation实现平滑的展开/收起过渡动画
  • 自定义CustomAppBar组件,通过SizeTransition控制展开区域的显示/隐藏
  • 添加半透明遮罩,点击遮罩可快速收起展开内容
  • 箭头图标通过RotationTransition实现旋转动画,提升交互体验

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 19:18:12