Прокрутите до пункта после того, как нижний лист отобразится во флаттере

#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);  } }