fluttercandies / flutter_scrollview_observer

A widget for observing data related to the child widgets being displayed in a ScrollView. Maintainer: @LinXunFeng
https://pub.dev/packages/scrollview_observer
MIT License
451 stars 46 forks source link

[Bug report] #78

Closed villainlin0321 closed 7 months ago

villainlin0321 commented 7 months ago

Version

1.16.5

Platforms

Web

Device Model

web

flutter info

[!] Flutter (Channel unknown, 3.10.7, on macOS 13.4.1 22F82 darwin-arm64
    (Rosetta), locale zh-Hans-CN)
    ! Flutter version 3.10.7 on channel unknown at /Users/linwei/flutter/stable
      Currently on an unknown channel. Run `flutter channel` to switch to an
      official channel.
      If that doesn't fix the issue, reinstall Flutter by following instructions
      at https://flutter.dev/docs/get-started/install.
    ! Unknown upstream repository.
      Reinstall Flutter by following instructions at
      https://flutter.dev/docs/get-started/install.
    • Framework revision e285328a69 (8 months ago), 2023-08-17 17:55:17 -0700
    • Engine revision 077a732ef4
    • Dart version 3.0.7
    • DevTools version 2.23.1
    • Pub download mirror https://pub.flutter-io.cn
    • Flutter download mirror https://storage.flutter-io.cn
    • If those were intentional, you can disregard the above warnings; however
      it is recommended to use "git" directly to perform update checks and
      upgrades.

[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.2)
    • Android SDK at /Users/linwei/Library/Android/sdk
    • Platform android-34, build-tools 33.0.2
    • Java binary at: /Applications/Android
      Studio.app/Contents/jbr/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build
      11.0.15+0-b2043.56-8887301)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 14.2)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 14C18
    • CocoaPods version 1.15.2

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2022.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build
      11.0.15+0-b2043.56-8887301)

[✓] VS Code (version 1.87.2)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.84.0

[✓] Connected device (2 available)            
    • macOS (desktop) • macos  • darwin-arm64   • macOS 13.4.1 22F82
      darwin-arm64 (Rosetta)
    • Chrome (web)    • chrome • web-javascript • Google Chrome 123.0.6312.107

[✓] Network resources
    • All expected network resources are available.

! Doctor found issues in 1 category.

How to reproduce?

数据源切换之后,onObserve方法不走

Logs

No response

Example code (optional)

import 'package:flutter/material.dart';
import 'package:scrollview_observer/scrollview_observer.dart';
import 'package:get/get.dart';

enum ChatLoadStatus {
  // 初始状态
  INIT,
  // 加载更多
  LOADING,
  // 加载完成
  LOAD_COMPLETED,
  // 加载失败
  LOAD_FAILED,
  // 没有更多数据
  LOAD_NO_MORE,
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late bool _isChangeData = false;

  final ScrollController scrollController = ScrollController();
  BuildContext? _sliverListViewContext;
  late ListObserverController observerController;
  late ChatScrollObserver chatObserver;
  BuildContext? get sliverListViewContext => _sliverListViewContext;

  RxList<String> messages = <String>[].obs;

  /// TODO - 修改此值 设置第一次数据
  late List<String> _tempList = [
    'test1', 'test2', 'test3', 'test4', 'test1', 'test2',
    'test3', 'test4', 'test1', 'test2', 'test3', 'test4',
    'test3', 'test4', 'test1', 'test2', 'test3', 'test4',   
  ];

  set sliverListViewContext(BuildContext? value) {
    if (value != null && _sliverListViewContext != value) {
      _sliverListViewContext = value;
      observerController.sliverContexts.add(value);
      ListViewOnceObserveNotification().dispatch(_sliverListViewContext);
      Future.delayed(Duration(milliseconds: 300), () {
        chatObserver.observeSwitchShrinkWrap();
      });
    }
  }

  ChatLoadStatus _chatLoadStatus = ChatLoadStatus.INIT;
  ChatLoadStatus get chatLoadStatus => _chatLoadStatus;
  ValueNotifier<ChatLoadStatus> chatLoadStatusNotifier =
  ValueNotifier(ChatLoadStatus.INIT);
  set chatLoadStatus(ChatLoadStatus status) {
    if (status != _chatLoadStatus) {
      _chatLoadStatus = status;
      chatLoadStatusNotifier.value = status;
    }
  }

  @override
  void initState() {
    super.initState();
    scrollController.addListener(_scrollListener);
    observerController = ListObserverController(controller: scrollController)
      ..cacheJumpIndexOffset = false;

    chatObserver = ChatScrollObserver(observerController)
      ..toRebuildScrollViewCallback = () {
        print('--- toRebuildScrollViewCallback');
      };

    _queryHistoryMessages();
  }

  ///聊天列表 滚动监听
  void _scrollListener() {

  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Expanded(child: _buildChatListView()),
          _createButton(),
        ],
      ),
    );
  }

  ///聊天列表 Widget
  Widget _buildChatListView() {
    return Obx(() {
      return ListViewObserver(
        sliverListContexts: () {
          return [if (sliverListViewContext != null) sliverListViewContext!];
        },
        controller: observerController,
        child: ListView.builder(
          controller: scrollController,
          shrinkWrap: chatObserver.isShrinkWrap,
          reverse: true,
          scrollDirection: Axis.vertical,
          itemCount: messages.length + 1,
          physics: chatObserver.isShrinkWrap
              ? const NeverScrollableScrollPhysics()
              : ChatObserverBouncingScrollPhysics(
              observer: chatObserver),
          itemBuilder: (ctx, index) {
            // 页面有数据才能正常滚动 如果当前消息数大于1时 把内部的context给到observerController
            if (sliverListViewContext != ctx &&
                messages.length > 1) {
              sliverListViewContext = ctx;
            }
            if (index < messages.length) {
              String message = messages[index];
              return _buildMessageRow(message, index);
            } else {
              return _buildListLoadStatusWidget();
            }
          },
        ),
        onObserve: (resultMap) {
          if (!chatObserver.isShrinkWrap) {
            int lastIndex = resultMap.displayingChildModelList.last.index;
            if (lastIndex >= messages.length - 3) {
              print('--- $lastIndex');
              if (chatLoadStatus == ChatLoadStatus.INIT ||
                  chatLoadStatus == ChatLoadStatus.LOAD_COMPLETED) {
                _queryHistoryMessages();
              }
            }
          }
        },
      );
    });
  }

  Widget _buildMessageRow(String s, int index) {
    return Container(
      height: 60,
      padding: EdgeInsets.only(left: 80, right: 80),
      child: Row(
        children: [
          Text(s),
          Spacer(),
          Text('$index'),
        ],
      ),
    );
  }

  Widget _buildListLoadStatusWidget() {
    return Container(
      alignment: Alignment.center,
      child: ValueListenableBuilder(
        valueListenable: chatLoadStatusNotifier,
        builder: (BuildContext context, ChatLoadStatus value, Widget? child) {
          if (value == ChatLoadStatus.LOADING) {
            return Container(
              width: 60,
              height: 60,
              color: Colors.transparent,
              child: CircularProgressIndicator(strokeWidth: 4),
            );
          }
          return Container(
            color: Colors.transparent,
          );
        },
      ),
    );
  }

  Widget _createButton() {
    return WidgetButton(
      clickEvent: () {
        _changeMessages();
      },
      height: 50,
      width: 200,
      circular: 25,
      backGroundColor: Colors.black12,
      child: Text(
        'change data',
      ),
    );
  }

  void _queryHistoryMessages() {
    List<String> data = [];

    if (_isChangeData) {
      for (int i = 0; i < 20; i ++) {
        data.add('change data $i');
      }
      messages.addAll(data);
    } else {
      messages.value = _tempList;
    }

  }

  void _changeMessages() {
    _isChangeData = !_isChangeData;

    if (!_isChangeData && _tempList.length == messages.length) {

    } else {
      messages.value = [];
      _queryHistoryMessages();
    }
  }
}

class WidgetButton extends StatelessWidget {
  const WidgetButton({
    Key? key,
    this.clickEvent,
    required this.child,
    this.padding,
    this.circular = 0,
    this.borderColor = Colors.transparent,
    this.borderWidth = 0,
    this.backGroundColor = Colors.transparent,
    this.width,
    this.height,
    this.overlayColor,
  }) : super(
      key: key
  );

  final Widget child;
  final VoidCallback? clickEvent;
  final EdgeInsets? padding;
  final Color? overlayColor;
  final double circular;
  final Color borderColor;
  final double borderWidth;
  final Color backGroundColor;
  final double? width;
  final double? height;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: width,
      height: height,
      child: TextButton (
        onPressed: clickEvent,
        style: ButtonStyle(
          padding: MaterialStateProperty.all(padding ?? const EdgeInsets.all(0)),
          shape: MaterialStateProperty.all(
            RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(circular),
            ),
          ),
          side: MaterialStateProperty.all(
            BorderSide(
              color: borderColor,
              width: borderWidth,
            ),
          ),
          overlayColor: MaterialStateProperty.all(overlayColor ??
              Colors.white.withOpacity(0.05)),
          backgroundColor: MaterialStateProperty.resolveWith((states) {
            return backGroundColor;
          }),
        ),
        child: child,
      ),
    );
  }
}

Contact

No response

LinXunFeng commented 7 months ago

做如下调整即可:

set sliverListViewContext(BuildContext? value) {
  if (value != null && _sliverListViewContext != value) {
    _sliverListViewContext = value;
+    observerController.reattach();
-    observerController.sliverContexts.add(value);
-    ListViewOnceObserveNotification().dispatch(_sliverListViewContext);
    Future.delayed(Duration(milliseconds: 300), () {
+      ListViewOnceObserveNotification().dispatch(_sliverListViewContext);
      chatObserver.observeSwitchShrinkWrap();
    });
  }
}

PS: 不要手动去给 observerController.sliverContexts 赋值~

onObserve 方法不走的原因就是因为当前 sliverContexts 里有两个 ctx,但是这个回调只会取第一个 ctx 的数据给你,但是它已经无了,没有数据的,当前观察结果与上次的结果一致,所以就不走了。你用 onObserveAll 回调就可以拿到所有 ctx 的数据,但不建议,按上述调整一下代码就可以了

villainlin0321 commented 7 months ago

我发现我的sliverContexts一直是空的, 之前observerController.sliverContexts.add(value);时就是去年10月份沟通的,手动调用observeSwitchShrinkWrap 也是一样的 image

LinXunFeng commented 7 months ago

之前observerController.sliverContexts.add(value);时就是去年10月份沟通的

这个是不是当时理解错了?当时是说可以手动指定对应的 ctx,并抛出了wikidemo 的链接而已。

现在按上述说明改了之后还会有问题吗?

villainlin0321 commented 7 months ago

demo没问题了,实际项目还有问题

villainlin0321 commented 7 months ago

observerController.sliverContexts.add(value); 必须加了这一行然后再去执行observeSwitchShrinkWrap 才有效,当时得出来的结论是可能跟我视图层级有关

LinXunFeng commented 7 months ago

demo没问题了,实际项目还有问题

我需要有可复现的 demo 才能定位问题

observerController.sliverContexts.add(value); 必须加了这一行然后再去执行observeSwitchShrinkWrap 才有效,当时得出来的结论是可能跟我视图层级有关

如果你实在需要这么做,可以改为使用 onObserveAll 回调,然后用 ctx 去取观察结果。或者先把 sliverContexts 清空再 add