Flutter实现GridView网格方块间跳跃动画,Hero/AnimatedContainer无效如何解决
问题根因
你当前的实现存在3个核心问题导致没有动画效果:
Hero动画仅适用于路由页面切换的共享元素场景,同页面内的元素位移不会触发Hero过渡AnimatedContainer的所有属性都是固定值,没有随状态变化的变量,自然无法产生过渡动画- 圆点仅在匹配
location == index的网格项中渲染,位置变化时旧位置的圆点直接销毁、新位置直接创建,只有闪烁没有过渡
实现思路
不要把圆点放在每个网格项内部,而是把圆点作为独立的动画层放在GridView的上层,通过动画修改圆点的偏移量实现跳跃效果,方案步骤如下:
- 预先计算每个网格项的尺寸:用
crossAxisCount算出网格宽度,SliverGridDelegateWithFixedCrossAxisCount的默认宽高比是1,所以网格宽高相同 - 用
Stack包裹GridView和圆点,圆点用AnimatedPositioned包裹,位移属性随location变化自动触发过渡动画 - 点击按钮修改
location时,只需要更新圆点的top和left偏移量即可
完整可运行代码
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatefulWidget { const MyApp({super.key}); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { int location = 0; // 网格列数 final int crossAxisCount = 3; // 圆点尺寸 final double dotSize = 7; @override Widget build(BuildContext context) { return Scaffold( body: Column(children: [ Expanded( child: LayoutBuilder( // 用LayoutBuilder获取父容器可用宽度,计算网格尺寸 builder: (context, constraints) { // 每个网格的宽度 = 总宽度 / 列数 final double gridItemWidth = constraints.maxWidth / crossAxisCount; // 网格默认宽高比1,所以高度和宽度相同 final double gridItemHeight = gridItemWidth; // 计算当前圆点所在的行和列 final int col = location % crossAxisCount; final int row = location ~/ crossAxisCount; // 计算圆点的偏移量:网格中心位置减去圆点自身一半尺寸,实现居中显示 final double left = col * gridItemWidth + (gridItemWidth - dotSize)/2; final double top = row * gridItemHeight + (gridItemHeight - dotSize)/2; return Stack( children: [ // 底层网格 GridView.builder( itemCount: 10, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: crossAxisCount), itemBuilder: (context, index) { return Container( decoration: BoxDecoration( border: Border.all(color: Colors.black, width: 0.1), ), ); }, ), // 上层动画圆点 AnimatedPositioned( duration: const Duration(milliseconds: 400), curve: Curves.bounceOut, // 跳跃效果的曲线,换成linear就是平滑移动 left: left, top: top, child: Container( width: dotSize, height: dotSize, decoration: const BoxDecoration( color: Colors.amber, shape: BoxShape.circle, ), ), ) ], ); } ), ), ElevatedButton( onPressed: () { setState(() { // 超过总数量就重置到开头,可根据需求修改 location = (location + 2) % 10; }); }, child: const Text('Jump'), ), ]), ); } }
效果调整说明
- 要修改跳跃的动效,只要替换
AnimatedPositioned的curve参数即可,比如用Curves.easeInOut就是平滑移动,Curves.bounceOut就是带弹跳的跳跃效果 - 如果网格项有自定义的padding、间距,只要在计算
left和top偏移量的时候加上对应间距即可
内容的提问来源于stack exchange,提问作者Patur
相关产品推荐
相关产品推荐

