Use a with a SingleChildScrollView
with a column as it's child. In order to make the picture small when it's a header, use a FittedBox
. Wrap the FittedBox
with a SizedBox
to control the size of the inside widgets. Use a scroll notifier to cause updates when it is scrolling and track how far the user scrolls. Divide the scroll amount by the max height that you want in order to know the current widget that needs resizing. Resize that widget by finding the remainder and dividing it by the max height and multiplying by the difference of the min and max size then add min size. This will ensure a smooth transition. Then make any widgets above in the column max sized and below minimum sized to make sure lag doesn't ruin the scroller.
Use AnimatedOpacity
to allow the description of the header to fade in and out or make a customized animation of how you think it should look.
The following code should work though customize the text widgets with what style you'd like. Enter the custom TitleWithImage
(contains widget and two strings) items to be in the list, the maxHeight and minHeight into the custom widget. It likely isn't completely optimized and probably has lots of bugs although I fixed some:
import 'package:flutter/material.dart';
class CoolListView extends StatefulWidget {
final List<TitleWithImage> items;
final double minHeight;
final double maxHeight;
const CoolListView({Key key, this.items, this.minHeight, this.maxHeight}) : super(key: key);
@override
_CoolListViewState createState() => _CoolListViewState();
}
class _CoolListViewState extends State<CoolListView> {
List<Widget> widgets=[];
ScrollController _scrollController = new ScrollController();
@override
Widget build(BuildContext context) {
if(widgets.length == 0){
for(int i = 0; i<widget.items.length; i++){
if(i==0){
widgets.add(ListItem(height: widget.maxHeight, item: widget.items[0],descriptionTransparent: false));
}
else{
widgets.add(
ListItem(height: widget.minHeight, item: widget.items[i], descriptionTransparent: true,)
);
}
}
}
return new NotificationListener<ScrollUpdateNotification>(
child: SingleChildScrollView(
controller: _scrollController,
child: Column(
children: widgets,
)
),
onNotification: (t) {
if (t!= null && t is ScrollUpdateNotification) {
int currentWidget = (_scrollController.position.pixels/widget.maxHeight).ceil();
currentWidget = currentWidget==-1?0:currentWidget;
setState(() {
if(currentWidget != widgets.length-1){//makes higher index min
for(int i = currentWidget+1; i<=widgets.length-1; i++){
print(i);
widgets[i] = ListItem(height: widget.minHeight, item: widget.items[i],descriptionTransparent: true,);
}
}
if(currentWidget!=0){
widgets[currentWidget] = ListItem(
height: _scrollController.position.pixels%widget.maxHeight/widget.maxHeight*(widget.maxHeight-widget.minHeight)+widget.minHeight,
item: widget.items[currentWidget],
descriptionTransparent: true,
);
for(int i = currentWidget-1; i>=0; i--){
widgets[i] = ListItem(height: widget.maxHeight,
item: widget.items[i],
descriptionTransparent: false,
);
}
}
else{
widgets[0] = ListItem(
height: widget.maxHeight,
item: widget.items[0],
descriptionTransparent: false
);
}
});
}
},
);
}
}
class TitleWithImage
{
final Widget image;
final String title;
final String description;
TitleWithImage(this.image, this.title, this.description);
}
class ListItem extends StatelessWidget {
final double height;
final TitleWithImage item;
final bool descriptionTransparent;
const ListItem({Key key, this.height, this.item, this.descriptionTransparent}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child:Stack(
children: [
SizedBox(
height: height,
width: MediaQuery.of(context).size.width,
child: FittedBox(
fit: BoxFit.none,
child:Align(
alignment: Alignment.center,
child: item.image
)
),
),
SizedBox(
height: height,
width: MediaQuery.of(context).size.width,
child: Column(
children: [
Spacer(),
Text(item.title,),
AnimatedOpacity(
child: Text(
item.description,
style: TextStyle(
color: Colors.black
),
),
opacity: descriptionTransparent? 0.0 : 1.0,
duration: Duration(milliseconds: 500),
),
],
),
),
],
),
);
}
}
Edit here is my main.dart:
import 'package:cool_list_view/CoolListView.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Collapsing List Demo')),
body: CoolListView(
items: [
new TitleWithImage(
Container(
height: 1000,
width:1000,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end:
Alignment(0.8, 0.0), // 10% of the width, so there are ten blinds.
colors: [
const Color(0xffee0000),
const Color(0xffeeee00)
], // red to yellow
tileMode: TileMode.repeated, // repeats the gradient over the canvas
),
),
),
'title',
'description',
),
new TitleWithImage(
Container(
height: 1000,
width:1000,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end:
Alignment(0.8, 0.0), // 10% of the width, so there are ten blinds.
colors: [
Colors.orange,
Colors.blue,
], // red to yellow
tileMode: TileMode.repeated, // repeats the gradient over the canvas
),
),
),
'title',
'description',
),
new TitleWithImage(
Container(
height: 1000,
width:1000,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end:
Alignment(0.8, 0.0), // 10% of the width, so there are ten blinds.
colors: [
const Color(0xffee0000),
const Color(0xffeeee00)
], // red to yellow
tileMode: TileMode.repeated, // repeats the gradient over the canvas
),
),
),
'title',
'description',
),
new TitleWithImage(
Container(
height: 1000,
width:1000,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end:
Alignment(0.8, 0.0), // 10% of the width, so there are ten blinds.
colors: [
const Color(0xffee0000),
const Color(0xffeeee00)
], // red to yellow
tileMode: TileMode.repeated, // repeats the gradient over the canvas
),
),
),
'title',
'description',
),
new TitleWithImage(
Container(
height: 1000,
width:1000,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end:
Alignment(0.8, 0.0), // 10% of the width, so there are ten blinds.
colors: [
const Color(0xffee0000),
const Color(0xffeeee00)
], // red to yellow
tileMode: TileMode.repeated, // repeats the gradient over the canvas
),
),
),
'title',
'description',
),
new TitleWithImage(
Container(
height: 1000,
width:1000,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end:
Alignment(0.8, 0.0), // 10% of the width, so there are ten blinds.
colors: [
const Color(0xffee0000),
const Color(0xffeeee00)
], // red to yellow
tileMode: TileMode.repeated, // repeats the gradient over the canvas
),
),
),
'title',
'description',
),
new TitleWithImage(
Container(
height: 1000,
width:1000,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end:
Alignment(0.8, 0.0), // 10% of the width, so there are ten blinds.
colors: [
const Color(0xffee0000),
const Color(0xffeeee00)
], // red to yellow
tileMode: TileMode.repeated, // repeats the gradient over the canvas
),
),
),
'title',
'description',
),
new TitleWithImage(Container(height: 1000,width:1000,color: Colors.blue), 'title', 'description'),
new TitleWithImage(Container(height: 1000,width:1000, color: Colors.orange), 'title', 'description'),
],
minHeight: 50,
maxHeight: 300,
),
),
);
}
}