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

为ListWheelScrollView的进入与退出子项添加平滑淡入淡出过渡效果

ListWheelScrollView的进入与退出子项添加平滑淡入淡出过渡效果

我懂你现在的困扰——用ListWheelScrollView的时候,子项滚出容器范围就突然消失,完全没过渡,用户体验确实拉胯。之前ChatGPT给的代码思路是对的,但计算过渡动画的逻辑没踩对点子,所以还是没解决问题。我给你调整一下代码,实现真正平滑的进入/退出过渡效果,包括淡入淡出和缩放动画。

修正后的完整可运行代码

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'List',
      theme: ThemeData(primarySwatch: Colors.blue),
      debugShowCheckedModeBanner: false,
      home: const ListWheelWithSmoothTransition(),
    );
  }
}

class ListWheelWithSmoothTransition extends StatefulWidget {
  const ListWheelWithSmoothTransition({Key? key}) : super(key: key);

  @override
  _ListWheelWithSmoothTransitionState createState() => _ListWheelWithSmoothTransitionState();
}

class _ListWheelWithSmoothTransitionState extends State<ListWheelWithSmoothTransition> {
  final FixedExtentScrollController _scrollController = FixedExtentScrollController();
  final double _itemExtent = 100.0;
  final int _itemCount = 5;
  // 可视区域内最多显示的子项数量(根据容器高度计算)
  final int _visibleItemCount = 5;

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SizedBox(
          height: _itemExtent * _visibleItemCount,
          child: ListWheelScrollView(
            controller: _scrollController,
            itemExtent: _itemExtent,
            physics: const FixedExtentScrollPhysics(),
            onSelectedItemChanged: (value) {},
            children: List.generate(_itemCount, (index) {
              return AnimatedBuilder(
                animation: _scrollController,
                builder: (context, child) {
                  // 计算当前子项相对于可视区域的偏移量
                  final double itemOffset = _scrollController.offset - (index * _itemExtent);
                  // 计算子项在可视区域内的位置比例(-1到1之间,0表示在中心)
                  final double positionRatio = (itemOffset / _itemExtent).clamp(-1.0, 1.0);
                  // 根据位置比例计算透明度和缩放值
                  final double opacity = 1.0 - positionRatio.abs();
                  final double scale = 1.0 - (positionRatio.abs() * 0.3); // 最多缩小30%

                  return Opacity(
                    opacity: opacity.clamp(0.0, 1.0),
                    child: Transform.scale(
                      scale: scale.clamp(0.7, 1.0),
                      child: child,
                    ),
                  );
                },
                child: Container(
                  color: Colors.green,
                  height: 50,
                  width: 50,
                  child: Center(child: Text('Item $index', style: const TextStyle(color: Colors.white))),
                ),
              );
            }),
          ),
        ),
      ),
    );
  }
}

关键逻辑说明

  1. 滚动控制器的作用:用FixedExtentScrollController实时获取滚动偏移量,这是实现过渡动画的核心依据
  2. 子项位置计算:对每个子项单独计算它相对于可视区域的偏移量,转换成positionRatio(范围-1到1):
    • 当子项处于可视区域中心时,positionRatio为0,此时透明度为1、缩放比例为1
    • 当子项逐渐滚出可视区域时,positionRatio趋近于±1,透明度逐渐降到0,缩放比例逐渐缩小到0.7
  3. 动画触发:通过AnimatedBuilder监听滚动控制器的动画状态,实时更新每个子项的透明度和缩放值,滚动时就能自然触发平滑过渡

为什么之前的代码无效?

之前ChatGPT的代码用scrollOffset % itemHeight计算过渡值,这个逻辑没有关联到子项的索引,导致所有子项的过渡计算是统一的,无法反映每个子项的实际进入/退出状态,所以达不到平滑过渡的效果。

备注:内容来源于stack exchange,提问作者Ramji

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.21 15:08:10