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

无状态组件中ScrollablePositionedList结合Navigator跳转致应用冻结的解决

解决ScrollablePositionedList结合Navigator.push时应用冻结的问题

问题场景

原本使用ListView配合Navigator的push/pop浏览文档章节,为实现页面初始滚动到特定项,替换为ScrollablePositionedList(SPL)后,第一个路由页面正常,但调用Navigator.push时应用冻结且无异常抛出。

问题原因

代码中使用了全局唯一的ItemScrollController,但每个ScrollablePositionedList实例需要独立的控制器。当push新路由创建第二个SPL时,两个SPL共用同一个控制器,导致内部状态冲突,引发应用冻结。

解决方案

  • 移除全局的ItemScrollController,为每个MyScroll组件创建独立的控制器实例
  • 若需手动触发滚动操作,可通过组件传递控制器的方式实现

修正后的代码

import 'package:flutter/material.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';

List<String> data = [for(var i=0; i<100; i++) '-- Item $i --'];

void main() => runApp(MaterialApp(title: 'Test', home: HomePage()));

class HomePage extends StatelessWidget {
  const HomePage({super.key});
  
  // 为首页的SPL创建独立控制器
  final ItemScrollController _homeScrollController = ItemScrollController();

  @override
  Widget build(BuildContext context) {
    var listAll = [for(var i=0; i<data.length; i++) i];  // data数组的索引列表
    var listPartim = [for(var i=20; i<40; i++) i]; // data数组的部分索引列表
    int hot = 0;  // 需要高亮的项索引
    return Scaffold(
      appBar: AppBar(
        title: Row(children: [
          TextButton(
            onPressed: () => _homeScrollController.scrollTo(
              index: 20, 
              duration: const Duration(seconds: 1), 
              curve: Curves.easeInOutCubic
            ), 
            child: const Text('滚动到第20项(仅ScrollablePositionedList可用)')
          ),
          TextButton(
            onPressed: () { 
              Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => SecondPage(list: listPartim, hot: hot, title: '第二页'))
              );
            },
            child: const Text('跳转到第二页')
          ),
        ],),
      ),
      body: MyScroll(listAll, 25, scrollController: _homeScrollController),  // 初始滚动到第25项
    );
  }
}

class SecondPage extends StatelessWidget {
  final List<int> list; // data数组的索引列表
  final int hot;        // 需要高亮的项索引
  final String title;
  const SecondPage({super.key, required this.list, required this.hot, required this.title});

  @override
  Widget build(BuildContext context) {
    // 为第二页的SPL创建独立控制器
    final ItemScrollController _pageScrollController = ItemScrollController();
    return Scaffold(
      appBar: AppBar(title: Text(title),),
      body: MyScroll(list, hot, scrollController: _pageScrollController), 
    );
  }
}

class MyScroll extends StatelessWidget {
  final List<int> list; 
  final int hotItem;
  final ItemScrollController scrollController;

  const MyScroll(this.list, this.hotItem, {super.key, required this.scrollController});

  @override
  Widget build(BuildContext context) => ScrollablePositionedList.separated( 
      itemCount: list.length,
      initialScrollIndex: hotItem,
      itemBuilder: (context, index) => MyItem(list[index], hotItem), // 使用list中的真实data索引,修正原逻辑错误
      itemScrollController: scrollController,
      separatorBuilder: (context, int index) => const Divider(height: 12, thickness: 1),
    );
}

class MyItem extends StatelessWidget {
  final int i, hot;
  const MyItem(this.i, this.hot, {super.key});

  @override
  Widget build(BuildContext context) => Center(
    heightFactor: 3, 
    child: Container(
      height: 20.0, 
      color: (i == hot) ? Colors.green :  Colors.white, 
      child: Text(data[i])
    )
  );
}

注:额外修正了MyItem的索引逻辑——原代码直接将SPL的本地索引传给MyItem,导致第二页高亮逻辑错误,现在改为传递list中的真实data索引,保证高亮正确。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 16:40:57