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

Flutter中Rive侧边栏菜单动画致首页报错:_sidebarAnim未初始化

解决Flutter中Rive侧边栏动画的“_sidebarAnim未初始化”错误

问题核心原因

你遇到的错误本质是AnimatedBuilder未指定child参数,导致Widget构建时动画无法正确关联目标组件;同时菜单按钮的点击事件未绑定动画触发逻辑,_animationController的可空声明也增加了不必要的空安全复杂度。

修复步骤

  1. 给AnimatedBuilder添加child参数,确保动画与目标Widget正确关联
  2. 将菜单按钮的点击事件绑定到onMenuPress方法,触发动画状态切换
  3. 去掉_animationController的可空声明,简化空安全逻辑
  4. 修正onMenuPress中的动画触发逻辑,根据菜单状态反向执行动画

修改后的完整代码

import 'package:INFLUFIT/MyProfile_business.dart';
import 'package:INFLUFIT/theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';

import 'package:rive/rive.dart';
import 'post_detail.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:flutter/cupertino.dart';
import 'package:flashy_tab_bar2/flashy_tab_bar2.dart';
import 'Influencerprofile.dart';
import 'package:liquid_pull_to_refresh/liquid_pull_to_refresh.dart';
import 'add_content.dart';
import 'sidebar_menu.dart';

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
  late AnimationController _animationController;
  late Animation<double> _sidebarAnim;
  late SMIBool _menuBtn;
  int _selectedIndex = 0;

  List<Widget> tabItems = [
    Center(child: Text("0")),
    Center(child: Text("1")),
    Center(child: Text("2")),
    Center(child: Text("3")),
    Center(child: Text("4"))
  ];
  final springDesc =
      const SpringDescription(mass: 0.1, stiffness: 40, damping: 5);
  
  void _onMenuIconInit(Artboard artboard) {
    final controller =
        StateMachineController.fromArtboard(artboard, "State Machine");
    artboard.addController(controller!);
    _menuBtn = controller.findInput<bool>("isOpen") as SMIBool;
    _menuBtn.value = false; // 默认关闭状态,符合常规交互逻辑
  }

  void onMenuPress() {
    setState(() {
      _menuBtn.value = !_menuBtn.value;
    });
    // 根据菜单状态触发对应方向的弹簧动画
    final targetValue = _menuBtn.value ? 1 : 0;
    final springAnim = SpringSimulation(springDesc, _sidebarAnim.value, targetValue, 0);
    _animationController.animateWith(springAnim);
  }

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

  Future<void> _handleRefresh() async {
    return await Future.delayed(Duration(seconds: 1));
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        automaticallyImplyLeading: false,
        backgroundColor: Colors.black,
        actions: <Widget>[
          AnimatedBuilder(
            animation: _sidebarAnim,
            // 将菜单按钮作为child传入AnimatedBuilder
            child: GestureDetector(
              onTap: onMenuPress, // 绑定点击触发动画
              child: Container(
                width: 70,
                height: 70,
                margin: const EdgeInsets.all(16),
                decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(70 / 2),
                    boxShadow: [
                      BoxShadow(
                          color: RiveAppTheme.shadow.withOpacity(0.2),
                          blurRadius: 5,
                          offset: const Offset(0, 5))
                    ]),
                child: RiveAnimation.asset(
                  '/Users/lilianasanda/influfit/assets/rive/menu_button.riv',
                  stateMachines: const ["State Machine"],
                  animations: const ["open", "close"],
                  onInit: _onMenuIconInit,
                ),
              ),
            ),
            builder: (context, child) {
              return Transform.translate(
                offset: Offset(_sidebarAnim.value * 265, 0),
                child: child,
              );
            },
          ),
        ],
        title: Text(
          'INFLUFIT.CLUB',
          style: GoogleFonts.bebasNeue(
              fontWeight: FontWeight.bold, fontSize: 30, color: Colors.white),
        ),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.vertical(
            bottom: Radius.circular(30),
          ),
        ),
      ),
      body: Padding(
        padding: const EdgeInsets.all(10.0),
        child: Column(children: [
          Container(
              child: Stack(alignment: Alignment.bottomLeft, children: [
            Align(
                alignment: Alignment.topCenter,
                child: Padding(
                    padding: const EdgeInsets.all(10.0),
                    child: TextField(
                      readOnly: true,
                      decoration: InputDecoration(
                        hintText: "Search influencer",
                        border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(50),
                          borderSide: BorderSide.none,
                        ),
                        fillColor: Colors.black.withOpacity(0.1),
                        filled: true,
                      ),
                    )))
          ])),
          SizedBox(
            height: 10,
          ),
          Container(
              alignment: Alignment.centerRight,
              child: Text(
                'Location',
                style: TextStyle(),
              )),
          Expanded(
            child: LiquidPullToRefresh(
              onRefresh: _handleRefresh,
              color: Colors.black,
              child: ListView.builder(
                itemCount: tabItems.length,
                itemBuilder: (context, index) => PostDetail(),
              ),
            ),
          ),
        ]),
      ),
      bottomNavigationBar: FlashyTabBar(
        selectedIndex: _selectedIndex,
        showElevation: true,
        onItemSelected: (index) => setState(() {
          _selectedIndex = index;
        }),
        items: [
          FlashyTabBarItem(
            icon: InkWell(
              child: Icon(Icons.home),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => HomePage()),
                );
              },
            ),
            title: Text('Feed'),
          ),
          FlashyTabBarItem(
            icon: Icon(Icons.favorite),
            title: Text('Favourites'),
          ),
          FlashyTabBarItem(
            icon: InkWell(
              child: Icon(Icons.add),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => ContentPage()),
                );
              },
            ),
            title: InkWell(
              child: Text('Add'),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => ContentPage()),
                );
              },
            ),
          ),
          FlashyTabBarItem(
            icon: Icon(
              Icons.notifications_active_outlined,
            ),
            title: Text('News'),
          ),
          FlashyTabBarItem(
            icon: InkWell(
              child: Icon(Icons.person),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => BusinessPage()),
                );
              },
            ),
            title: InkWell(
              child: Text('My profile'),
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => BusinessPage()),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

关键修复说明

  • AnimatedBuilder添加child:解决了动画与目标组件未关联的核心问题,消除“未初始化”错误
  • 绑定点击事件:让菜单按钮的点击操作真正触发动画状态切换
  • 优化动画逻辑:根据菜单当前状态计算动画目标值,实现平滑的展开/收起过渡
  • 简化控制器类型:去掉可空声明,避免不必要的空安全检查,让代码更简洁

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 05:25:59