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

Flutter页面滚动交互效果实现求助(参考MyFitnessPal)

Flutter 实现类似 MyFitnessPal 首页的滚动交互效果

核心实现思路

  • 通过监听滚动偏移量,动态控制页面中各组件的位置、缩放状态,实现联动动画
  • 底部固定footer直接借助Scaffold的bottomNavigationBar实现,无需额外处理滚动逻辑
  • 采用NotificationListener捕获滚动事件,配合Transform组件完成组件的位移、缩放动画

具体实现步骤

  • 滚动监听:用NotificationListener<ScrollNotification>包裹滚动容器,实时获取滚动距离
  • 固定Footer:在Scaffold中配置bottomNavigationBar,确保底部导航栏不随页面滚动
  • 黄色半圆动画:根据滚动距离,逐步缩小半圆尺寸并向左上角偏移,直至完全移出视野
  • 标题滑出:当滚动超过设定阈值,让标题和描述向上偏移,最终消失在屏幕外
  • 按钮与头像布局调整:滚动到特定位置后,将“Go Premium”按钮向左上移动,与头像并排显示,同时展示下方所有卡片

关键代码示例

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

  @override
  State<MyFitnessPalHome> createState() => _MyFitnessPalHomeState();
}

class _MyFitnessPalHomeState extends State<MyFitnessPalHome> {
  double _scrollOffset = 0.0;
  // 自定义动画阈值,可根据设计需求调整
  final double _titleHideThreshold = 200.0;
  final double _buttonMoveThreshold = 150.0;
  final double _circleDisappearThreshold = 100.0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NotificationListener<ScrollNotification>(
        onNotification: (notification) {
          setState(() {
            _scrollOffset = notification.metrics.pixels;
          });
          return true;
        },
        child: CustomScrollView(
          slivers: [
            SliverToBoxAdapter(
              child: Stack(
                children: [
                  // 左上角黄色半圆背景
                  Transform.translate(
                    offset: Offset(
                      -_scrollOffset * 0.8,
                      -_scrollOffset * 0.8,
                    ),
                    child: Transform.scale(
                      scale: 1 - (_scrollOffset / _circleDisappearThreshold).clamp(0, 1),
                      child: Container(
                        width: MediaQuery.of(context).size.width * 0.6,
                        height: MediaQuery.of(context).size.width * 0.6,
                        decoration: const BoxDecoration(
                          color: Colors.yellow,
                          borderRadius: BorderRadius.only(
                            bottomRight: Radius.circular(300),
                          ),
                        ),
                      ),
                    ),
                  ),
                  // 标题与描述区域
                  Transform.translate(
                    offset: Offset(0, -_scrollOffset.clamp(0, _titleHideThreshold)),
                    child: Padding(
                      padding: const EdgeInsets.only(top: 120, left: 20),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: const [
                          Text(
                            "JustLift",
                            style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
                          ),
                          SizedBox(height: 8),
                          Text("Track your workouts and reach goals"),
                        ],
                      ),
                    ),
                  ),
                  // 头像与Premium按钮区域
                  Positioned(
                    top: 60,
                    right: 20,
                    child: Row(
                      children: [
                        // 滚动到阈值后显示并移动按钮
                        if (_scrollOffset > _buttonMoveThreshold)
                          Transform.translate(
                            offset: Offset(
                              -(_scrollOffset - _buttonMoveThreshold) * 0.6,
                              -(_scrollOffset - _buttonMoveThreshold) * 0.3,
                            ),
                            child: ElevatedButton(
                              onPressed: () {},
                              child: const Text("Go Premium"),
                            ),
                          ),
                        const SizedBox(width: 10),
                        const CircleAvatar(
                          radius: 25,
                          // 替换为实际头像资源
                          backgroundImage: AssetImage("assets/avatar.png"),
                        ),
                      ],
                    ),
                  ),
                  // 下方卡片列表
                  Padding(
                    padding: EdgeInsets.only(
                      top: 280 - _scrollOffset.clamp(0, 100),
                      left: 20,
                      right: 20,
                      bottom: 80, // 给底部footer留空间
                    ),
                    child: Column(
                      children: [
                        Card(child: ListTile(title: const Text("Workout Plan"), subtitle: const Text("Today's routine"))),
                        Card(child: ListTile(title: const Text("Nutrition"), subtitle: const Text("Calories tracked"))),
                        Card(child: ListTile(title: const Text("Progress"), subtitle: const Text("Weekly stats"))),
                        // 可添加更多卡片
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
      // 固定底部Footer
      bottomNavigationBar: Container(
        height: 60,
        color: Colors.white,
        child: const Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            Icon(Icons.home),
            Icon(Icons.bar_chart),
            Icon(Icons.person),
          ],
        ),
      ),
    );
  }
}

优化提示

  • 可以调整动画系数(比如_scrollOffset * 0.8中的0.8),让动画速率更贴合设计预期
  • 若需要更复杂的动画逻辑,可结合AnimationController和ScrollController实现精细控制
  • 用clamp(0, maxValue)限制偏移量范围,避免组件出现异常位移

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 12:17:46