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

如何使用Flutter实现该括号式UI动效?附演示动图

Flutter 实现滚动触发式UI动效

UI动效演示

你推测用ListView结合滚动位置触发动画的思路是对的,这类滚动过程中动态变换组件状态的效果,核心是通过ScrollController监听滚动偏移,结合子项在视口中的位置计算动画进度,再将进度映射到组件的变换属性上(比如缩放、位移、透明度)。

实现步骤:

  • 初始化ScrollController并监听滚动事件,实时获取滚动偏移量
  • 遍历列表项,根据每个子项的位置计算其进入视口的比例(动画进度)
  • 基于动画进度,动态调整子组件的外观参数,配合AnimatedContainer或AnimatedBuilder实现平滑过渡

代码示例:

import 'package:flutter/material.dart';

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

  @override
  State<ScrollTriggerAnimPage> createState() => _ScrollTriggerAnimPageState();
}

class _ScrollTriggerAnimPageState extends State<ScrollTriggerAnimPage> {
  final ScrollController _scrollCtrl = ScrollController();
  final Map<int, double> _itemProgress = {};
  final double _itemHeight = 200;

  @override
  void initState() {
    super.initState();
    _scrollCtrl.addListener(_updateItemProgress);
  }

  void _updateItemProgress() {
    setState(() {
      final viewportHeight = MediaQuery.of(context).size.height;
      for (int i = 0; i < 10; i++) {
        // 计算子项顶部相对于视口的位置
        final itemTop = _scrollCtrl.offset + i * _itemHeight;
        // 计算动画进度:0(完全在视口下方)→1(完全进入视口)
        double progress = (viewportHeight - itemTop) / viewportHeight;
        progress = progress.clamp(0.0, 1.0);
        _itemProgress[i] = progress;
      }
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView.builder(
        controller: _scrollCtrl,
        padding: const EdgeInsets.symmetric(vertical: 16),
        itemCount: 10,
        itemBuilder: (ctx, index) {
          final progress = _itemProgress[index] ?? 0.0;
          return AnimatedContainer(
            duration: const Duration(milliseconds: 120),
            height: _itemHeight,
            margin: EdgeInsets.symmetric(
              horizontal: 16 + (1 - progress) * 20,
              vertical: 8,
            ),
            transform: Matrix4.identity()
              ..scale(0.75 + progress * 0.25)
              ..translate(0, (1 - progress) * 30),
            child: Container(
              decoration: BoxDecoration(
                color: Colors.primaries[index % Colors.primaries.length],
                borderRadius: BorderRadius.circular(16),
                boxShadow: [
                  BoxShadow(
                    color: Colors.black.withOpacity(0.2 * progress),
                    blurRadius: 8 * progress,
                    offset: Offset(0, 4 * progress),
                  )
                ],
              ),
              child: Center(
                child: Text(
                  "卡片 ${index + 1}",
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 22,
                    fontWeight: FontWeight.w600,
                  ),
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

自定义调整:

你可以根据动图的具体效果,修改动画参数:

  • 调整scale的范围实现不同的缩放幅度
  • 修改translate的数值控制位移距离
  • 添加opacity属性实现淡入淡出效果
  • 调整boxShadow参数实现阴影的动态变化

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 14:32:06