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
421 stars 44 forks source link

[Feature request] 能否通过这个组件做个一类似美团外卖店铺首页或饿了么店铺首页的示例呢? #88

Open sjkyyt01 opened 1 month ago

sjkyyt01 commented 1 month ago

Platforms

dart

Description

我并不是一个资深的Flutter人员,目前在研究你的这个插件,想结合你这个做一个类似美团外卖或是饿了么店铺首页页面,通过你的示例进行结合,要么商品列表上移时,SliverAppBar无法上移,要么上移后会被SliverAppBar或是SliverPersistentHeader总价盖住,能否结合这些做一个类似美团外卖或是饿了么店铺首页的示例呢?

Why

No response

sjkyyt01 commented 1 month ago
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_sticky_header/flutter_sticky_header.dart';
import 'package:sliver_tools/sliver_tools.dart';

import '../../components/scrollview_observer/src/common/observer_typedef.dart';
import '../../components/scrollview_observer/src/sliver/sliver_observer_controller.dart';
import '../../components/scrollview_observer/src/sliver/sliver_observer_view.dart';
import '../../components/scrollview_observer/src/utils/src/nested_scroll_util.dart';
import '../../components/scrollview_observer/src/utils/src/observer_utils.dart';
import '../../components/scrollview_observer/src/utils/src/slivers.dart';
import '../demo/common/wigdets.dart';

class Category {
  int id;
  String categoryName;
  List<Goods> goods;

  Category({
    this.id = 0,
    this.categoryName = '',
    this.goods = const [],
  });
}

class Goods {
  int id;
  String goodsName;

  Goods({
    this.id = 0,
    this.goodsName = '',
  });
}

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

  @override
  State<Other4Page> createState() => _Other4PageState();
}

class _Other4PageState extends State<Other4Page> with TickerProviderStateMixin {
  List<Category> list = List.generate(10, (index) {
    int categoryId = index + 1;
    return Category(
      id: categoryId,
      categoryName: 'category_$categoryId',
      goods: List.generate(10, (i) => Goods(id: i + categoryId * 10, goodsName: 'category_$categoryId - item_${i + categoryId * 10}')),
    );
  });

  final nestedScrollViewKey = GlobalKey();
  final appBarKey = GlobalKey();
  final appHeaderKey = GlobalKey();
  final tabBarKey = GlobalKey();

  final scrollController = ScrollController();
  late SliverObserverController sliverItemObserverController;
  final nestedScrollUtil = NestedScrollUtil();

  // late final SliverObserverController sliverObserverController;
  Map<int, BuildContext> itemSliverIndexCtxMap = {};
  Map<int, BuildContext> sliverIndexCtxMap = {};

  ValueNotifier<int> tabCurrentSelectedIndex = ValueNotifier(0);
  bool isIgnoreCalcTabBarIndex = false;

  late TabController tabBarController;

  @override
  void initState() {
    super.initState();
    tabBarController = TabController(
      length: 2,
      vsync: this,
    );
    sliverItemObserverController = SliverObserverController(controller: scrollController);
    nestedScrollUtil.outerScrollController = scrollController;
    // sliverObserverController = SliverObserverController(
    //   controller: scrollController,
    // );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SliverViewObserver(
        /// To observe which sliver is currently the first.
        sliverContexts: () => sliverIndexCtxMap.values.toList(),
        // customOverlap: (sliverContext) {
        //   return nestedScrollUtil.calcOverlap(
        //     nestedScrollViewKey: nestedScrollViewKey,
        //     sliverContext: sliverContext,
        //   );
        // },
        triggerOnObserveType: ObserverTriggerOnObserveType.directly,
        dynamicLeadingOffset: () {
          // Accumulate the height of all PersistentHeader.
          return ObserverUtils.calcPersistentHeaderExtent(
                key: appBarKey,
                offset: scrollController.offset,
              ) +
              1;
          // To avoid tabBar index rebound.
        },
        onObserveViewport: (result) {
          if (isIgnoreCalcTabBarIndex) return;
          int? currentTabIndex;
          final currentFirstSliverCtx = result.firstChild.sliverContext;
          for (var sectionIndex in sliverIndexCtxMap.keys) {
            final ctx = sliverIndexCtxMap[sectionIndex];
            if (ctx == null) continue;
            // If they are not the same sliver, continue.
            if (currentFirstSliverCtx != ctx) continue;
            // If the sliver is not visible, continue.
            final visible = (ctx.findRenderObject() as RenderSliver).geometry?.visible ?? false;
            if (!visible) continue;
            currentTabIndex = sectionIndex;
            break;
          }
          if (currentTabIndex == null) return;
          updateTabBarIndex(currentTabIndex);
        },
        child: SliverViewObserver(
          /// To observe sliver items and handle scrollTo.
          controller: sliverItemObserverController,
          sliverContexts: () => itemSliverIndexCtxMap.values.toList(),
          child: NestedScrollView(
            // key: nestedScrollViewKey,
            controller: scrollController,
            headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
              return [
                SliverOverlapAbsorber(
                  handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
                  sliver: MultiSliver(
                    // key: nestedScrollViewKey,
                    pushPinnedChildren: true,
                    children: [
                      SliverAppBar(
                        key: appBarKey,
                        forceElevated: innerBoxIsScrolled,
                        pinned: true,
                        title: const Text('Other4Page'),
                      ),
                      SliverToBoxAdapter(
                        key: appHeaderKey,
                        child: Container(
                          height: 100,
                          color: Colors.red[200],
                        ),
                      ),
                      SliverPersistentHeader(
                        key: tabBarKey,
                        pinned: true,
                        delegate: SliverHeaderDelegate.fixedHeight(
                          height: 40,
                          child: Container(
                            color: Colors.blue,
                            child: TabBar(
                              controller: tabBarController,
                              tabs: const [
                                Tab(text: "Tab 1"),
                                Tab(text: "Tab 2"),
                              ],
                            ),
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
              ];
            },
            body: Builder(
              builder: (context) {
                return TabBarView(
                  controller: tabBarController,
                  children: [
                    Row(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        SizedBox(
                          width: 100,
                          child: Container(
                            height: 500,
                            color: Colors.blue[200],
                            child: const Text('data'),
                          ),
                        ),
                        Expanded(
                          child: CustomScrollView(
                            // controller: sliverItemObserverController.controller,
                            // physics: const RangeMaintainingScrollPhysics(),
                            physics: const ClampingScrollPhysics(),
                            slivers: [
                              SliverOverlapInjector(
                                handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
                              ),
                              ...List.generate(list.length, (mainIndex) {
                                return _buildSectionListView(mainIndex);
                              }),
                            ],
                          ),
                        ),
                      ],
                    ),
                    Container(
                      height: 500,
                      color: Colors.white,
                    ),
                  ],
                );
              }
            ),
          ),
        ),
      ),
      bottomNavigationBar: buildBottomNavigationBar(context),
    );
  }

  Widget buildBottomNavigationBar(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Row(
          children: List.generate(list.length, (index) {
            return Expanded(
              child: InkWell(
                onTap: () async {
                  updateTabBarIndex(index);
                  isIgnoreCalcTabBarIndex = true;
                  // await sliverItemObserverController.jumpTo(
                  //   sliverContext: itemSliverIndexCtxMap[index],
                  //   index: 0,
                  //   isFixedHeight: true,
                  //   offset: (offset) {
                  //     return ObserverUtils.calcPersistentHeaderExtent(
                  //       key: appBarKey,
                  //       offset: offset,
                  //     );
                  //   },
                  // );
                  await sliverItemObserverController.animateTo(
                    sliverContext: itemSliverIndexCtxMap[index],
                    index: 0,
                    isFixedHeight: true,
                    duration: const Duration(milliseconds: 200),
                    curve: Curves.easeInOut,
                    offset: (offset) {
                      return ObserverUtils.calcPersistentHeaderExtent(
                        key: appBarKey,
                        offset: offset,
                      );
                    },
                  );
                  isIgnoreCalcTabBarIndex = false;
                },
                child: ValueListenableBuilder(
                  valueListenable: tabCurrentSelectedIndex,
                  builder: (BuildContext context, int value, Widget? child) {
                    return Container(
                      alignment: Alignment.center,
                      height: 40,
                      decoration: BoxDecoration(
                        border: Border.all(width: 0.5),
                        color: value == index ? Colors.amber : Colors.white,
                      ),
                      child: Text(
                        list[index].categoryName,
                      ),
                    );
                  },
                ),
              ),
            );
          }),
        ),
        SizedBox(height: MediaQuery.paddingOf(context).bottom),
      ],
    );
  }

  Widget _buildSectionListView(int mainIndex) {
    return SliverObserveContext(
      child: SliverStickyHeader(
        header: Container(
          color: Colors.red,
          height: 40,
          alignment: Alignment.centerLeft,
          child: Text(
            list[mainIndex].categoryName,
            style: const TextStyle(color: Colors.white, fontSize: 16),
          ),
        ),
        sliver: SliverFixedExtentList(
          itemExtent: 120,
          delegate: SliverChildBuilderDelegate(
            (context, index) {
              // Save the context of SliverList.
              itemSliverIndexCtxMap[mainIndex] = context;
              return Container(
                color: Colors.white,
                margin: const EdgeInsets.symmetric(vertical: 5),
                height: 100,
                child: Text(list[mainIndex].goods[index].goodsName),
              );
            },
            childCount: list[mainIndex].goods.length,
          ),
        ),
      ),
      onObserve: (context) {
        // Save the context of the outermost sliver.
        sliverIndexCtxMap[mainIndex] = context;
      },
    );
  }

  updateTabBarIndex(int index) {
    if (index == tabCurrentSelectedIndex.value) return;
    tabCurrentSelectedIndex.value = index;
  }
}

上面这段代码商品列表在上移后会被挡住,不知道怎么改写