在没有 setState Flutter 的情况下滚动时禁用 CustomScrollView 的滚动

Disable scrolling of CustomScrollView while scrolling without setState Flutter

我在 CustomScrollView 中有多个小部件和列表,我想在某些像素限制条件下滚动时停止 CustomScrollView 滚动。

我可以使用 NeverScrollPhysics() 来停止它,但我不想在这里使用 setState() 函数,因为带有列表的 CustomScrollview 内容足够大,在滚动时重新加载时屏幕会变慢。

也尝试使用 Provider,但构建器仅提供了一个 child 小部件,该小部件不适用于 sliver 列表。

这是使用 setState() 的代码:

              NotificationListener(
                  onNotification: (ScrollNotification notif) {
                    if(notif is ScrollUpdateNotification) {
                      if (canScroll && notif.metrics.pixels > 100) {
                        canScroll = false;
                        setState(() {});
                      }
                    }
                    if(notif is ScrollEndNotification) {
                      if(!canScroll) {
                        canScroll = true;
                        setState(() {});
                      }
                    }
                    return true;
                  },
                  child: CustomScrollView(
                      shrinkWrap: true,
                      physics: canScroll ? BouncingScrollPhysics() : NeverScrollableScrollPhysics(), 
                      slivers: [
                        SliverToBoxAdapter(),                                              
                        List(),
                        List(),
                      ],
                    ),
                ),

有没有办法只重新加载 CustomScrollView 而没有 child ?否则在这种情况下有什么解决方法可以防止滚动吗?

感谢帮助

试试这个解决方案,对子小部件使用 const 构造函数,这样除非小部件更改,否则它不会重建
class MyHomePage extends StatelessWidget {
  ValueNotifier<ScrollPhysics> canScroll =
      ValueNotifier(const BouncingScrollPhysics());

  MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NotificationListener(
        onNotification: (ScrollNotification notif) {
          if (notif is ScrollUpdateNotification) {
            if (canScroll.value.runtimeType == BouncingScrollPhysics &&
                notif.metrics.pixels > 100) {
              canScroll.value = const NeverScrollableScrollPhysics();
              debugPrint("End false");
            }
          }
          if (notif is ScrollEndNotification) {
            if (canScroll.value.runtimeType == NeverScrollableScrollPhysics) {
              debugPrint("End");
              Future.delayed(const Duration(milliseconds: 300),
                  () => canScroll.value = const BouncingScrollPhysics());

              debugPrint("End1");
            }
          }
          return true;
        },
        child: ValueListenableBuilder(
          valueListenable: canScroll,
          builder:
              (BuildContext context, ScrollPhysics scrollType, Widget? child) =>
                  CustomScrollView(
            physics: scrollType,
            slivers: [
              SliverToBoxAdapter(
                child: Container(
                  height: 200,
                  color: Colors.black,
                ),
              ),
              SliverToBoxAdapter(
                child: Column(
                  children: [
                    Container(
                      height: 100,
                      color: Colors.blue,
                    ),
                    Container(
                      height: 200,
                      color: Colors.grey,
                    ),
                    Container(
                      height: 200,
                      color: Colors.blue,
                    ),
                    Container(
                      height: 200,
                      color: Colors.grey,
                    ),
                    Container(
                      height: 200,
                      color: Colors.blue,
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

您是否只需要阻止用户滚动它?我认为您可以尝试使用 jumoTo.

将列表控制到固定位置
  ...
  final _controller = ScrollController();

  @override
  Widget build(BuildContext context) {
    return NotificationListener(
      onNotification: (ScrollNotification notif) {
        if (notif is ScrollUpdateNotification) {
          if (notif.metrics.pixels > 100) {
            _controller.jumpTo(100)
          }
        }
        return true;
      },
      child: CustomScrollView(
        controller: _controller,
        ...

当构建方法被调用时,该构建方法中的所有小部件都将被重建,除了 const 个小部件,但是 const 个小部件不能接受动态参数(只能是常量值)。

Riverpod在这种情况下提供了一个很好的解决方案, 使用 ProviderScope,您可以通过 inherited widget 而不是小部件构造函数传递参数(就像使用导航传递参数时一样),因此承包商可以是 const.

示例:

数据模块

TLDR 你需要使用 Freezed 包或覆盖 == operatorhashCode 几乎总是因为飞镖问题。

class DataClass {
  final int age;
  final String name;

  const DataClass(this.age, this.name);

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;

    return other is DataClass && other.age == age && other.name == name;
  }

  @override
  int get hashCode => age.hashCode ^ name.hashCode;
}

将我们的 ScopedProvider 设置为全局变量

final dataClassScope = ScopedProvider<DataClass>(null);

我们在列表中使用的小部件

class MyChildWidget extends ConsumerWidget {
  const MyChildWidget();

  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final data = watch(dataClassScope);

    // Note for better optimaization
    // in case you are sure the data you are passing to this widget wouldn't change
    // you can just use StatelessWidget and set the data as:
    // final data = context.read(dataClassScope);
    // use ConsumerWidget (or Consumer down in this child widget tree) if the data has to change

    print('widget with name\n${data.name} rebuild');

    return SliverToBoxAdapter(
      child: Padding(
        padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 20),
        child: Text(
          'Name : ${data.name}\nAge ${data.age}',
          textAlign: TextAlign.center,
        ),
      ),
    );
  }
}

最后是主要的 CustomScrollView 小部件

class MyMainWidget extends StatefulWidget {
  const MyMainWidget();

  @override
  State<MyMainWidget> createState() => _MyMainWidgetState();
}

class _MyMainWidgetState extends State<MyMainWidget> {
  bool canScroll = true;

  void changeCanScrollState() {
    setState(() => canScroll = !canScroll);
    print('canScroll $canScroll');
  }

  final dataList = List.generate(
    20,
    (index) => DataClass(10 * index, '$index'),
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        behavior: HitTestBehavior.opaque,
        onTap: () {
          changeCanScrollState();
        },
        child: CustomScrollView(
          shrinkWrap: true,
          physics: canScroll
              ? BouncingScrollPhysics()
              : NeverScrollableScrollPhysics(),
          slivers: [
            for (int i = 0; i < dataList.length; i++)
              ProviderScope(
                overrides: [
                  dataClassScope.overrideWithValue(dataList[i]),
                ],
                child: const MyChildWidget(),
              ),
          ],
        ),
      ),
    );
  }
}

不要忘记用 ProviderScope 包裹 MaterialApp

  runApp(
    ProviderScope(
      child: MyApp(),
    ),
  );