I have a vertical PageView with different item height :
But I would like to wrap each item according their height.
The final result I want here:
How can we do this ?
I have a vertical PageView with different item height :
But I would like to wrap each item according their height.
The final result I want here:
How can we do this ?
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.
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;
}
}
}