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

Flutter中使用AnimatedContainer无法实现侧边栏选中项全色效果

解决Flutter侧边栏选中项全色效果与宽度动画冲突问题

我正在开发一款Flutter应用,侧边栏会在鼠标hover时切换宽度。希望选中的侧边栏项能显示全色背景,但给AnimatedContainer设置颜色后,选中项的全色效果异常,布局也被破坏了。

相关代码

import 'package:SolidCheck/core/constants/app_colors.dart';
import 'package:SolidCheck/core/constants/side_bar_menu_icons.dart';
import 'package:SolidCheck/core/utils/form_contents/form_contents.dart';
import 'package:SolidCheck/ui/screens/dbs/views/main/applicant_main.dart';
import 'package:SolidCheck/ui/screens/main/main.dart';
import 'package:SolidCheck/ui/widgets/widgets/custom_app_bar.dart';
import 'package:SolidCheck/ui/widgets/widgets/get_side_bar_border_radius.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';

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

  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  bool _isHovered = false;
  int _selectedSidebarIndex = 0;

  void _setHovered(bool isHovered) {
    setState(() {
      _isHovered = isHovered;
    });
  }

  Widget _buildSidebarItem(String assetPath, String title, int index) {
    final isSelected = index == _selectedSidebarIndex;
    return GestureDetector(
      onTap: () {
        setState(() {
          _selectedSidebarIndex = index;
        });
      },
      child: SizedBox(
        child: Container(
          decoration: BoxDecoration(
            borderRadius: getBorderRadius(_selectedSidebarIndex, index),
            color:
                isSelected ? AppColors.kWhiteColor : AppColors.sideBarMenuColor,
          ),
          padding: const EdgeInsets.symmetric(vertical: 8.0),
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const SizedBox(width: 20.0),
              SvgPicture.asset(
                assetPath,
                width: 20.0,
                height: 20.0,
                color: AppColors.kSideMenuIconColor,
              ),
              if (_isHovered || isSelected) ...[
                const SizedBox(width: 16.0),
                Expanded(
                  child: Text(
                    title,
                    overflow: TextOverflow.ellipsis,
                    style: TextStyle(
                      color: isSelected ? AppColors.kBlueColor : Colors.white,
                    ),
                    textAlign: TextAlign.left,
                  ),
                ),
              ],
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildContent(int index) {
    switch (index) {
      case 0:
        return const HomeMainScreen();
      case 1:
        return const ApplicantMainScreen();
      case 2:
        return FormContents.buildDBSFormContent();
      case 3:
        return FormContents.buildBasicDisclosureContent();
      case 4:
        return FormContents.buildReferenceCheckContent();
      case 5:
        return FormContents.buildRightToWorkContent();
      case 6:
        return FormContents.buildQualificationContent();
      case 7:
        return FormContents.buildIDContent();
      default:
        return const HomeMainScreen();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: CustomAppBar(isAdminActive: false),
      body: Row(
        children: [
          MouseRegion(
            onEnter: (_) => _setHovered(true),
            onExit: (_) => _setHovered(false),
            child: AnimatedContainer(
              duration: const Duration(milliseconds: 300),
              width: _isHovered ? 200.0 : 60.0,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  _buildSidebarItem(
                      SideBarMenuIcons.applicantSideMenuIcon8, 'Home', 0),
                  _buildSidebarItem(
                      SideBarMenuIcons.applicantSideMenuIcon1, 'Overview', 1),
                  _buildSidebarItem(
                      SideBarMenuIcons.applicantSideMenuIcon7, 'DBS Form', 2),
                  _buildSidebarItem(
                      SideBarMenuIcons.applicantSideMenuIcon6, 'Disclosure', 3),
                  _buildSidebarItem(SideBarMenuIcons.applicantSideMenuIcon5,
                      'Reference Check', 4),
                  _buildSidebarItem(SideBarMenuIcons.applicantSideMenuIcon4,
                      'Right to Work', 5),
                  _buildSidebarItem(SideBarMenuIcons.applicantSideMenuIcon3,
                      'Qualification', 6),
                  _buildSidebarItem(
                      SideBarMenuIcons.applicantSideMenuIcon2, 'ID', 7),
                ],
              ),
            ),
          ),
          Expanded(
            flex: 6,
            child: _buildContent(_selectedSidebarIndex),
          ),
        ],
      ),
    );
  }
}

问题点

给AnimatedContainer添加背景色后,侧边栏选中项的全色背景无法正常显示,布局出现混乱。

预期效果

  • 选中的侧边栏项始终显示完整的全色背景
  • 侧边栏宽度在hover时流畅切换,不影响选中项的样式

已尝试的方案

  • 调整侧边栏项Container的BoxDecoration中color属性
  • 修改AnimatedContainer的width参数

解决方案

问题根源在于:

  1. AnimatedContainer设置背景色后,会覆盖侧边栏项的背景色视觉效果,且侧边栏项的Container没有占满AnimatedContainer的宽度
  2. 侧边栏项的Row布局在宽度变化时没有正确对齐,导致选中项背景无法撑满侧边栏宽度

1. 修改AnimatedContainer配置

给AnimatedContainer设置基础背景色,并让内部Column撑满宽度:

MouseRegion(
  onEnter: (_) => _setHovered(true),
  onExit: (_) => _setHovered(false),
  child: AnimatedContainer(
    duration: const Duration(milliseconds: 300),
    width: _isHovered ? 200.0 : 60.0,
    color: AppColors.sideBarMenuColor, // 侧边栏基础背景色
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.stretch, // 让子项撑满宽度
      children: [
        // ... 侧边栏项
      ],
    ),
  ),
)

2. 重构_buildSidebarItem方法

让选中项的Container撑满侧边栏宽度,优化颜色逻辑:

Widget _buildSidebarItem(String assetPath, String title, int index) {
  final isSelected = index == _selectedSidebarIndex;
  return GestureDetector(
    onTap: () {
      setState(() {
        _selectedSidebarIndex = index;
      });
    },
    child: Container(
      decoration: BoxDecoration(
        borderRadius: getBorderRadius(_selectedSidebarIndex, index),
        color: isSelected ? AppColors.kWhiteColor : Colors.transparent, // 未选中时透明,继承侧边栏背景
      ),
      padding: const EdgeInsets.symmetric(vertical: 8.0),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.center, // 居中对齐避免布局偏移
        children: [
          const SizedBox(width: 20.0),
          SvgPicture.asset(
            assetPath,
            width: 20.0,
            height: 20.0,
            color: isSelected ? AppColors.kBlueColor : AppColors.kSideMenuIconColor, // 选中时图标同步变色
          ),
          if (_isHovered || isSelected) ...[
            const SizedBox(width: 16.0),
            Expanded(
              child: Text(
                title,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(
                  color: isSelected ? AppColors.kBlueColor : Colors.white,
                ),
              ),
            ),
          ],
        ],
      ),
    ),
  );
}

关键说明

  • 未选中项设为透明背景,继承AnimatedContainer的基础色,避免颜色重叠冲突
  • Column的CrossAxisAlignment.stretch确保每个侧边栏项自动撑满侧边栏宽度,无论展开/收起状态
  • 去掉侧边栏项外层的SizedBox,解除宽度限制
  • 选中时同步修改图标颜色,让选中状态视觉更统一

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 14:17:04