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

如何在Flutter中实现类似WhatsApp的动画弹出菜单效果

实现WhatsApp风格消息气泡展开/折叠动画

核心实现思路

  • 用AnimatedContainer处理气泡的尺寸平滑过渡,这是Flutter中实现容器类动画最便捷的组件
  • 结合AnimatedOpacity控制联系人名称的显示/隐藏透明度
  • 通过AnimationController配合Transform.scale、Transform.translate实现图标的缩放和位置偏移
  • 用GestureDetector绑定点击事件,切换展开/折叠状态

完整可运行代码示例

import 'package:flutter/material.dart';

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

  @override
  State<WhatsAppMessageBubble> createState() => _WhatsAppMessageBubbleState();
}

class _WhatsAppMessageBubbleState extends State<WhatsAppMessageBubble> with SingleTickerProviderStateMixin {
  bool _isExpanded = false;
  late AnimationController _animController;
  late Animation<double> _iconScaleAnim;
  late Animation<double> _nameSlideAnim;

  @override
  void initState() {
    super.initState();
    _animController = AnimationController(
      duration: const Duration(milliseconds: 250),
      vsync: this,
    );
    // 图标缩放动画:展开时缩小到0.8倍
    _iconScaleAnim = Tween<double>(begin: 1.0, end: 0.8).animate(
      CurvedAnimation(parent: _animController, curve: Curves.easeInOut),
    );
    // 名称滑动动画:从左侧偏移位置滑入
    _nameSlideAnim = Tween<double>(begin: -20, end: 0).animate(
      CurvedAnimation(parent: _animController, curve: Curves.easeInOut),
    );
  }

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

  void _toggleExpandState() {
    setState(() => _isExpanded = !_isExpanded);
    _isExpanded ? _animController.forward() : _animController.reverse();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _toggleExpandState,
      child: Container(
        margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 250),
          curve: Curves.easeInOut,
          width: _isExpanded ? double.infinity : 60,
          height: 60,
          decoration: BoxDecoration(
            color: Colors.grey[200],
            borderRadius: BorderRadius.circular(30),
          ),
          padding: const EdgeInsets.symmetric(horizontal: 12),
          child: Row(
            children: [
              Transform.scale(
                scale: _iconScaleAnim.value,
                child: const CircleAvatar(
                  backgroundImage: NetworkImage('https://picsum.photos/200'),
                  radius: 20,
                ),
              ),
              if (_isExpanded)
                Transform.translate(
                  offset: Offset(_nameSlideAnim.value, 0),
                  child: AnimatedOpacity(
                    opacity: _isExpanded ? 1.0 : 0.0,
                    duration: const Duration(milliseconds: 200),
                    child: const Padding(
                      padding: EdgeInsets.only(left: 12),
                      child: Text(
                        '联系人名称',
                        style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
                      ),
                    ),
                  ),
                ),
              const Spacer(),
              if (_isExpanded) const Icon(Icons.keyboard_voice_outlined),
            ],
          ),
        ),
      ),
    );
  }
}

// 调用示例
void main() {
  runApp(const MaterialApp(
    home: Scaffold(
      backgroundColor: Colors.white,
      body: Center(child: WhatsAppMessageBubble()),
    ),
  ));
}

关键细节说明

  • 气泡尺寸过渡:AnimatedContainer自动处理宽度从固定值到全屏的平滑变化,Curves.easeInOut保证动画自然不生硬
  • 图标缩放:通过动画控制器驱动缩放值,展开时图标缩小,贴合WhatsApp的视觉逻辑
  • 名称入场动画:组合Transform.translate和AnimatedOpacity,让名称从左侧滑入同时渐显,避免突兀出现
  • 状态同步:点击切换状态时,同步控制动画控制器的正向/反向播放,保证动画与状态一致

扩展优化方向

  • 可添加消息预览文本的展开动画,丰富气泡内容
  • 自定义Curves参数,调整动画速率,更贴近原版效果
  • 在列表中使用时,可配合AutomaticKeepAliveClientMixin保持组件状态,避免滚动时重置

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 04:32:35