在底部页面显示为浮动后滚动到项目



在模式底部工作表动画进入视图后,我正试图滚动到列表中的一个项目。项目应该滚动,使其底部边缘在底部页面上方可见。以下示例的问题是,在显示了底部工作表之后,列表项的上下文为null。我怎样才能做到这一点?

以下是一个最低限度的工作示例:

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) => _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);
},
));
}
Future<void> _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");
}
}
}

我使用包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() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
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);
},
));
}
Future<void> _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);
}
}

最新更新