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

如何实现Flutter ElevatedButton悬停时的颜色滑入滑出过渡动画

实现ElevatedButton悬停时颜色滑入滑出过渡

直接嵌套InkWell和ElevatedButton容易和按钮自身的交互状态冲突,推荐通过自定义StatefulWidget结合Stack与SlideTransition来实现滑入效果,以下是可行方案:

核心思路

  • 用MouseRegion监听鼠标悬停状态,替代嵌套InkWell
  • 通过AnimationController和Tween控制滑入动画的进度
  • 用Stack将按钮本体和滑入颜色层叠加,通过SlideTransition控制颜色层的位置

完整代码示例

import 'package:flutter/material.dart';

class HoverSlideButton extends StatefulWidget {
  final String text;
  final VoidCallback onPressed;
  final Color defaultColor;
  final Color hoverColor;
  final Duration animationDuration;

  const HoverSlideButton({
    super.key,
    required this.text,
    required this.onPressed,
    this.defaultColor = Colors.blue,
    this.hoverColor = Colors.blueAccent,
    this.animationDuration = const Duration(milliseconds: 300),
  });

  @override
  State<HoverSlideButton> createState() => _HoverSlideButtonState();
}

class _HoverSlideButtonState extends State<HoverSlideButton>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<Offset> _slideAnimation;
  bool _isHovered = false;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: widget.animationDuration,
    );
    _slideAnimation = Tween<Offset>(
      begin: const Offset(-1, 0), // 从左侧滑入,可改为(1,0)从右侧滑入
      end: Offset.zero,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    ));
  }

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

  void _handleHover(bool isHovered) {
    setState(() => _isHovered = isHovered);
    isHovered ? _controller.forward() : _controller.reverse();
  }

  @override
  Widget build(BuildContext context) {
    return MouseRegion(
      onEnter: (_) => _handleHover(true),
      onExit: (_) => _handleHover(false),
      child: Stack(
        children: [
          ElevatedButton(
            onPressed: widget.onPressed,
            style: ElevatedButton.styleFrom(
              backgroundColor: widget.defaultColor,
              foregroundColor: Colors.white,
              padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12),
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(8),
              ),
            ),
            child: Text(widget.text),
          ),
          SlideTransition(
            position: _slideAnimation,
            child: ElevatedButton(
              onPressed: widget.onPressed,
              style: ElevatedButton.styleFrom(
                backgroundColor: widget.hoverColor,
                foregroundColor: Colors.white,
                padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 12),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(8),
                ),
              ),
              child: Text(widget.text),
            ),
          ),
        ],
      ),
    );
  }
}

// 使用示例
class DemoPage extends StatelessWidget {
  const DemoPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: HoverSlideButton(
          text: 'Hover Me',
          onPressed: () {},
          defaultColor: Colors.grey[700]!,
          hoverColor: Colors.orange,
        ),
      ),
    );
  }
}

关键说明

  • MouseRegion直接监听按钮区域的悬停状态,避免和ElevatedButton内部的交互逻辑冲突
  • 修改Tween<Offset>的begin值可自定义滑入方向:比如Offset(0,-1)是从顶部滑入
  • 若要实现颜色渐变覆盖效果,可将上层按钮替换为ColoredBox并设置blendMode调整混合样式

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 07:42:23