Flutter如何根据Scroll Controller偏移量平滑动态调整Container尺寸?
解决方案:基于滚动偏移量平滑调整Container尺寸
要实现Container在400到150之间的平滑尺寸过渡,核心是用线性插值关联滚动偏移量与尺寸值,避免之前公式中除法、取模这类会导致跳变的操作。以下是具体实现方案:
核心思路
- 定义滚动阈值:设定一个滚动偏移量上限(比如300),当滚动到这个值时,Container尺寸从400完全过渡到150;超出阈值后保持150不变。
- 实时监听滚动:通过
ScrollController的addListener捕获所有滚动事件(包括手势、代码触发的滚动)。 - 平滑插值计算:用Flutter内置的
lerpDouble函数实现尺寸的线性过渡,确保无跳变。
基础实现(setState)
import 'package:flutter/material.dart'; class ResizingContainer extends StatefulWidget { const ResizingContainer({super.key}); @override State<ResizingContainer> createState() => _ResizingContainerState(); } class _ResizingContainerState extends State<ResizingContainer> { final ScrollController _scrollController = ScrollController(); double _containerSize = 400.0; // 滚动到该偏移量时,尺寸完成从400到150的过渡,可按需调整 static const _scrollThreshold = 300.0; @override void initState() { super.initState(); _scrollController.addListener(_updateSize); } void _updateSize() { // 限制偏移量在0到阈值之间,避免尺寸超出范围 final clampedOffset = _scrollController.offset.clamp(0.0, _scrollThreshold); // 计算过渡进度(0到1),再通过线性插值得到当前尺寸 final newSize = lerpDouble(400.0, 150.0, clampedOffset / _scrollThreshold)!; if (_containerSize != newSize) { setState(() => _containerSize = newSize); } } @override void dispose() { _scrollController.removeListener(_updateSize); _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: ListView( controller: _scrollController, children: [ Container( height: _containerSize, width: _containerSize, color: Colors.blueAccent, alignment: Alignment.center, child: Text('当前尺寸:${_containerSize.toStringAsFixed(1)}'), ), // 填充足够内容让列表可滚动 for (int i = 0; i < 20; i++) Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Text('列表项 $i'), ), ], ), ); } }
优化实现(AnimatedBuilder)
如果列表内容较多,用AnimatedBuilder可以只重绘Container部分,避免整个ListView重绘,提升性能:
import 'package:flutter/material.dart'; class ResizingContainer extends StatefulWidget { const ResizingContainer({super.key}); @override State<ResizingContainer> createState() => _ResizingContainerState(); } class _ResizingContainerState extends State<ResizingContainer> { final ScrollController _scrollController = ScrollController(); static const _scrollThreshold = 300.0; @override void initState() { super.initState(); _scrollController.addListener(() => setState(() {})); } @override void dispose() { _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: ListView( controller: _scrollController, children: [ AnimatedBuilder( animation: _scrollController, builder: (context, child) { final clampedOffset = _scrollController.offset.clamp(0.0, _scrollThreshold); final size = lerpDouble(400.0, 150.0, clampedOffset / _scrollThreshold)!; return Container( height: size, width: size, color: Colors.blueAccent, alignment: Alignment.center, child: Text('当前尺寸:${size.toStringAsFixed(1)}'), ); }, ), for (int i = 0; i < 20; i++) Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Text('列表项 $i'), ), ], ), ); } }
可选:非线性过渡
如果需要更自然的过渡效果(比如先慢后快),可以用曲线函数修改过渡进度:
final progress = Curves.easeInOut.transform(clampedOffset / _scrollThreshold); final size = lerpDouble(400.0, 150.0, progress)!;
内容的提问来源于stack exchange,提问作者نعمان منذر محمود الجميلي
相关产品推荐
相关产品推荐

