Flutter动画高级技巧:创建流畅的用户体验

引言

动画是现代移动应用中不可或缺的一部分,它可以提升用户体验,使应用更加生动和富有吸引力。Flutter提供了强大的动画系统,从基本的补间动画到复杂的物理动画,都可以轻松实现。本文将深入探讨Flutter动画的高级技巧,帮助你创建流畅、自然的动画效果。

一、Flutter动画基础

1. 动画控制器

import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';

class MyAnimationWidget extends StatefulWidget {
  @override
  _MyAnimationWidgetState createState() => _MyAnimationWidgetState();
}

class _MyAnimationWidgetState extends State<MyAnimationWidget> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );
    _animation = CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    );
    _controller.forward();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ScaleTransition(
          scale: _animation,
          child: Container(
            width: 200,
            height: 200,
            color: Colors.blue,
          ),
        ),
      ),
    );
  }
}

2. 动画类型

Flutter提供了多种动画类型:

  • 补间动画:在两个值之间进行插值
  • 物理动画:模拟物理世界的运动
  • 页面转场动画:页面之间的过渡效果
  • Hero动画:元素在页面之间的平滑过渡

二、高级补间动画

1. 自定义曲线

// 自定义曲线
class CustomCurve extends Curve {
  @override
  double transform(double t) {
    // 自定义曲线逻辑
    return t * t * (3.0 - 2.0 * t); // 三次贝塞尔曲线
  }
}

// 使用自定义曲线
_animation = CurvedAnimation(
  parent: _controller,
  curve: CustomCurve(),
);

2. 多动画组合

class MultiAnimationWidget extends StatefulWidget {
  @override
  _MultiAnimationWidgetState createState() => _MultiAnimationWidgetState();
}

class _MultiAnimationWidgetState extends State<MultiAnimationWidget> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _scaleAnimation;
  late Animation<double> _opacityAnimation;
  late Animation<Offset> _slideAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );

    _scaleAnimation = Tween<double>(begin: 0.5, end: 1.0).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeOut),
    );

    _opacityAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeIn),
    );

    _slideAnimation = Tween<Offset>(begin: Offset(0, 1), end: Offset(0, 0)).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeOut),
    );

    _controller.forward();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: FadeTransition(
          opacity: _opacityAnimation,
          child: SlideTransition(
            position: _slideAnimation,
            child: ScaleTransition(
              scale: _scaleAnimation,
              child: Container(
                width: 200,
                height: 200,
                color: Colors.blue,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

3. 动画序列

class AnimationSequenceWidget extends StatefulWidget {
  @override
  _AnimationSequenceWidgetState createState() => _AnimationSequenceWidgetState();
}

class _AnimationSequenceWidgetState extends State<AnimationSequenceWidget> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _firstAnimation;
  late Animation<double> _secondAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 4),
      vsync: this,
    );

    _firstAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Interval(0.0, 0.5, curve: Curves.easeOut),
      ),
    );

    _secondAnimation = Tween<double>(begin: 1.0, end: 0.5).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Interval(0.5, 1.0, curve: Curves.easeIn),
      ),
    );

    _controller.forward();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: AnimatedBuilder(
          animation: _controller,
          builder: (context, child) {
            return Transform.scale(
              scale: _firstAnimation.value > 0.5 ? _secondAnimation.value : _firstAnimation.value,
              child: Container(
                width: 200,
                height: 200,
                color: Colors.blue,
              ),
            );
          },
        ),
      ),
    );
  }
}

三、物理动画

1. 弹簧动画

import 'package:flutter/physics.dart';

class SpringAnimationWidget extends StatefulWidget {
  @override
  _SpringAnimationWidgetState createState() => _SpringAnimationWidgetState();
}

class _SpringAnimationWidgetState extends State<SpringAnimationWidget> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<Offset> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 1),
      vsync: this,
    );

    final spring = SpringSimulation(
      SpringDescription(
        mass: 1.0,
        stiffness: 100.0,
        damping: 10.0,
      ),
      0.0,
      1.0,
      0.0,
    );

    _animation = Tween<Offset>(begin: Offset(0, 0), end: Offset(1, 0)).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Curves.linear,
      ),
    );

    _controller.animateWith(spring);
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SlideTransition(
          position: _animation,
          child: Container(
            width: 100,
            height: 100,
            color: Colors.blue,
          ),
        ),
      ),
    );
  }
}

2. 重力动画

class GravityAnimationWidget extends StatefulWidget {
  @override
  _GravityAnimationWidgetState createState() => _GravityAnimationWidgetState();
}

class _GravityAnimationWidgetState extends State<GravityAnimationWidget> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );

    final gravity = GravitySimulation(
      9.8, // 重力加速度
      0.0, // 初始位置
      0.0, // 初始速度
      500.0, // 目标位置
    );

    _animation = Tween<double>(begin: 0.0, end: 500.0).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Curves.linear,
      ),
    );

    _controller.animateWith(gravity);
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          AnimatedBuilder(
            animation: _controller,
            builder: (context, child) {
              return Positioned(
                left: 100,
                top: _animation.value,
                child: Container(
                  width: 100,
                  height: 100,
                  color: Colors.blue,
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}

四、Hero动画

1. 基本Hero动画

// 源页面
class SourcePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Source Page')),
      body: GestureDetector(
        onTap: () {
          Navigator.push(
            context,
            MaterialPageRoute(builder: (context) => DestinationPage()),
          );
        },
        child: Hero(
          tag: 'imageHero',
          child: Image.network(
            'https://example.com/image.jpg',
            width: 100,
            height: 100,
          ),
        ),
      ),
    );
  }
}

// 目标页面
class DestinationPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Destination Page')),
      body: Center(
        child: Hero(
          tag: 'imageHero',
          child: Image.network(
            'https://example.com/image.jpg',
            width: 300,
            height: 300,
          ),
        ),
      ),
    );
  }
}

2. 高级Hero动画

class AdvancedHeroWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Advanced Hero')),
      body: GridView.builder(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          crossAxisSpacing: 10,
          mainAxisSpacing: 10,
        ),
        itemCount: 6,
        itemBuilder: (context, index) {
          return GestureDetector(
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => DetailPage(imageIndex: index),
                ),
              );
            },
            child: Hero(
              tag: 'image_$index',
              child: Container(
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(8),
                  image: DecorationImage(
                    image: NetworkImage('https://example.com/image$index.jpg'),
                    fit: BoxFit.cover,
                  ),
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

class DetailPage extends StatelessWidget {
  final int imageIndex;

  const DetailPage({Key? key, required this.imageIndex}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onTap: () {
          Navigator.pop(context);
        },
        child: Container(
          color: Colors.black,
          child: Center(
            child: Hero(
              tag: 'image_$imageIndex',
              child: Container(
                width: MediaQuery.of(context).size.width,
                height: MediaQuery.of(context).size.height * 0.7,
                decoration: BoxDecoration(
                  image: DecorationImage(
                    image: NetworkImage('https://example.com/image$imageIndex.jpg'),
                    fit: BoxFit.contain,
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

五、页面转场动画

1. 自定义页面转场

class CustomTransitionPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Custom Transition')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.push(
              context,
              PageRouteBuilder(
                pageBuilder: (context, animation, secondaryAnimation) => SecondPage(),
                transitionsBuilder: (context, animation, secondaryAnimation, child) {
                  const begin = Offset(1.0, 0.0);
                  const end = Offset.zero;
                  const curve = Curves.easeInOut;

                  var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));

                  return SlideTransition(
                    position: animation.drive(tween),
                    child: child,
                  );
                },
                transitionDuration: Duration(milliseconds: 500),
              ),
            );
          },
          child: Text('Go to Second Page'),
        ),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Second Page')),
      body: Center(
        child: Text('Second Page Content'),
      ),
    );
  }
}

2. 淡入淡出转场

Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => SecondPage(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      return FadeTransition(
        opacity: animation,
        child: child,
      );
    },
  ),
);

六、动画控制器高级用法

1. 动画状态监听

class AnimationListenerWidget extends StatefulWidget {
  @override
  _AnimationListenerWidgetState createState() => _AnimationListenerWidgetState();
}

class _AnimationListenerWidgetState extends State<AnimationListenerWidget> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;
  String _status = 'Ready';

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );

    _animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);

    _controller.addStatusListener((status) {
      setState(() {
        switch (status) {
          case AnimationStatus.dismissed:
            _status = 'Dismissed';
            break;
          case AnimationStatus.forward:
            _status = 'Forward';
            break;
          case AnimationStatus.reverse:
            _status = 'Reverse';
            break;
          case AnimationStatus.completed:
            _status = 'Completed';
            break;
        }
      });
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            AnimatedBuilder(
              animation: _animation,
              builder: (context, child) {
                return Transform.scale(
                  scale: _animation.value,
                  child: Container(
                    width: 200,
                    height: 200,
                    color: Colors.blue,
                  ),
                );
              },
            ),
            SizedBox(height: 20),
            Text('Animation Status: $_status'),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                if (_controller.status == AnimationStatus.completed) {
                  _controller.reverse();
                } else {
                  _controller.forward();
                }
              },
              child: Text('Toggle Animation'),
            ),
          ],
        ),
      ),
    );
  }
}

2. 动画值监听

class AnimationValueListenerWidget extends StatefulWidget {
  @override
  _AnimationValueListenerWidgetState createState() => _AnimationValueListenerWidgetState();
}

class _AnimationValueListenerWidgetState extends State<AnimationValueListenerWidget> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;
  double _currentValue = 0.0;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );

    _animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);

    _animation.addListener(() {
      setState(() {
        _currentValue = _animation.value;
      });
    });

    _controller.forward();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            AnimatedBuilder(
              animation: _animation,
              builder: (context, child) {
                return Container(
                  width: 200 * _animation.value,
                  height: 200 * _animation.value,
                  color: Colors.blue,
                );
              },
            ),
            SizedBox(height: 20),
            Text('Animation Value: ${_currentValue.toStringAsFixed(2)}'),
          ],
        ),
      ),
    );
  }
}

七、性能优化

1. 使用AnimatedBuilder

// 不好的做法:整个widget重建
class BadAnimationWidget extends StatefulWidget {
  @override
  _BadAnimationWidgetState createState() => _BadAnimationWidgetState();
}

class _BadAnimationWidgetState extends State<BadAnimationWidget> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );
    _animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
    _controller.repeat(reverse: true);
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          transform: Matrix4.rotationZ(_animation.value * 2 * 3.14159),
          child: ExpensiveWidget(), // 每次动画都会重建
        ),
      ),
    );
  }
}

// 好的做法:只重建动画部分
class GoodAnimationWidget extends StatefulWidget {
  @override
  _GoodAnimationWidgetState createState() => _GoodAnimationWidgetState();
}

class _GoodAnimationWidgetState extends State<GoodAnimationWidget> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );
    _animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
    _controller.repeat(reverse: true);
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: AnimatedBuilder(
          animation: _animation,
          builder: (context, child) {
            return Transform.rotate(
              angle: _animation.value * 2 * 3.14159,
              child: child, // 不会重建
            );
          },
          child: ExpensiveWidget(), // 只构建一次
        ),
      ),
    );
  }
}

2. 使用RepaintBoundary

class RepaintBoundaryWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: RepaintBoundary(
          child: AnimatedWidget(), // 隔离重绘
        ),
      ),
    );
  }
}

3. 避免在动画中使用setState

// 不好的做法:在动画监听器中使用setState
_animation.addListener(() {
  setState(() {
    // 更新状态
  });
});

// 好的做法:使用AnimatedBuilder
AnimatedBuilder(
  animation: _animation,
  builder: (context, child) {
    return Container(
      // 使用动画值
    );
  },
);

八、实战案例:复杂动画效果

1. 加载动画

class LoadingAnimationWidget extends StatefulWidget {
  @override
  _LoadingAnimationWidgetState createState() => _LoadingAnimationWidgetState();
}

class _LoadingAnimationWidgetState extends State<LoadingAnimationWidget> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 1),
      vsync: this,
    );
    _animation = Tween<double>(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(parent: _controller, curve: Curves.linear),
    );
    _controller.repeat();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: AnimatedBuilder(
          animation: _animation,
          builder: (context, child) {
            return Transform.rotate(
              angle: _animation.value * 2 * 3.14159,
              child: Container(
                width: 50,
                height: 50,
                child: CircularProgressIndicator(),
              ),
            );
          },
        ),
      ),
    );
  }
}

2. 脉冲动画

class PulseAnimationWidget extends StatefulWidget {
  @override
  _PulseAnimationWidgetState createState() => _PulseAnimationWidgetState();
}

class _PulseAnimationWidgetState extends State<PulseAnimationWidget> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 1),
      vsync: this,
    );
    _animation = Tween<double>(begin: 1.0, end: 1.2).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
    );
    _controller.repeat(reverse: true);
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: AnimatedBuilder(
          animation: _animation,
          builder: (context, child) {
            return Transform.scale(
              scale: _animation.value,
              child: Container(
                width: 100,
                height: 100,
                decoration: BoxDecoration(
                  color: Colors.blue,
                  borderRadius: BorderRadius.circular(50),
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}

3. 复杂交互动画

class InteractiveAnimationWidget extends StatefulWidget {
  @override
  _InteractiveAnimationWidgetState createState() => _InteractiveAnimationWidgetState();
}

class _InteractiveAnimationWidgetState extends State<InteractiveAnimationWidget> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<Offset> _animation;
  bool _isDragging = false;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    );
    _animation = Tween<Offset>(begin: Offset(0, 0), end: Offset(1, 0)).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeOut),
    );
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GestureDetector(
          onPanStart: (details) {
            setState(() => _isDragging = true);
          },
          onPanUpdate: (details) {
            final width = MediaQuery.of(context).size.width;
            final progress = (details.localPosition.dx / width).clamp(0.0, 1.0);
            _controller.value = progress;
          },
          onPanEnd: (details) {
            setState(() => _isDragging = false);
            if (_controller.value > 0.5) {
              _controller.forward();
            } else {
              _controller.reverse();
            }
          },
          child: Container(
            width: 300,
            height: 60,
            decoration: BoxDecoration(
              color: Colors.grey[200],
              borderRadius: BorderRadius.circular(30),
            ),
            child: Stack(
              children: [
                SlideTransition(
                  position: _animation,
                  child: Container(
                    width: 150,
                    height: 60,
                    decoration: BoxDecoration(
                      color: Colors.blue,
                      borderRadius: BorderRadius.circular(30),
                    ),
                  ),
                ),
                Center(
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    children: [
                      Text(
                        'Off',
                        style: TextStyle(
                          color: _controller.value > 0.5 ? Colors.grey : Colors.white,
                          fontWeight: _controller.value > 0.5 ? FontWeight.normal : FontWeight.bold,
                        ),
                      ),
                      Text(
                        'On',
                        style: TextStyle(
                          color: _controller.value > 0.5 ? Colors.white : Colors.grey,
                          fontWeight: _controller.value > 0.5 ? FontWeight.bold : FontWeight.normal,
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

九、第三方动画库

1. 使用flutter_animate

// 添加依赖
dependencies:
  flutter_animate: ^4.0.0

// 使用示例
import 'package:flutter_animate/flutter_animate.dart';

class AnimateWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          width: 200,
          height: 200,
          color: Colors.blue,
        )
            .animate()
            .fadeIn(duration: 1000.ms)
            .scale(duration: 500.ms)
            .moveX(amount: 100, duration: 1000.ms)
            .rotate(duration: 1000.ms),
      ),
    );
  }
}

2. 使用 rive

// 添加依赖
dependencies:
  rive: ^0.11.0

// 使用示例
import 'package:rive/rive.dart';

class RiveAnimationWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: RiveAnimation.asset(
          'assets/animation.riv',
          animations: ['idle', 'hover'],
        ),
      ),
    );
  }
}

十、总结

Flutter的动画系统非常强大,通过本文介绍的高级技巧,你可以:

  1. 掌握动画控制器:创建和管理动画控制器
  2. 实现高级补间动画:自定义曲线、多动画组合、动画序列
  3. 使用物理动画:弹簧动画、重力动画
  4. 创建Hero动画:实现元素在页面间的平滑过渡
  5. 自定义页面转场:创建独特的页面切换效果
  6. 优化动画性能:使用AnimatedBuilder、RepaintBoundary
  7. 使用第三方库:flutter_animate、rive等

通过综合应用这些技巧,你将能够创建出流畅、自然的动画效果,提升应用的用户体验和视觉吸引力。

输入输出示例

输入输出示例

场景:创建一个带有动画效果的登录按钮

代码

class AnimatedLoginButton extends StatefulWidget {
  @override
  _AnimatedLoginButtonState createState() => _AnimatedLoginButtonState();
}

class _AnimatedLoginButtonState extends State<AnimatedLoginButton> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _scaleAnimation;
  late Animation<double> _opacityAnimation;
  bool _isLoading = false;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    );
    _scaleAnimation = Tween<double>(begin: 1.0, end: 0.9).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeOut),
    );
    _opacityAnimation = Tween<double>(begin: 1.0, end: 0.7).animate(
      CurvedAnimation(parent: _controller, curve: Curves.easeOut),
    );
  }

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

  Future<void> _login() async {
    setState(() => _isLoading = true);
    _controller.forward();

    // 模拟登录过程
    await Future.delayed(Duration(2000));

    _controller.reverse();
    setState(() => _isLoading = false);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GestureDetector(
          onTap: _isLoading ? null : _login,
          onTapDown: (_) => _controller.forward(),
          onTapUp: (_) => _controller.reverse(),
          onTapCancel: () => _controller.reverse(),
          child: FadeTransition(
            opacity: _opacityAnimation,
            child: ScaleTransition(
              scale: _scaleAnimation,
              child: Container(
                width: 200,
                height: 50,
                decoration: BoxDecoration(
                  color: Colors.blue,
                  borderRadius: BorderRadius.circular(25),
                ),
                child: Center(
                  child: _isLoading
                      ? SizedBox(
                          width: 20,
                          height: 20,
                          child: CircularProgressIndicator(
                            strokeWidth: 2,
                            valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
                          ),
                        )
                      : Text(
                          'Login',
                          style: TextStyle(
                            color: Colors.white,
                            fontSize: 16,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

输出:一个带有按下动画和加载状态的登录按钮,按下时会缩小并降低透明度,加载时显示圆形进度指示器。

代码优化建议

  1. 使用Animation而非AnimationController直接控制:AnimationController负责动画的控制,Animation负责动画值的计算

  2. 合理使用Curve:选择合适的曲线可以使动画更加自然

  3. 使用AnimatedBuilder:避免不必要的widget重建

  4. 使用RepaintBoundary:隔离动画重绘区域,提高性能

  5. 避免在动画监听器中使用setState:使用AnimatedBuilder代替

  6. 合理设置动画 duration:根据动画类型和场景设置合适的持续时间

  7. 使用物理动画:对于模拟物理效果的动画,使用SpringSimulation等物理模拟

  8. 优化Hero动画:确保源和目标Hero的tag一致,避免冲突

  9. 使用第三方库:对于复杂动画,考虑使用flutter_animate等库

  10. 测试不同设备:在不同性能的设备上测试动画,确保流畅运行

通过遵循这些最佳实践,你将能够创建出更加流畅、自然的动画效果,提升应用的用户体验。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐