diff --git a/example/lib/main.dart b/example/lib/main.dart index 794399f..ea2db89 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -37,27 +37,47 @@ class _MyHomePageState extends State { static const double maxHeight = 1000; final random = math.Random(); final scrollDirection = Axis.vertical; + final nestedParentScrollViewKey = GlobalKey(); late AutoScrollController controller; late List> randomList; + bool nested = false; @override void initState() { super.initState(); - controller = AutoScrollController( - viewportBoundaryGetter: () => - Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom), - axis: scrollDirection); + controller = _getController(nested); randomList = List.generate(maxCount, (index) => [index, (maxHeight * random.nextDouble()).toInt()]); } + AutoScrollController _getController(bool withScrollerKey) { + final scrollerKey = withScrollerKey ? nestedParentScrollViewKey : null; + + return AutoScrollController( + viewportBoundaryGetter: () => + Rect.fromLTRB(0, 0, 0, MediaQuery.of(context).padding.bottom), + axis: scrollDirection, + scrollerWidgetKey: scrollerKey, // only need to assign in nested use case + ); + } + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), actions: [ + IconButton( + onPressed: () { + setState(() { + nested = !nested; + controller = _getController(nested); + }); + }, + icon: Text(nested ? 'Single' : 'Nested'), + iconSize: 50, + ), IconButton( onPressed: () { setState(() => counter = 0); @@ -74,16 +94,7 @@ class _MyHomePageState extends State { ) ], ), - body: ListView( - scrollDirection: scrollDirection, - controller: controller, - children: randomList.map((data) { - return Padding( - padding: EdgeInsets.all(8), - child: _getRow(data[0], math.max(data[1].toDouble(), 50.0)), - ); - }).toList(), - ), + body: nested ? _buildNestedListView() : _buildSingleListView(), floatingActionButton: FloatingActionButton( onPressed: _nextCounter, tooltip: 'Increment', @@ -92,6 +103,46 @@ class _MyHomePageState extends State { ); } + Widget _buildSingleListView() { + return ListView( + scrollDirection: scrollDirection, + controller: controller, + children: randomList.map((data) { + return Padding( + padding: EdgeInsets.all(8), + child: _getRow(data[0], math.max(data[1].toDouble(), 50.0)), + ); + }).toList(), + ); + } + + Widget _buildNestedListView() { + return SingleChildScrollView( + key: nestedParentScrollViewKey, + scrollDirection: Axis.vertical, + controller: controller, + child: Column( + children: [ + Padding( + padding: EdgeInsets.all(40), + child: Text('Nested Demo', style: TextStyle(fontSize: 48)), + ), + ListView( + scrollDirection: scrollDirection, + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + children: randomList.map((data) { + return Padding( + padding: EdgeInsets.all(8), + child: _getRow(data[0], math.max(data[1].toDouble(), 50.0)), + ); + }).toList(), + ), + ], + ), + ); + } + int counter = -1; Future _nextCounter() { setState(() => counter = (counter + 1) % maxCount); diff --git a/lib/scroll_to_index.dart b/lib/scroll_to_index.dart index 5ff4840..7f7f68a 100644 --- a/lib/scroll_to_index.dart +++ b/lib/scroll_to_index.dart @@ -29,6 +29,7 @@ abstract class AutoScrollController implements ScrollController { factory AutoScrollController( {double initialScrollOffset: 0.0, bool keepScrollOffset: true, + GlobalKey? scrollerWidgetKey, double? suggestedRowHeight, ViewportBoundaryGetter viewportBoundaryGetter: defaultViewportBoundaryGetter, @@ -38,6 +39,7 @@ abstract class AutoScrollController implements ScrollController { return SimpleAutoScrollController( initialScrollOffset: initialScrollOffset, keepScrollOffset: keepScrollOffset, + scrollerWidgetKey: scrollerWidgetKey, suggestedRowHeight: suggestedRowHeight, viewportBoundaryGetter: viewportBoundaryGetter, beginGetter: axis == Axis.horizontal ? (r) => r.left : (r) => r.top, @@ -92,6 +94,8 @@ abstract class AutoScrollController implements ScrollController { class SimpleAutoScrollController extends ScrollController with AutoScrollControllerMixin { @override + final GlobalKey? scrollerWidgetKey; + @override final double? suggestedRowHeight; @override final ViewportBoundaryGetter viewportBoundaryGetter; @@ -103,6 +107,7 @@ class SimpleAutoScrollController extends ScrollController SimpleAutoScrollController( {double initialScrollOffset: 0.0, bool keepScrollOffset: true, + this.scrollerWidgetKey, this.suggestedRowHeight, this.viewportBoundaryGetter: defaultViewportBoundaryGetter, required this.beginGetter, @@ -120,6 +125,8 @@ class SimpleAutoScrollController extends ScrollController class PageAutoScrollController extends PageController with AutoScrollControllerMixin { @override + final GlobalKey? scrollerWidgetKey; + @override final double? suggestedRowHeight; @override final ViewportBoundaryGetter viewportBoundaryGetter; @@ -132,6 +139,7 @@ class PageAutoScrollController extends PageController {int initialPage: 0, bool keepPage: true, double viewportFraction: 1.0, + this.scrollerWidgetKey, this.suggestedRowHeight, this.viewportBoundaryGetter: defaultViewportBoundaryGetter, AutoScrollController? copyTagsFrom, @@ -149,6 +157,7 @@ mixin AutoScrollControllerMixin on ScrollController implements AutoScrollController { @override final Map tagMap = {}; + GlobalKey? scrollerWidgetKey; double? get suggestedRowHeight; ViewportBoundaryGetter get viewportBoundaryGetter; AxisValueGetter get beginGetter; @@ -477,12 +486,35 @@ mixin AutoScrollControllerMixin on ScrollController final renderBox = ctx.findRenderObject()!; assert(Scrollable.of(ctx) != null); - final RenderAbstractViewport viewport = - RenderAbstractViewport.of(renderBox)!; - final revealedOffset = viewport.getOffsetToReveal(renderBox, alignment); + final RenderAbstractViewport? viewport = scrollerWidgetKey != null + ? _getScrollerWidgetViewPort(scrollerWidgetKey!, renderBox) + : RenderAbstractViewport.of(renderBox); + assert(viewport != null); + final revealedOffset = viewport!.getOffsetToReveal(renderBox, alignment); return revealedOffset; } + + RenderAbstractViewport? _getScrollerWidgetViewPort( + GlobalKey scrollerWidgetKey, + RenderObject? childItem, + ) { + final scrollerWidgetCtx = scrollerWidgetKey.currentContext; + assert(scrollerWidgetCtx != null); + final scrollerRenderBox = scrollerWidgetCtx!.findRenderObject(); + RenderAbstractViewport? viewportBeforeController; + + while (childItem != null) { + if (childItem == scrollerRenderBox) { + return viewportBeforeController; + } + if (childItem is RenderAbstractViewport) { + viewportBeforeController = childItem; + } + childItem = childItem.parent as RenderObject?; + } + return null; + } } void _cancelAllHighlights([AutoScrollTagState? state]) {