Wrap item different height in vertical PageView - Flutter
512
Solution 1
Use a vertical page builder and the content within a stack. This way you can take advantage of the scroll animation while having your own layout.
Here is the plugin I wrote. Feel free to use it and turn it into a pub.dev plugin.
Usage:
import 'package:flutter/material.dart';
import 'VerticalPageViewer.dart';
class StackTest extends StatefulWidget {
const StackTest({Key? key}) : super(key: key);
@override
State<StackTest> createState() => _StackTestState();
}
class _StackTestState extends State<StackTest> {
final List<Widget> images = [
Container(
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.all(Radius.circular(10)),
),
),
Container(
decoration: BoxDecoration(
color: Colors.cyan,
borderRadius: BorderRadius.all(Radius.circular(10)),
),
),
Container(
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.all(Radius.circular(10)),
),
),
];
List<double> testHeight = [100, 300, 500];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Dynamic Height PageView',
style: TextStyle(color: Colors.white),
),
centerTitle: true,
),
body: SafeArea(
child: Container(
child: DynamicHeightPageView(
heightList: testHeight,
children: images,
onSelectedItem: (index) {
print("index: $index");
},
),
),
),
);
}
}
DynamicHeightPageView class:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:collection/collection.dart';
typedef PageChangedCallback = void Function(double? page);
typedef PageSelectedCallback = void Function(int index);
class DynamicHeightPageView extends StatefulWidget {
final List<double> heightList;
final List<Widget> children;
final double cardWidth;
final ScrollPhysics? physics;
final PageChangedCallback? onPageChanged;
final PageSelectedCallback? onSelectedItem;
final int initialPage;
DynamicHeightPageView({
required this.heightList,
required this.children,
this.physics,
this.cardWidth = 300,
this.onPageChanged,
this.initialPage = 0,
this.onSelectedItem,
}) : assert(heightList.length == children.length);
@override
_DynamicHeightPageViewState createState() => _DynamicHeightPageViewState();
}
class _DynamicHeightPageViewState extends State<DynamicHeightPageView> {
double? currentPosition;
PageController? controller;
@override
void initState() {
super.initState();
currentPosition = widget.initialPage.toDouble();
controller = PageController(initialPage: widget.initialPage);
controller!.addListener(() {
setState(() {
currentPosition = controller!.page;
if (widget.onPageChanged != null) {
Future(() => widget.onPageChanged!(currentPosition));
}
if (widget.onSelectedItem != null && (currentPosition! % 1) == 0) {
Future(() => widget.onSelectedItem!(currentPosition!.toInt()));
}
});
});
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
return GestureDetector(
onTap: () {
print("Current Element index tab: ${currentPosition!.round()}");
},
child: Stack(
children: [
CardController(
cardWidth: widget.cardWidth,
heightList: widget.heightList,
children: widget.children,
currentPosition: currentPosition,
cardViewPagerHeight: constraints.maxHeight,
cardViewPagerWidth: constraints.maxWidth,
),
Positioned.fill(
child: PageView.builder(
physics: widget.physics,
scrollDirection: Axis.vertical,
itemCount: widget.children.length,
controller: controller,
itemBuilder: (context, index) {
return Container();
},
),
)
],
),
);
});
}
}
class CardController extends StatelessWidget {
final double? currentPosition;
final List<double> heightList;
final double cardWidth;
final double cardViewPagerHeight;
final double? cardViewPagerWidth;
final List<Widget>? children;
CardController({
this.children,
this.cardViewPagerWidth,
required this.cardWidth,
required this.cardViewPagerHeight,
required this.heightList,
this.currentPosition,
});
@override
Widget build(BuildContext context) {
List<Widget> cardList = [];
for (int i = 0; i < children!.length; i++) {
var cardHeight = heightList[i];
var cardTop = getTop(cardHeight, cardViewPagerHeight, i, heightList);
var cardLeft = (cardViewPagerWidth! / 2) - (cardWidth / 2);
Widget card = Positioned(
top: cardTop,
left: cardLeft,
child: Container(
width: cardWidth,
height: cardHeight,
child: children![i],
),
);
cardList.add(card);
}
return Stack(
children: cardList,
);
}
double getTop(
double cardHeight, double viewHeight, int i, List<double> heightList) {
double diff = (currentPosition! - i);
double diffAbs = diff.abs();
double basePosition = (viewHeight / 2) - (cardHeight / 2);
if (diffAbs == 0) {
//element in focus
return basePosition;
}
int intCurrentPosition = currentPosition!.toInt();
double doubleCurrentPosition = currentPosition! - intCurrentPosition;
//calculate distance between to-pull elements
late double pullHeight;
if (heightList.length > intCurrentPosition + 1) {
//check for end of list
pullHeight = heightList[intCurrentPosition] / 2 +
heightList[intCurrentPosition + 1] / 2;
} else {
pullHeight = heightList[intCurrentPosition] / 2;
}
if (diff >= 0) {
//before focus element
double afterListSum = heightList.getRange(i, intCurrentPosition + 1).sum;
return (viewHeight / 2) -
afterListSum +
heightList[intCurrentPosition] / 2 -
pullHeight * doubleCurrentPosition;
} else {
//after focus element
var beforeListSum = heightList.getRange(intCurrentPosition, i).sum;
return (viewHeight / 2) +
beforeListSum -
heightList[intCurrentPosition] / 2 -
pullHeight * doubleCurrentPosition;
}
}
}
Solution 2
first of all, you should use SafeArea
in order to prevent your widgets go through the notch. see [this][1].
Then you should use ListView
instead of PageView
because PageView
creates pages with the same sizes. in ListView
create an array of int
that stores height of widget and use it to create widgets with different size.
List<int> heights = [100, 120, 10];// and so on
\\then use it as follow:
ListView.builder(
itemCount: 6,
itemBuilder: (context, i){
return Container(
height:heights[i],
width: 200, // or any value you want
padding: const EdgeInsets.all(8.0),
alignment: Alignment.center,
child: YourWidget);
},
),
[1]: https://stackoverflow.com/questions/49227667/using-safearea-in-flutter#:~:text=SafeArea%20is%20basically%20a%20glorified,%22creative%22%20features%20by%20manufactures.
Author by