Reputation: 169
I have a Carousel Slider with a dotted indicator as shown in this image
Each page of the slider is a Card widget with a ListView as a child.
If the Card is not inside the Carousel widget, it expands as the elements inside the Listview increase.
I would like to maintain this behavior for each page of the slider, but when I put the cards inside the slider, they no longer resize based on the content, but seem to have a pre-defined height (even if I haven't specified any!).
How could I dynamically update the height of the cards inside the slider?
This is my code:
main.dart file
import 'package:flutter/material.dart';
import 'carousel.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Slider App',
home: MyHomePage(),
);
}
}
class GeneralEvent {
final String title;
final String desc;
GeneralEvent(this.title, this.desc);
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
final List<GeneralEvent> userEvents = [
GeneralEvent(
"List Item 1",
"Lorem ipsum dolor sit amet, consectetur adipisci elit.",
),
GeneralEvent(
"List Item 2",
"Lorem ipsum dolor sit amet, consectetur adipisci elit.",
),
GeneralEvent(
"List Item 3",
"Lorem ipsum dolor sit amet, consectetur adipisci elit.",
),
GeneralEvent(
"List Item 4",
"Lorem ipsum dolor sit amet, consectetur adipisci elit.",
),
GeneralEvent(
"List Item 5",
"Lorem ipsum dolor sit amet, consectetur adipisci elit.",
),
];
class _MyHomePageState extends State<MyHomePage> {
final List<Card> cards = [
Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'Card 1',
style: TextStyle(fontSize: 24),
),
),
Container(
height: 1,
width: double.infinity,
color: Color.fromRGBO(0, 0, 0, 0.12),
),
ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (ctx, index) {
return ListTile(
title: Text(
userEvents.sublist(0, 3)[index].title,
),
subtitle: Text(userEvents.sublist(0, 3)[index].desc),
);
},
itemCount: userEvents.sublist(0, 3).length,
),
],
),
),
Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'Card 2',
style: TextStyle(fontSize: 24),
),
),
Container(
height: 1,
width: double.infinity,
color: Color.fromRGBO(0, 0, 0, 0.12),
),
ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (ctx, index) {
return ListTile(
title: Text(
userEvents.sublist(3)[index].title,
),
subtitle: Text(userEvents.sublist(3)[index].desc),
);
},
itemCount: userEvents.sublist(3).length,
),
],
),
),
];
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromRGBO(235, 235, 235, 1),
appBar: AppBar(title: Text('iWantASliderAPP')),
body: Padding(
padding: EdgeInsets.fromLTRB(5, 0, 5, 0),
child: LayoutBuilder(
builder: (context, constraints) {
return ListView(
children: <Widget>[
CarouselWithIndicator(cards),
Padding(
padding: const EdgeInsets.fromLTRB(0, 50, 0, 0),
child: Text(
"if the Card is not in the slider it resize properly:",
),
),
cards[0]
],
);
},
),
),
);
}
}
and carousel.dart
import 'package:flutter/material.dart';
import 'package:carousel_slider/carousel_slider.dart';
class CarouselWithIndicator extends StatefulWidget {
final List cards;
CarouselWithIndicator(this.cards);
@override
_CarouselWithIndicatorState createState() => _CarouselWithIndicatorState();
}
class _CarouselWithIndicatorState extends State<CarouselWithIndicator> {
List<T> map<T>(List list, Function handler) {
List<T> result = [];
for (var i = 0; i < list.length; i++) {
result.add(handler(i, list[i]));
}
return result;
}
int _current = 0;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
CarouselSlider(
// height: // NO HEIGHT SPECIFIED!
viewportFraction: 1.0,
enlargeCenterPage: true,
enableInfiniteScroll: false,
onPageChanged: (index) {
setState(() {
_current = index;
});
},
items: widget.cards.map((card) {
return Builder(
builder: (BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
child: card,
);
},
);
}).toList(),
),
SizedBox(
height: 5,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: map<Widget>(widget.cards, (index, card) {
return Container(
width: 10.0,
height: 10.0,
margin: EdgeInsets.symmetric(vertical: 10.0, horizontal: 3.0),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _current == index ? Colors.grey : Color.fromRGBO(200, 200, 200, 1),
),
);
}),
),
],
);
}
}
Upvotes: 7
Views: 18818
Reputation: 1
I have made a custom Widget Class for handling this also used the animation pacakage https://pub.dev/packages/animate_do
import 'package:animate_do/animate_do.dart';
import 'package:flutter/material.dart';
class SwipeAbleDynamicHeightViews extends StatefulWidget {
///PASS UniqueKey() TO REBUILD THE WIDGET WHENEVER NEEDED
final List<Widget>? children;
final Function(int)? onIndexChanged;
final bool showDotIndicator;
const SwipeAbleDynamicHeightViews({
super.key,
this.children,
this.onIndexChanged,
this.showDotIndicator = true,
});
@override
State<SwipeAbleDynamicHeightViews> createState() =>
_SwipeAbleDynamicHeightViewsState();
}
bool currentSwipeIsLeft = false;
class _SwipeAbleDynamicHeightViewsState
extends State<SwipeAbleDynamicHeightViews> {
List<Widget> _children = <Widget>[
Container(
height: 300,
color: Colors.red,
),
Container(
height: 100,
color: Colors.green,
),
Container(
height: 250,
color: Colors.blue,
),
Container(
height: 350,
color: Colors.black,
),
];
int currentIndex = 0;
@override
void initState() {
super.initState();
if (widget.children != null && widget.children!.isNotEmpty) {
setState(() {
_children = widget.children!;
});
}
}
@override
Widget build(BuildContext context) {
if (_children.isEmpty) {
return SizedBox();
}
return GestureDetector(
onHorizontalDragEnd: (details) {
//on left swipe increase index if can be increased vise versa
if (details.velocity.pixelsPerSecond.dx < 0) {
if (currentIndex < _children.length - 1) {
//if last index reached do nothing
if (currentIndex == _children.length - 1) return;
setState(() {
currentSwipeIsLeft = true;
currentIndex++;
widget.onIndexChanged?.call(currentIndex);
});
}
} else {
//if first index reached do nothing
if (currentIndex == 0) return;
if (currentIndex > 0) {
setState(() {
currentSwipeIsLeft = false;
currentIndex--;
widget.onIndexChanged?.call(currentIndex);
});
}
}
},
child: Column(
children: [
if (currentSwipeIsLeft)
SlideInRight(
key: UniqueKey(),
duration: const Duration(milliseconds: 200),
child: _children[currentIndex],
)
else
SlideInLeft(
key: UniqueKey(),
duration: const Duration(milliseconds: 200),
child: _children[currentIndex],
),
//dot indicator
if (widget.showDotIndicator && _children.length > 1)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
_children.length,
(index) => Container(
margin: const EdgeInsets.all(4),
width: 6,
height: 6,
decoration: BoxDecoration(
color: currentIndex == index ? Colors.black : Colors.grey,
shape: BoxShape.circle,
),
),
),
),
],
),
);
}
}
Upvotes: 0
Reputation: 71
I recognized that using CarouselSlider for different content sizes is difficult.
I used this package instead: https://pub.dev/packages/expandable_page_view
int current = 0;
Container(
width: MediaQuery.of(context).size.width,
child: ExpandablePageView.builder(
onPageChanged: (index) {
setState(() {
current = index;
});
},
itemCount: itemList.length,
itemBuilder: (context, index) {
return GalleryItemThumbnail(
galleryItemModel: GalleryItemModel(id: itemList[index]),
onTap: () {
_open(context, index);
},
);
},
),
),
Upvotes: 7
Reputation: 362
I finally found a solution to this with the help of runtime height determination. Link to runtime size determination question : How to get height of a Widget?
Just define a height variable and update it in Measure size's onChanged Function.
MeasureSizeRenderObject.dart
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
typedef void OnWidgetSizeChange(Size size);
class MeasureSizeRenderObject extends RenderProxyBox {
Size oldSize;
final OnWidgetSizeChange onChange;
MeasureSizeRenderObject(this.onChange);
@override
void performLayout() {
super.performLayout();
Size newSize = child.size;
if (oldSize == newSize) return;
oldSize = newSize;
WidgetsBinding.instance.addPostFrameCallback((_) {
onChange(newSize);
});
}
}
class MeasureSize extends SingleChildRenderObjectWidget {
final OnWidgetSizeChange onChange;
const MeasureSize({
Key key,
@required this.onChange,
@required Widget child,
}) : super(key: key, child: child);
@override
RenderObject createRenderObject(BuildContext context) {
return MeasureSizeRenderObject(onChange);
}
}
Import the above dart file and you are good to go.
In my case I had a list of days and a Map<String,List> of operational hours per day. So when ever a new operational hour was added the ListView length would grow in size.
List _days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
Map<String, List> _operationalHours = {};
I was populating operational hours from Firebase Cloud Firestore.
CarouselSlider Code :
CarouselSlider.builder(
itemCount: days.length,
itemBuilder: (BuildContext context, int index, int realIdx) {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20)),
color: Color(0xff3b3b3b),
child: Container(
width: displayWidth(context),
padding: EdgeInsets.all(15.0),
child: Column(
children: [
Text(
days[index],
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 20),
),
SizedBox(
height: 15,
),
Column(
children: [
MeasureSize(
onChange: (size) {
height = size.height;
print("log" + height.toString());
setState(() {});
},
child: ListView.builder(
shrinkWrap: true,
itemCount: operationalHours[days[index]]
.length,
itemBuilder: (context, i) {
return getTimeSlot(
operationalHours[days[index]][i]);
}),
)
],
)
],
),
),
);
},
options: new CarouselOptions(
autoPlay: false,
viewportFraction: 1.0,
height: 100 + height,
enlargeCenterPage: false,
enableInfiniteScroll: false,
onPageChanged: (index, reason) {
setState(() {
_current = index;
});
}),
),
If I made any typo or bug while copying my code from IDE to StackOverflow let me know. I will update the answer.
Upvotes: 5
Reputation: 2956
this is an open issue of the library but you can still achieve partially by the below code.
carousel.dart
import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter/material.dart';
class CarouselWithIndicator extends StatefulWidget {
final List cards;
CarouselWithIndicator(this.cards);
@override
_CarouselWithIndicatorState createState() => _CarouselWithIndicatorState();
}
class _CarouselWithIndicatorState extends State<CarouselWithIndicator> {
List<T> map<T>(List list, Function handler) {
List<T> result = [];
for (var i = 0; i < list.length; i++) {
result.add(handler(i, list[i]));
}
return result;
}
int _current = 0;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Wrap(
children: <Widget>[
CarouselSlider(
height: double.maxFinite,
viewportFraction: 1.0,
enlargeCenterPage: true,
enableInfiniteScroll: false,
onPageChanged: (index) {
setState(() {
_current = index;
});
},
items: widget.cards.map((card) {
return Builder(
builder: (BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
child: card,
);
},
);
}).toList(),
),
],
),
SizedBox(
height: 5,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: map<Widget>(widget.cards, (index, card) {
return Container(
width: 10.0,
height: 10.0,
margin: EdgeInsets.symmetric(vertical: 10.0, horizontal: 3.0),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: _current == index
? Colors.grey
: Color.fromRGBO(200, 200, 200, 1),
),
);
}),
),
],
);
}
}
main.dart file
import 'package:flutter/material.dart';
import 'carousel.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Slider App',
home: MyHomePage(),
);
}
}
class GeneralEvent {
final String title;
final String desc;
GeneralEvent(this.title, this.desc);
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
final List<GeneralEvent> userEvents = [
GeneralEvent(
"List Item 1",
"Lorem ipsum dolor sit amet, consectetur adipisci elit.",
),
GeneralEvent(
"List Item 2",
"Lorem ipsum dolor sit amet, consectetur adipisci elit.",
),
GeneralEvent(
"List Item 3",
"Lorem ipsum dolor sit amet, consectetur adipisci elit.",
),
GeneralEvent(
"List Item 4",
"Lorem ipsum dolor sit amet, consectetur adipisci elit.",
),
GeneralEvent(
"List Item 5",
"Lorem ipsum dolor sit amet, consectetur adipisci elit.",
),
];
class _MyHomePageState extends State<MyHomePage> {
List<Widget> cards = [
Wrap(
children: <Widget>[
Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'Card 1',
style: TextStyle(fontSize: 24),
),
),
Container(
height: 1,
width: double.infinity,
color: Color.fromRGBO(0, 0, 0, 0.12),
),
ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (ctx, index) {
return ListTile(
title: Text(
userEvents.sublist(0, 3)[index].title,
),
subtitle: Text(userEvents.sublist(0, 3)[index].desc),
);
},
itemCount: userEvents.sublist(0, 3).length,
),
],
),
),
],
),
Wrap(
children: <Widget>[
Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'Card 2',
style: TextStyle(fontSize: 24),
),
),
Container(
height: 1,
width: double.infinity,
color: Color.fromRGBO(0, 0, 0, 0.12),
),
ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (ctx, index) {
return ListTile(
title: Text(
userEvents.sublist(3)[index].title,
),
subtitle: Text(userEvents.sublist(3)[index].desc),
);
},
itemCount: userEvents.sublist(3).length,
),
],
),
),
],
),
];
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromRGBO(235, 235, 235, 1),
appBar: AppBar(title: Text('iWantASliderAPP')),
body: Padding(
padding: EdgeInsets.fromLTRB(5, 0, 5, 0),
child: LayoutBuilder(
builder: (context, constraints) {
return ListView(
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
CarouselWithIndicator(cards),
],
),
Padding(
padding: const EdgeInsets.fromLTRB(0, 50, 0, 0),
child: Text(
"if the Card is not in the slider it resize properly:",
),
),
cards[0]
],
);
},
),
),
);
}
}
I hope this would be somewhat helpful for you. :)
Upvotes: 0
Reputation: 2470
This is a known issue in the library.
This is the answer from the creator of the lib Currently there is no way to define a dynamic height for each carousel item. But I think it can be done by creating a dynamic height container in the carousel item.
For the whole conversation have a look at the issue: https://github.com/serenader2014/flutter_carousel_slider/issues/106
Upvotes: 2