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

Flutter如何根据Scroll Controller偏移量平滑动态调整Container尺寸?

解决方案:基于滚动偏移量平滑调整Container尺寸

要实现Container在400到150之间的平滑尺寸过渡,核心是用线性插值关联滚动偏移量与尺寸值,避免之前公式中除法、取模这类会导致跳变的操作。以下是具体实现方案:

核心思路

  1. 定义滚动阈值:设定一个滚动偏移量上限(比如300),当滚动到这个值时,Container尺寸从400完全过渡到150;超出阈值后保持150不变。
  2. 实时监听滚动:通过ScrollController的addListener捕获所有滚动事件(包括手势、代码触发的滚动)。
  3. 平滑插值计算:用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,提问作者نعمان منذر محمود الجميلي

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 14:40:48