Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.27.2
- ListViewObserver
- Support `SliverPrototypeExtentList` (`prototypeItem` of `ListView` and `ReorderableListView`) by @LinXunFeng in [#158](https://github.com/fluttercandies/flutter_scrollview_observer/issues/158).

## 1.27.1
- ObserverController
- Correct the scrolling clamped by an outdated `scrollExtent` by @LinXunFeng in [#150](https://github.com/fluttercandies/flutter_scrollview_observer/issues/150).
Expand Down
6 changes: 6 additions & 0 deletions example/lib/common/route/router_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import 'package:scrollview_observer_example/features/listview/listview_custom_de
import 'package:scrollview_observer_example/features/listview/listview_demo/listview_demo_page.dart';
import 'package:scrollview_observer_example/features/listview/listview_dynamic_offset/listview_dynamic_offset_page.dart';
import 'package:scrollview_observer_example/features/listview/listview_fixed_height_demo/listview_fixed_height_demo_page.dart';
import 'package:scrollview_observer_example/features/listview/reorderable_listview_demo/reorderable_listview_demo_page.dart';
import 'package:scrollview_observer_example/features/listview/sliver_list_demo/sliver_list_demo_page.dart';
import 'package:scrollview_observer_example/features/nested_scrollview/nested_scrollview_demo/nested_scrollview_demo_page.dart';
import 'package:scrollview_observer_example/features/nested_scrollview/nested_scrollview_tab_bar_view_demo/page/nested_scrollview_tab_bar_view_demo_page.dart';
Expand Down Expand Up @@ -57,6 +58,7 @@ class MyPage {
static const listViewDynamicOffset = '/list_view_dynamic_offset';
static const listViewCustom = '/list_view_custom';
static const listViewInfinite = '/list_view_infinite';
static const listViewReorderable = '/list_view_reorderable';
static const sliverListView = '/sliver_list_view';
// GridView
static const gridView = '/grid_view';
Expand Down Expand Up @@ -141,6 +143,10 @@ class MyRoute {
path: MyPage.listViewInfinite,
builder: (context, state) => const InfiniteListViewPage(),
),
GoRoute(
path: MyPage.listViewReorderable,
builder: (context, state) => const ReorderableListViewDemoPage(),
),
GoRoute(
path: MyPage.sliverListView,
builder: (context, state) => const SliverListViewDemoPage(),
Expand Down
6 changes: 6 additions & 0 deletions example/lib/features/home/home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ class HomePage extends StatelessWidget {
NavigationService.push(MyPage.listViewInfinite);
},
),
ListTile(
title: const Text("ListView - Reorderable"),
onTap: () {
NavigationService.push(MyPage.listViewReorderable);
},
),
ListTile(
title: const Text("SliverListView"),
onTap: () {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*
* @Author: LinXunFeng linxunfeng@yeah.net
* @Repo: https://github.com/fluttercandies/flutter_scrollview_observer
* @Date: 2026-09-13 16:00:00
*/

import 'package:flutter/material.dart';
import 'package:scrollview_observer/scrollview_observer.dart';
import 'package:scrollview_observer_example/utils/snackbar.dart';

class ReorderableListViewDemoPage extends StatefulWidget {
const ReorderableListViewDemoPage({Key? key}) : super(key: key);

@override
State<ReorderableListViewDemoPage> createState() =>
_ReorderableListViewDemoPageState();
}

class _ReorderableListViewDemoPageState
extends State<ReorderableListViewDemoPage> {
final items = List.generate(100, (index) => index);

int _hitIndex = 0;

/// Whether to use the prototypeItem of [ReorderableListView].
bool _usePrototypeItem = false;

ScrollController scrollController = ScrollController();

late ListObserverController observerController;

@override
void initState() {
super.initState();

observerController = ListObserverController(controller: scrollController);
}

@override
void dispose() {
scrollController.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
Widget resultWidget = _buildListView();
resultWidget = ListViewObserver(
child: resultWidget,
controller: observerController,
onObserve: (resultModel) {
setState(() {
_hitIndex = resultModel.firstChild?.index ?? 0;
});
},
);
resultWidget = Scaffold(
appBar: _buildAppBar(),
body: resultWidget,
floatingActionButton: _buildJumpButton(),
);
return resultWidget;
}

PreferredSizeWidget _buildAppBar() {
return AppBar(
title: const Text("ReorderableListView"),
actions: [
_buildPrototypeItemSwitch(),
],
);
}

Widget _buildPrototypeItemSwitch() {
final prototypeItemSwitch = Switch(
value: _usePrototypeItem,
onChanged: (value) => _onPrototypeItemChanged(value: value),
);
Widget resultWidget = Row(
children: [
const Text('prototypeItem'),
prototypeItemSwitch,
],
);
return resultWidget;
}

Widget _buildJumpButton() {
Widget resultWidget = const Icon(Icons.airline_stops_outlined);
resultWidget = FloatingActionButton(
child: resultWidget,
onPressed: () => _onJumpToItem(item: 50),
);
return resultWidget;
}

Widget _buildListView() {
return ReorderableListView.builder(
// It must be the same as the controller of ListObserverController.
scrollController: scrollController,
prototypeItem: _usePrototypeItem ? const SizedBox(height: 80) : null,
itemBuilder: (ctx, index) {
return _buildListItemView(index: index);
},
itemCount: items.length,
onReorderItem: (oldIndex, newIndex) => _onReorderItem(
oldIndex: oldIndex,
newIndex: newIndex,
),
);
}

Widget _buildListItemView({required int index}) {
final item = items[index];
final isHit = _hitIndex == index;
Widget resultWidget = Text(
"item -- $item, index -- $index",
style: TextStyle(
color: isHit ? Colors.white : Colors.black,
),
);
resultWidget = Center(child: resultWidget);
resultWidget = Container(
margin: const EdgeInsets.only(bottom: 5),
color: isHit ? Colors.red : Colors.black12,
child: resultWidget,
);
resultWidget = SizedBox(
// The key is required by ReorderableListView.
key: ValueKey(item),
height: _usePrototypeItem || item.isEven ? 80 : 50,
child: resultWidget,
);
return resultWidget;
}

void _onPrototypeItemChanged({required bool value}) {
setState(() {
_usePrototypeItem = value;
});
// The type of the sliver will be changed, so the sliver context needs to
// be re-recorded.
observerController.reattach();
}

void _onJumpToItem({required int item}) {
// The index of the item would be changed after reordering.
final index = items.indexOf(item);
SnackBarUtil.showSnackBar(
context: context,
text: 'Jump to item $item, index $index',
);
observerController.jumpTo(
index: index,
isFixedHeight: _usePrototypeItem,
);
}

void _onReorderItem({
required int oldIndex,
required int newIndex,
}) {
setState(() {
items.insert(newIndex, items.removeAt(oldIndex));
});
}
}
4 changes: 2 additions & 2 deletions lib/src/common/observer_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ class ObserverWidget<C extends ObserverController, M extends ObserveModel,

/// Used to find the target RenderSliver.
///
/// The default is to find [RenderSliverList], [RenderSliverFixedExtentList]
/// and [RenderSliverGrid].
/// The default is to find [RenderSliverList],
/// [RenderSliverFixedExtentBoxAdaptor] and [RenderSliverGrid].
final bool Function(RenderObject?)? customTargetRenderSliverType;

/// It allows you to customize observation logic when original logic doesn't
Expand Down
17 changes: 6 additions & 11 deletions lib/src/listview/list_observer_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -126,17 +126,12 @@ class ListViewObserver extends ObserverWidget<ListObserverController,

/// Determine whether the [obj] is a supported RenderSliver type.
static bool isSupportRenderSliverType(RenderObject? obj) {
if (obj == null) return false;
if (obj is RenderSliverList || obj is RenderSliverFixedExtentList) {
return true;
}
final objRuntimeTypeStr = obj.runtimeType.toString();
final types = [
// New type added in flutter 3.16.0.
// https://github.com/fluttercandies/flutter_scrollview_observer/issues/74
'RenderSliverVariedExtentList',
];
return types.contains(objRuntimeTypeStr);
// [RenderSliverFixedExtentBoxAdaptor] covers
// - [RenderSliverFixedExtentList] (itemExtent)
// - RenderSliverVariedExtentList (itemExtentBuilder, added in flutter
// 3.16.0, https://github.com/fluttercandies/flutter_scrollview_observer/issues/74)
// - _RenderSliverPrototypeExtentList (prototypeItem, https://github.com/fluttercandies/flutter_scrollview_observer/issues/158)
return obj is RenderSliverList || obj is RenderSliverFixedExtentBoxAdaptor;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class ListViewObserveDisplayingChildModel extends ObserveDisplayingChildModel
);

/// The target sliverList.
/// It would be [RenderSliverList] or [RenderSliverFixedExtentList].
/// It would be [RenderSliverList] or [RenderSliverFixedExtentBoxAdaptor].
RenderSliverMultiBoxAdaptor sliverList;

@override
Expand Down
2 changes: 1 addition & 1 deletion lib/src/listview/models/listview_observe_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ListViewObserveModel extends ObserveModel {
);

/// The target sliverList.
/// It would be [RenderSliverList] or [RenderSliverFixedExtentList].
/// It would be [RenderSliverList] or [RenderSliverFixedExtentBoxAdaptor].
RenderSliverMultiBoxAdaptor sliverList;

/// The observing data of the first child widget that is displaying.
Expand Down
2 changes: 1 addition & 1 deletion lib/src/sliver/sliver_observer_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class SliverViewObserver extends ObserverWidget<SliverObserverController,
final OnObserveViewportCallback? onObserveViewport;

/// It's used to handle the observation logic for other types of Sliver
/// besides [RenderSliverList], [RenderSliverFixedExtentList] and
/// besides [RenderSliverList], [RenderSliverFixedExtentBoxAdaptor] and
/// [RenderSliverGrid].
final ObserveModel? Function(BuildContext context)? extendedHandleObserve;

Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: scrollview_observer
description: A widget for observing data related to the child widgets being displayed in a ScrollView.
version: 1.27.1
version: 1.27.2
homepage: https://github.com/fluttercandies/flutter_scrollview_observer

environment:
Expand Down
Loading
Loading