Flutter:根据ListView内可见性更新Widget,实现滚动计时器效果
实现垂直滚动计时器(带动态模糊效果)
核心思路
通过ScrollController监听ListView滚动位置,计算每个列表项与视口中心的偏移量,根据偏移比例动态调整项的透明度、模糊度和缩放比例,实现中心内容清晰、边缘内容渐变模糊的视觉效果。
具体实现步骤
1. 初始化控制器与状态变量
late ScrollController _scrollController; double _viewportHeight = 0; // 记录ListView视口高度 final double _itemHeight = 60; // 单个数字项的固定高度
在initState中完成控制器初始化并添加滚动监听:
@override void initState() { super.initState(); _scrollController = ScrollController() ..addListener(() { setState(() {}); // 滚动时触发UI重绘 }); }
2. 计算列表项的视觉参数
先计算列表项中心与视口中心的垂直偏移:
double _calculateCenterOffset(int index) { final itemTop = index * _itemHeight - _scrollController.offset; final viewportCenterY = _viewportHeight / 2; // 返回项中心与视口中心的距离绝对值 return (itemTop + _itemHeight / 2 - viewportCenterY).abs(); }
根据偏移距离计算样式参数:
Map<String, double> _getItemVisualStyle(int index) { final offset = _calculateCenterOffset(index); const maxEffectRange = 80.0; // 超过此距离的项完全模糊 if (offset > maxEffectRange) { return { 'opacity': 0.3, 'blur': 4.0, 'scale': 0.8 }; } final ratio = offset / maxEffectRange; return { 'opacity': 1 - ratio * 0.7, 'blur': ratio * 4.0, 'scale': 1 - ratio * 0.2 }; }
3. 构建带动态效果的ListView
用LayoutBuilder获取视口高度,再渲染列表项:
@override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { _viewportHeight = constraints.maxHeight; return ListView.builder( controller: _scrollController, itemCount: 60, // 计时器数字范围0-59 itemExtent: _itemHeight, itemBuilder: (context, index) { final style = _getItemVisualStyle(index); return Center( child: BackdropFilter( filter: ImageFilter.blur(sigmaX: style['blur']!, sigmaY: style['blur']!), child: Opacity( opacity: style['opacity']!, child: Transform.scale( scale: style['scale']!, child: Text( '$index', style: const TextStyle( fontSize: 32, fontWeight: FontWeight.bold, ), ), ), ), ), ); }, ); }, ); }
4. 优化:滚动结束后自动居中选中项
添加自动对齐逻辑,让滚动停止时中心项自动对齐视口中心:
void _autoAlignCenterItem() { final currentOffset = _scrollController.offset; final closestIndex = (currentOffset + _viewportHeight/2) ~/ _itemHeight; final targetOffset = closestIndex * _itemHeight - (_viewportHeight/2 - _itemHeight/2); _scrollController.animateTo( targetOffset, duration: const Duration(milliseconds: 200), curve: Curves.easeOut, ); }
修改滚动监听,在滚动停止时触发对齐:
_scrollController.addListener(() { if (!_scrollController.position.isScrollingNotifier.value) { _autoAlignCenterItem(); } setState(() {}); });
关键注意事项
BackdropFilter需要配合明确的组件边界(比如父容器设置clipBehavior: Clip.hardEdge),避免模糊效果溢出- 可通过调整
maxEffectRange和样式比例参数,适配不同的视觉需求 - 若列表项高度不固定,需修改
_calculateCenterOffset中的高度计算逻辑
内容的提问来源于stack exchange,提问作者Rehan
相关产品推荐
相关产品推荐

