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

Flutter可旋转ListView开发需求咨询:滚动轮切换显示隐藏项

实现可滚动替换项的ListView方案

核心思路

通过ScrollController监听滚动位置,动态调整显示的起始项索引,始终保持列表只显示12个项。当滚动触发阈值时,自动更新起始索引并修正滚动位置,让用户感觉是在同一滚轮上滚动替换前后项。

代码实现

import 'package:flutter/material.dart';

class RotatingListView extends StatefulWidget {
  final int totalItems;
  final Widget Function(int index) itemBuilder;

  const RotatingListView({
    super.key,
    required this.totalItems,
    required this.itemBuilder,
  });

  @override
  State<RotatingListView> createState() => _RotatingListViewState();
}

class _RotatingListViewState extends State<RotatingListView> {
  final ScrollController _scrollController = ScrollController();
  int _startIndex = 0;
  // 固定子项高度,可根据实际需求调整
  final double _itemHeight = 60.0;
  // 滚动触发项替换的阈值:超过项高度一半时触发
  final double _triggerThreshold = 30.0;
  // 标记是否正在调整滚动位置,避免重复触发
  bool _isAdjusting = false;

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(_onScroll);
  }

  void _onScroll() {
    if (_isAdjusting) return;

    final scrollOffset = _scrollController.offset;
    // 向下滚动:判断是否需要加载下一组项
    if (scrollOffset > _triggerThreshold) {
      if (_startIndex + 12 < widget.totalItems) {
        setState(() => _startIndex += 1);
        _adjustScrollPosition();
      } else {
        // 已到最后一组,重置滚动位置到顶部
        _scrollController.jumpTo(0);
      }
    }
    // 向上滚动:判断是否需要加载上一组项
    else if (scrollOffset < -_triggerThreshold) {
      if (_startIndex > 0) {
        setState(() => _startIndex -= 1);
        _adjustScrollPosition();
      } else {
        // 已到第一组,重置滚动位置到底部
        _scrollController.jumpTo(_itemHeight * 11);
      }
    }
  }

  void _adjustScrollPosition() async {
    _isAdjusting = true;
    // 等待帧更新避免状态冲突
    await Future.delayed(const Duration(milliseconds: 50));
    // 将滚动位置重置到中间,保持视觉连贯性
    _scrollController.jumpTo(_itemHeight * 5);
    _isAdjusting = false;
  }

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

  @override
  Widget build(BuildContext context) {
    // 计算当前显示的项数,不足12个时显示全部剩余项
    final displayCount = widget.totalItems - _startIndex > 12
        ? 12
        : widget.totalItems - _startIndex;

    return ListView.builder(
      controller: _scrollController,
      physics: const BouncingScrollPhysics(),
      itemCount: displayCount,
      itemBuilder: (context, index) {
        final actualIndex = _startIndex + index;
        return widget.itemBuilder(actualIndex);
      },
    );
  }
}

// 使用示例
void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text("可旋转ListView")),
        body: RotatingListView(
          totalItems: 15, // 测试总项数15
          itemBuilder: (index) {
            return Container(
              height: 60,
              alignment: Alignment.center,
              child: Text("第 ${index+1} 项"),
              decoration: BoxDecoration(
                border: Border(bottom: BorderSide(color: Colors.grey.shade300)),
              ),
            );
          },
        ),
      ),
    );
  }
}

关键说明

  • 固定子项高度:确保滚动阈值计算准确,若需自适应高度,可通过GlobalKey获取子项实际高度,但会增加复杂度。
  • 滚动阈值调整:_triggerThreshold设为项高度一半,可根据交互习惯修改触发时机。
  • 边界处理:滚动到首尾组时重置滚动位置,避免出现空白区域。
  • 重复触发防护:用_isAdjusting标记状态,防止调整滚动位置时重复触发监听事件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 08:34:52