Flutter: сохранение состояния PageController (в PageView) при перестроении родительского виджета
Фон
Я пытаюсь реализовать складной контейнер, который сворачивается, когда ListView ниже прокручивается вверх (таким образом, сворачивается при прокрутке Listview). Для этой цели я использую DraggableScrollableSheet с AnimatedContainer, так что при прокрутке устанавливается новая высота для AnimatedContainer (вызывая коллапс ) .
В этом AnimatedContainer у меня есть PageView (обернутый SingleChildScrollView), так что пользователь может переключаться между различными виджетами карточек с помощью PageController.
Поскольку крах AnimatedContainer требует перестроения его дочерних элементов, я хочу сохранить состояние текущего выбранного карточного виджета при этих перестроениях (== изменение размера контейнера).
Проблема
Каким-то образом, когда AnimatedContainer перестраивается, PageController сбрасывается, и вместо текущего отображается первый выбранный карточный виджет.
Минимальный код проблемы
home_page.dart
/// Actually [MyHomePage] is a TabItem for the actual Home Page
/// hence the [AutomaticKeepAliveClientMixin] is needed
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with AutomaticKeepAliveClientMixin<MyHomePage> {
double _appointmentHeightFrac = 3.0;
bool _appointsCollapsed = false;
// Seems to keep resetting when the container is collapsed . . .
final PageController _controller = PageController(keepPage: true, );
@override
Widget build(BuildContext context) {
super.build(context);
return Stack(
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 75),
height: MediaQuery.of(context).size.height / _appointmentHeightFrac,
child: Card(
color: Colors.blue,
child: Center(
// RELEVANT WIDGET
child: UpcomingAppoint(isCollapsed: _appointsCollapsed, tileController: _controller)
)
),
),
DraggableScrollableSheet(
initialChildSize: 0.5,
minChildSize: 0.5,
maxChildSize: 3 / 4,
builder: (BuildContext context, ScrollController scrollController) {
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollNotification) {
if (scrollController.position.userScrollDirection ==
ScrollDirection.reverse) {
setState(() {
_appointmentHeightFrac = 10.0;
_appointsCollapsed = true;
});
return true;
} else if (scrollController.position.userScrollDirection ==
ScrollDirection.forward &&
scrollNotification.metrics.atEdge) {
setState(() {
_appointmentHeightFrac = 3.0;
_appointsCollapsed = false;
});
return true;
}
return false;
},
child: ListView.builder(
controller: scrollController,
itemCount: 7,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text('Item $index'),
);
}),
);
},
),
],
);
}
@override
bool get wantKeepAlive => true;
}
предстоящее_назначение.dart
class UpcomingAppoint extends StatefulWidget {
bool isCollapsed;
PageController tileController;
UpcomingAppoint({required this.isCollapsed, super.key, required this.tileController});
@override
State<UpcomingAppoint> createState() => _UpcomingAppointState();
}
class _UpcomingAppointState extends State<UpcomingAppoint> {
@override
Widget build(BuildContext context) {
return (widget.isCollapsed)
? _SwippableCard(tileController: widget.tileController)
: Column(
children: [
_SwippableCard(tileController: widget.tileController),
const Text("Random bottom Text, that will disappear when collapsed")
],
);
}
}
class _SwippableCardState extends State<_SwippableCard> with AutomaticKeepAliveClientMixin<_SwippableCard> {
final pages = List.unmodifiable([
const Center(child: Text("Tile Page 1")),
const Center(child: Text("Tile Page 2"))
]);
@override
Widget build(BuildContext context) {
super.build(context);
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const SizedBox(height: 16),
SizedBox(
height: 100,
child: PageView.builder(
controller: widget.tileController,
itemCount: pages.length,
itemBuilder: (_, index) {
return pages[index % pages.length];
},
),
),
],
),
);
}
@override
bool get wantKeepAlive => true;
}
Пробные решения
Я попытался вытащить PageController вверх, поэтому он не воссоздан. Пробовал сделать его статичным. Пробовал использовать AutomaticKeepAliveClientMixin, так как он работает для моего TabView. Пробовал через LocalKeys (может неправильно?). Пытался использоватьsavePage: true
флаг для PageController.
Диагностическая информация
Flutter (Channel stable, 3.0.3, on Microsoft Windows [Version 10.0.22000.739], locale en-US)
• Flutter version 3.0.3 at C:\Users\ahalm\.android\flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 676cefaaff (13 days ago), 2022-06-22 11:34:49 -0700
• Engine revision ffe7b86a1e
• Dart version 2.17.5
• DevTools version 2.12.2
Я чувствую, что упускаю что-то очень тривиальное, но я не могу понять это сам. Любая помощь будет оценена по достоинству. Кроме того, если есть более элегантный способ сделать это (без чрезмерной инженерии с блоками или около того), то я также хотел бы это знать. Заранее спасибо!