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

Flutter实现滚动时可滚动Widget互相填充空间的动画效果

Flutter双列表联动滚动实现

需求说明

  • 顶部紫色SizedBox保持固定位置,不随滚动变化
  • 滚动底部列表(BottomList)时,顶部列表(TopList)收缩至最小高度,底部列表上移占据顶部列表的空间;当顶部列表到达最小高度后,底部列表正常滚动
  • 滚动顶部列表时,顶部列表恢复至初始设定高度,底部列表回到原位置

实现代码

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late final ScrollController _topListController;
  late final ScrollController _bottomListController;

  late double _topListMaxHeight;
  late double _topListMinHeight;
  late double _currentTopListHeight;

  @override
  void initState() {
    super.initState();
    _topListController = ScrollController()..addListener(_onTopListScroll);
    _bottomListController = ScrollController()..addListener(_onBottomListScroll);
    
    // 计算顶部列表的最大高度和最小高度
    final screenHeight = MediaQuery.of(context).size.height;
    _topListMaxHeight = screenHeight / 2 - screenHeight / 5 - kToolbarHeight;
    _topListMinHeight = 80; // 自定义最小高度
    _currentTopListHeight = _topListMaxHeight;
  }

  void _onBottomListScroll() {
    // 底部列表向下滚动时,收缩顶部列表
    if (_bottomListController.position.userScrollDirection == ScrollDirection.reverse) {
      if (_currentTopListHeight > _topListMinHeight) {
        setState(() {
          _currentTopListHeight = _topListMinHeight;
        });
        // 重置顶部列表滚动位置,避免滚动冲突
        _topListController.jumpTo(0);
      }
    }
  }

  void _onTopListScroll() {
    // 顶部列表向上滚动时,恢复最大高度
    if (_topListController.position.userScrollDirection == ScrollDirection.forward) {
      if (_currentTopListHeight < _topListMaxHeight) {
        setState(() {
          _currentTopListHeight = _topListMaxHeight;
        });
        // 重置底部列表滚动位置
        _bottomListController.jumpTo(0);
      }
    }
  }

  @override
  void dispose() {
    _topListController.dispose();
    _bottomListController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('two scrolls')),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 固定位置的紫色SizedBox
          SizedBox(
            height: MediaQuery.of(context).size.height / 5,
            child: Container(
              color: Colors.purple,
            ),
          ),
          // 可收缩的顶部列表
          AnimatedContainer(
            duration: const Duration(milliseconds: 300),
            height: _currentTopListHeight,
            child: ListView(
              controller: _topListController,
              children: [
                Container(width: 160, height: 160, color: Colors.red),
                Container(width: 160, height: 160, color: Colors.blue),
                Container(width: 160, height: 160, color: Colors.red),
                Container(width: 160, height: 160, color: Colors.blue),
                Container(width: 160, height: 160, color: Colors.red),
              ],
            ),
          ),
          const Divider(height: 16, thickness: 0.5, color: Colors.black),
          // 可上移的底部列表
          Expanded(
            child: ListView(
              controller: _bottomListController,
              children: [
                Container(width: 160, height: 160, color: Colors.red),
                Container(width: 160, height: 160, color: Colors.blue),
                Container(width: 160, height: 160, color: Colors.red),
                Container(width: 160, height: 160, color: Colors.blue),
                Container(width: 160, height: 160, color: Colors.red),
              ],
            ),
          )
        ],
      ),
    );
  }
}

实现要点

  • 使用ScrollController监听两个列表的滚动方向,判断触发收缩/恢复操作的时机
  • 用AnimatedContainer实现顶部列表高度变化的平滑过渡
  • 在触发高度变化时,重置对应列表的滚动位置,避免滚动状态冲突
  • 可通过修改_topListMinHeight自定义顶部列表的最小收缩高度

内容的提问来源于stack exchange,提问作者vladli

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 13:01:09