Flutter 进阶 - 动画(Animation)

Flutter 进阶 - 动画(Animation)

一、隐式动画

1. AnimatedContainer

定义:
最常用的隐式动画组件,自动处理动画细节,只需改变属性值。

基础用法:

class AnimatedContainerDemo extends StatefulWidget {
  
  _AnimatedContainerDemoState createState() => _AnimatedContainerDemoState();
}

class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
  bool _isExpanded = false;

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GestureDetector(
          onTap: () {
            setState(() {
              _isExpanded = !_isExpanded;
            });
          },
          child: AnimatedContainer(
            duration: Duration(milliseconds: 500),  // 动画时长
            curve: Curves.easeInOut,                // 动画曲线
            width: _isExpanded ? 200 : 100,
            height: _isExpanded ? 200 : 100,
            color: _isExpanded ? Colors.blue : Colors.red,
            alignment: _isExpanded ? Alignment.center : Alignment.topLeft,
            child: Text('点击我', style: TextStyle(color: Colors.white)),
          ),
        ),
      ),
    );
  }
}

实际应用 - 动画卡片:

class AnimatedCard extends StatefulWidget {
  
  _AnimatedCardState createState() => _AnimatedCardState();
}

class _AnimatedCardState extends State<AnimatedCard> {
  bool _isExpanded = false;

  
  Widget build(BuildContext context) {
    return AnimatedContainer(
      duration: Duration(milliseconds: 300),
      width: double.infinity,
      height: _isExpanded ? 300 : 100,
      margin: EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12),
        boxShadow: [
          BoxShadow(
            color: Colors.grey.withOpacity(0.3),
            blurRadius: 10,
            offset: Offset(0, 5),
          ),
        ],
      ),
      child: Column(
        children: [
          ListTile(
            title: Text('可展开卡片'),
            trailing: IconButton(
              icon: Icon(_isExpanded ? Icons.expand_less : Icons.expand_more),
              onPressed: () {
                setState(() {
                  _isExpanded = !_isExpanded;
                });
              },
            ),
          ),
          if (_isExpanded)
            Expanded(
              child: Padding(
                padding: EdgeInsets.all(16),
                child: Text('这是展开后的内容...'),
              ),
            ),
        ],
      ),
    );
  }
}

2. AnimatedOpacity(透明度动画)

用法:

class OpacityDemo extends StatefulWidget {
  
  _OpacityDemoState createState() => _OpacityDemoState();
}

class _OpacityDemoState extends State<OpacityDemo> {
  bool _visible = true;

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            AnimatedOpacity(
              opacity: _visible ? 1.0 : 0.0,
              duration: Duration(milliseconds: 500),
              child: Container(
                width: 200,
                height: 200,
                color: Colors.blue,
                child: Center(child: Text('渐变效果')),
              ),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _visible = !_visible;
                });
              },
              child: Text(_visible ? '隐藏' : '显示'),
            ),
          ],
        ),
      ),
    );
  }
}

3. AnimatedAlign(对齐动画)

用法:

class AlignDemo extends StatefulWidget {
  
  _AlignDemoState createState() => _AlignDemoState();
}

class _AlignDemoState extends State<AlignDemo> {
  bool _isLeft = true;

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        width: double.infinity,
        height: 200,
        color: Colors.grey[200],
        child: AnimatedAlign(
          alignment: _isLeft ? Alignment.centerLeft : Alignment.centerRight,
          duration: Duration(milliseconds: 500),
          curve: Curves.easeInOut,
          child: GestureDetector(
            onTap: () {
              setState(() {
                _isLeft = !_isLeft;
              });
            },
            child: Container(
              width: 100,
              height: 100,
              color: Colors.blue,
              child: Center(child: Text('点击移动')),
            ),
          ),
        ),
      ),
    );
  }
}

4. AnimatedDefaultTextStyle(文本样式动画)

用法:

class TextStyleDemo extends StatefulWidget {
  
  _TextStyleDemoState createState() => _TextStyleDemoState();
}

class _TextStyleDemoState extends State<TextStyleDemo> {
  bool _isBig = false;

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            AnimatedDefaultTextStyle(
              duration: Duration(milliseconds: 500),
              style: TextStyle(
                fontSize: _isBig ? 50 : 20,
                color: _isBig ? Colors.blue : Colors.red,
                fontWeight: _isBig ? FontWeight.bold : FontWeight.normal,
              ),
              child: Text('动画文本'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                setState(() {
                  _isBig = !_isBig;
                });
              },
              child: Text('切换样式'),
            ),
          ],
        ),
      ),
    );
  }
}

二、显式动画

5. AnimationController

定义:
手动控制动画过程,更加灵活和强大。

基础示例:

class ExplicitAnimationDemo extends StatefulWidget {
  
  _ExplicitAnimationDemoState createState() => _ExplicitAnimationDemoState();
}

class _ExplicitAnimationDemoState extends State<ExplicitAnimationDemo>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  
  void initState() {
    super.initState();
    
    // 创建动画控制器
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    );
    
    // 创建动画(从 0 到 300)
    _animation = Tween<double>(begin: 0, end: 300).animate(_controller);
    
    // 监听动画状态
    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _controller.reverse();  // 反向播放
      } else if (status == AnimationStatus.dismissed) {
        _controller.forward();  // 正向播放
      }
    });
    
    // 开始动画
    _controller.forward();
  }

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: AnimatedBuilder(
          animation: _animation,
          builder: (context, child) {
            return Container(
              width: _animation.value,
              height: _animation.value,
              color: Colors.blue,
              child: child,
            );
          },
          child: Center(
            child: Text('动画', style: TextStyle(color: Colors.white)),
          ),
        ),
      ),
    );
  }
}

6. 旋转动画

class RotationDemo extends StatefulWidget {
  
  _RotationDemoState createState() => _RotationDemoState();
}

class _RotationDemoState extends State<RotationDemo>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(seconds: 2),
      vsync: this,
    )..repeat();  // 重复播放
  }

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: RotationTransition(
          turns: _controller,
          child: Icon(Icons.refresh, size: 100, color: Colors.blue),
        ),
      ),
    );
  }
}

7. 缩放动画

class ScaleDemo extends StatefulWidget {
  
  _ScaleDemoState createState() => _ScaleDemoState();
}

class _ScaleDemoState extends State<ScaleDemo>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(milliseconds: 500),
      vsync: this,
    );
    
    _animation = Tween<double>(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(parent: _controller, curve: Curves.elasticOut),
    );
    
    _controller.forward();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ScaleTransition(
          scale: _animation,
          child: Container(
            width: 200,
            height: 200,
            color: Colors.blue,
            child: Center(child: Text('弹性缩放')),
          ),
        ),
      ),
    );
  }

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

三、Hero 动画

8. Hero 基础

定义:
实现页面跳转时的共享元素动画效果。

基础用法:

// 第一个页面
class FirstPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('第一页')),
      body: Center(
        child: GestureDetector(
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => SecondPage()),
            );
          },
          child: Hero(
            tag: 'hero-image',  // 唯一标识
            child: Image.network(
              'https://picsum.photos/200',
              width: 100,
              height: 100,
            ),
          ),
        ),
      ),
    );
  }
}

// 第二个页面
class SecondPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('第二页')),
      body: Center(
        child: Hero(
          tag: 'hero-image',  // 相同的标识
          child: Image.network(
            'https://picsum.photos/200',
            width: 300,
            height: 300,
          ),
        ),
      ),
    );
  }
}

9. Hero 实际应用 - 图片详情

// 列表页
class ImageListPage extends StatelessWidget {
  final List<String> images = List.generate(
    20,
    (index) => 'https://picsum.photos/200?random=$index',
  );

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('图片列表')),
      body: GridView.builder(
        padding: EdgeInsets.all(8),
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          crossAxisSpacing: 8,
          mainAxisSpacing: 8,
        ),
        itemCount: images.length,
        itemBuilder: (context, index) {
          return GestureDetector(
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => ImageDetailPage(
                    imageUrl: images[index],
                    tag: 'image-$index',
                  ),
                ),
              );
            },
            child: Hero(
              tag: 'image-$index',
              child: ClipRRect(
                borderRadius: BorderRadius.circular(8),
                child: Image.network(images[index], fit: BoxFit.cover),
              ),
            ),
          );
        },
      ),
    );
  }
}

// 详情页
class ImageDetailPage extends StatelessWidget {
  final String imageUrl;
  final String tag;

  const ImageDetailPage({
    required this.imageUrl,
    required this.tag,
  });

  
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      appBar: AppBar(
        backgroundColor: Colors.transparent,
        elevation: 0,
      ),
      body: Center(
        child: Hero(
          tag: tag,
          child: Image.network(imageUrl),
        ),
      ),
    );
  }
}

四、动画曲线

10. 常用 Curves

// 不同的动画曲线效果
class CurvesDemo extends StatefulWidget {
  
  _CurvesDemoState createState() => _CurvesDemoState();
}

class _CurvesDemoState extends State<CurvesDemo> {
  bool _moved = false;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('动画曲线')),
      body: Column(
        children: [
          _buildAnimatedBox('linear', Curves.linear),
          _buildAnimatedBox('easeIn', Curves.easeIn),
          _buildAnimatedBox('easeOut', Curves.easeOut),
          _buildAnimatedBox('easeInOut', Curves.easeInOut),
          _buildAnimatedBox('bounceIn', Curves.bounceIn),
          _buildAnimatedBox('elasticOut', Curves.elasticOut),
          SizedBox(height: 20),
          ElevatedButton(
            onPressed: () {
              setState(() {
                _moved = !_moved;
              });
            },
            child: Text('开始动画'),
          ),
        ],
      ),
    );
  }

  Widget _buildAnimatedBox(String label, Curve curve) {
    return Container(
      height: 50,
      margin: EdgeInsets.symmetric(vertical: 4),
      child: Row(
        children: [
          SizedBox(
            width: 100,
            child: Text(label, style: TextStyle(fontSize: 12)),
          ),
          Expanded(
            child: Stack(
              children: [
                AnimatedAlign(
                  alignment: _moved ? Alignment.centerRight : Alignment.centerLeft,
                  duration: Duration(seconds: 1),
                  curve: curve,
                  child: Container(
                    width: 40,
                    height: 40,
                    decoration: BoxDecoration(
                      color: Colors.blue,
                      borderRadius: BorderRadius.circular(20),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

常用曲线:

  • Curves.linear - 线性
  • Curves.easeIn - 缓慢开始
  • Curves.easeOut - 缓慢结束
  • Curves.easeInOut - 缓慢开始和结束
  • Curves.bounceIn - 弹跳进入
  • Curves.bounceOut - 弹跳退出
  • Curves.elasticIn - 弹性进入
  • Curves.elasticOut - 弹性退出

总结

隐式动画 vs 显式动画

特性隐式动画显式动画
使用难度简单较复杂
灵活性
控制力自动手动
适用场景简单属性变化复杂动画逻辑
性能良好需要优化

选择建议

  1. 简单动画:使用隐式动画(AnimatedContainer、AnimatedOpacity 等)
  2. 复杂动画:使用显式动画(AnimationController)
  3. 页面过渡:使用 Hero 动画
  4. 组合动画:结合显式动画和自定义曲线
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值