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

Flutter中Widget显示隐藏动画异常及实现合理性咨询

Fixing Fade-In/Fade-Out Animation for Toggled GridView

Let's tackle your two main issues: missing fade-out animation and broken subsequent animations. Here's the revised code with fixes, followed by clear explanations:

import 'package:flutter/material.dart';

void main() { runApp(MyApp()); }

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark(),
      home: Scaffold(
        appBar: AppBar(title: Text('Test')),
        body: Center(child: MyWidget()),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  @override
  _MyWidget createState() => _MyWidget();
}

class _MyWidget extends State<MyWidget> with SingleTickerProviderStateMixin {
  AnimationController _animationController;
  Animation<double> _animation;
  bool _isExpanded = false;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 800),
    );
    _animation = Tween<double>(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
    );
  }

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

  void _toggleExpanded() {
    if (_isExpanded) {
      // Collapse: fade out first, then hide the widget
      _animationController.reverse().whenComplete(() {
        setState(() => _isExpanded = false);
      });
    } else {
      // Expand: show the widget first, then fade it in
      setState(() => _isExpanded = true);
      // Wait for the widget to render before starting animation
      WidgetsBinding.instance.addPostFrameCallback((_) {
        _animationController.forward();
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Column(children: <Widget>[
        // First grid (always visible)
        GridView.builder(
          physics: NeverScrollableScrollPhysics(),
          shrinkWrap: true,
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: 3,
            childAspectRatio: 1.0,
            mainAxisSpacing: 0.0,
            crossAxisSpacing: 0.0,
          ),
          itemCount: 3,
          itemBuilder: (context, index) => ItemCategory(
            title: "Teste $index",
            icon: Icons.apps,
            coloritem: Colors.deepPurple,
            onpressbtn: "teste $index",
          ),
        ),
        // Second grid (toggled with animation)
        if (_isExpanded)
          FadeTransition(
            opacity: _animation,
            child: GridView.builder(
              physics: NeverScrollableScrollPhysics(),
              shrinkWrap: true,
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3,
                childAspectRatio: 1.0,
                mainAxisSpacing: 0.0,
                crossAxisSpacing: 0.0,
              ),
              itemCount: 5,
              itemBuilder: (context, index) {
                final indx = index + 3;
                return ItemCategory(
                  title: "Teste $indx",
                  icon: Icons.apps,
                  coloritem: Colors.deepPurple,
                  onpressbtn: "teste $index",
                );
              },
            ),
          ),
        // Toggle button
        IconButton(
          icon: _isExpanded 
              ? Icon(Icons.keyboard_arrow_up) 
              : Icon(Icons.keyboard_arrow_down),
          tooltip: 'Open More options',
          onPressed: _toggleExpanded,
        ),
      ]),
    );
  }
}

class ItemCategory extends StatelessWidget {
  ItemCategory({this.title, this.icon, this.coloritem, this.onpressbtn});
  final String title;
  final IconData icon;
  final MaterialColor coloritem;
  final String onpressbtn;

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: EdgeInsets.all(8.5),
      child: InkWell(
        onTap: () {},
        splashColor: Colors.amber,
        child: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              Icon(icon, size: 70.0, color: coloritem),
              Text(title, style: TextStyle(fontSize: 17.0))
            ],
          ),
        ),
      ),
    );
  }
}

Key Fixes & Explanations:

  1. Fade-Out Animation:

    • Previously, you hid the widget immediately when collapsing, which cut off the fade-out. Now, we first reverse the animation (fade from 1 to 0) and only set _isExpanded to false after the animation finishes. This lets the fade-out play fully before the widget is removed from the tree.
  2. Consistent Subsequent Animations:

    • Using WidgetsBinding.instance.addPostFrameCallback when expanding ensures the widget is fully rendered before starting the fade-in animation. This prevents cases where the animation runs before the widget is visible, making it seem like the animation failed.
    • Reversing the controller back to 0.0 when collapsing means the next forward animation starts from the beginning, not stuck at 1.0.
  3. Simplified Visibility Control:

    • Replaced Visibility with a conditional if statement. This is cleaner and ensures the widget is only present in the tree when needed, avoiding unnecessary clutter.

Is Your Original Approach Reasonable?

Splitting the content into two separate GridView widgets is totally reasonable for your use case. It keeps the code simple and avoids having to handle partial animations for individual items in a single grid. If your item count grows significantly, you might want to consider a single grid with animated item insertion/removal, but for your current scenario, the two-grid approach is efficient and easy to maintain.

内容的提问来源于stack exchange,提问作者Bruno De Souza Carvalho

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 23:37:45