I'm trying to implement a DraggableScrollableSheet with 2 nested scrollable lists.
I cannot use the out-of-the-box NestedScrollView class (I already tried it, but i couldn't get it to work, since it seems to be made for one preceding header sliver and only one scrollable list).
My approach with the CustomScrollView class generally seems to work, but it unfortunately brings up another problem: Everytime the user drags the sheet, all slivers get rebuild with every frame. Which is horribly slow.
I opened up a simple test case to exclude it's not the fault of any complex widget- / provider- / consumer- etc logic. Even with these simple widgets all slivers get rebuild once per frame...
Is it a bug in the framework or what am i doing wrong here?
Thank you very much...
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: DraggableScrollableActuator(child: MyHomePage(title: 'Flutter Demo Home Page')),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
AnimationController _controller;
Tween<Offset> _tween = Tween(begin: Offset(0, 1), end: Offset(0, 0));
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: Duration(milliseconds: 300));
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(children: [
SlideTransition(
position: _tween.animate(_controller),
child: MyPopUpSheet(),
),
Center(
child: IconButton(
icon: const Icon(Icons.info),
onPressed: () {
setState(() {
if (_controller.isDismissed)
_controller.forward();
else if (_controller.isCompleted) _controller.reverse().then((value) => _controller.reset());
});
}),
),
]));
}
}
class MyPopUpSheet extends StatelessWidget {
ScrollController _scrollController; //Needed for
@override
Widget build(BuildContext context) {
print("MyPopUpSheet.build()");
return Material(
elevation: 4,
shadowColor: Colors.orange,
child: DraggableScrollableSheet(
expand: true,
initialChildSize: 0.4,
minChildSize: 0.4,
maxChildSize: 1.0,
builder: (ctx, scrollController) {
_scrollController = scrollController;
return CustomScrollView(
shrinkWrap: true,
controller: _scrollController,
slivers: [
SliverToBoxAdapter(child: Test(Colors.deepOrange)),
SliverToBoxAdapter(child: Test(Colors.red)),
SliverToBoxAdapter(child: Test(Colors.green)),
SliverToBoxAdapter(child: Test(Colors.purpleAccent)),
SliverToBoxAdapter(child: Test(Colors.amberAccent))
],
);
},
),
);
}
}
class Test extends StatelessWidget {
final Color color;
Test(this.color);
@override
Widget build(BuildContext context) {
print("Test.build()");
return Container(height: 100, color: color);
}
}
I faced the same issue and found a workaround which doesn't stop the rebuilds but caches the child and fixes the scroll jank. What worked was, to store the widget inside the builder in a variable and return that variable from the builder.
var child;
return DraggableScrollableSheet(
expand: true,
initialChildSize: 0.4,
minChildSize: 0.4,
maxChildSize: 1.0,
builder: (ctx, scrollController) {
if(child == null) {
child = SomeWidget();
}
return child;
}
);
var child;
return DraggableScrollableSheet(
expand: true,
initialChildSize: 0.4,
minChildSize: 0.4,
maxChildSize: 1.0,
builder: (ctx, scrollController) {
if(child == null) {
child = CustomScrollView(
shrinkWrap: true,
controller: _scrollController,
slivers: [
SliverToBoxAdapter(child: Test(Colors.deepOrange)),
SliverToBoxAdapter(child: Test(Colors.red)),
SliverToBoxAdapter(child: Test(Colors.green)),
SliverToBoxAdapter(child: Test(Colors.purpleAccent)),
SliverToBoxAdapter(child: Test(Colors.amberAccent))
],
);
}
return child;
}
);