【问题标题】:Disable scrolling of CustomScrollView while scrolling without setState Flutter在没有 setState Flutter 的情况下滚动时禁用 CustomScrollView 的滚动
【发布时间】:2021-11-07 16:14:09
【问题描述】:

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

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

也尝试使用Provider,但构建器仅提供了一个子小部件,该小部件不适用于 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 而没有它的孩子?否则在这种情况下是否有任何解决方法来防止滚动?

感谢您的帮助

【问题讨论】:

  • 使用 Stream 而不是 setState。
  • 您的意思是StreamBuilder 吗?在这种情况下与Provider 相同,将重新加载CustomScrollView 的全部内容。
  • 您需要一个状态管理解决方案,例如 bloc 或 riverpod。
  • @7mada 我已经在使用 Provider 但它不能解决这个问题
  • 我知道 Provider 不会解决这个问题,但是 RiverPod 和 Bloc 可以,如果你想使用 RiverPod 我可以给你写一个答案来解决这个问题。

标签: flutter dart scrollview physics reload


【解决方案1】:

当 build 方法被调用时,除了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(),
    ),
  );

【讨论】:

  • 非常感谢。不知道 Riverpod
【解决方案2】:

您是否只需要阻止用户滚动它?我认为您可以尝试使用 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,
        ...

【讨论】:

    【解决方案3】:
    试试这个解决方案,对子部件使用 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,
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
    

    【讨论】:

    • 不幸的是,我无法创建 const 构造函数。我的列表包含带有多个参数变量的用户项。感谢您的帮助
    • @nicover 上述解决方案的工作主要是用更少的重建时间你可以试一试吗?并尝试使用无状态和有状态小部件拆分条子内容
    • 我试过你的代码。不幸的是,在我的用例中我不能这样做。条子项目被重建,我的列表使屏幕在滚动时滞后。无论如何,您的答案对另一种情况很有用
    猜你喜欢
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    • 2013-12-11
    • 1970-01-01
    • 2021-08-24
    • 2022-12-28
    • 1970-01-01
    • 2022-12-17
    相关资源
    最近更新 更多