#flutter
#трепетать
Вопрос:
Я пытаюсь прокрутить элемент в списке после того, как в поле зрения появится модальный нижний лист. Элемент должен прокручиваться так, чтобы его нижний край был виден над нижним листом. Проблема с приведенным ниже примером заключается в том, что контекст элемента списка равен нулю после отображения нижнего листа. Как я могу этого достичь?
Вот минимальный рабочий пример:
import 'package:flutter/material.dart'; const ITEMS = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ]; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const MaterialApp( title: 'Flutter Demo', home: MyHomePage(), ); } } class MyHomePage extends StatelessWidget { const MyHomePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( body: _buildBody(context), ); } Widget _buildBody(BuildContext context) { return SingleChildScrollView( child: Column( children: ITEMS.map((i) =gt; _buildItem(context, i)).toList())); } Widget _buildItem(BuildContext context, int index) { final itemKey = GlobalKey(); return Card( key: key, color: index % 2 == 0 ? Colors.red : Colors.blue, child: ListTile( title: Text('Item $index'), onTap: () { _tappedItemAsync(context, index, itemKey); }, )); } Futurelt;voidgt; _tappedItemAsync( BuildContext context, int index, GlobalKey itemKey) async { showModalBottomSheet( context: context, builder: (context) { return BottomSheet( enableDrag: false, onClosing: () {}, builder: (c) { return const SizedBox( height: 400, ); }); }); final itemContext = itemKey.currentContext; if (itemContext is BuildContext) { print("Item has non-null context. Scrolling to it."); await Scrollable.ensureVisible(itemContext); } else { print("Item context is null"); } } }
Ответ №1:
Я решил эту проблему, используя пакет scroll_to_index и установив заполнение тела после сборки нижнего листа:
import 'package:flutter/material.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; const ITEMS = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ]; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', home: MyHomePage(), ); } } class MyHomePage extends StatefulWidget { // padding must exit faster bottom sheet otherwise it shows final paddingDuration = const Duration(milliseconds: 200); final scrollController = AutoScrollController(); MyHomePage({Key? key}) : super(key: key); @override _MyHomePageState createState() =gt; _MyHomePageState(); } class _MyHomePageState extends Statelt;MyHomePagegt; { double _bottomSheetHeight = 0; @override Widget build(BuildContext context) { return Scaffold( body: AnimatedPadding( duration: widget.paddingDuration, padding: EdgeInsets.only(bottom: _bottomSheetHeight), child: _buildBody(context)), ); } Widget _buildBody(BuildContext context) { return ListView.builder( controller: widget.scrollController, itemCount: 20, itemBuilder: (context, index) { return AutoScrollTag( key: ValueKey(index), controller: widget.scrollController, index: index, child: _buildItem(context, index)); }); } Widget _buildItem(BuildContext context, int index) { return Card( color: index % 2 == 0 ? Colors.red : Colors.blue, child: ListTile( title: Text('Item $index'), onTap: () { _tappedItemAsync(context, index); }, )); } Futurelt;voidgt; _tappedItemAsync(BuildContext context, int index) async { // start showing the bottom sheet final bottomSheetKey = GlobalKey(); final bottomSheetClosedFuture = showModalBottomSheet( context: context, builder: (context) { return BottomSheet( key: bottomSheetKey, enableDrag: false, onClosing: () {}, builder: (c) { return const SizedBox( height: 400, ); }); }); // when the bottom starts closing reset the body padding bottomSheetClosedFuture.then((value) { setState(() { _bottomSheetHeight = 0; }); }); // wait for the bottom sheet to have height // then update the body padding WidgetsBinding.instance?.addPostFrameCallback((_) { final bottomSheetHeight = bottomSheetKey.currentContext?.size?.height; if (bottomSheetHeight == null) { throw Exception('bottomsheet has no height'); } if (_bottomSheetHeight != bottomSheetHeight) { setState(() { _bottomSheetHeight = bottomSheetHeight; }); } }); // wait for the body padding to finish animating await Future.delayed( const Duration(milliseconds: 85) widget.paddingDuration); // scroll the item into bottom of newly constrained viewport await widget.scrollController.scrollToIndex(index, preferPosition: AutoScrollPosition.end, duration: widget.paddingDuration); } }