문제 설명
각 페이지에 대한 동적 높이로 Flutter Carousel Slider 만들기 (Create a Flutter Carousel Slider with dynamic heigth for each page)
점선 표시가 있는 회전 목마 슬라이더가 있습니다. 이 이미지와 같이
슬라이더의 각 페이지는 ListView가 자식으로 있는 카드 위젯입니다.
p>카드가 Carousel 위젯 내부에 없으면 Listview 내부의 요소가 증가함에 따라 확장됩니다.
슬라이더의 각 페이지에 대해 이 동작을 유지하고 싶지만 슬라이더 안의 카드는 더 이상 콘텐츠에 따라 크기가 조정되지 않지만 미리 정의된 높이가 있는 것 같습니다(내가 지정하지 않은 경우에도!).
슬라이더 안의 카드 높이를 어떻게 동적으로 업데이트할 수 있습니까?
이것은 내 코드입니다:
main.dart 파일
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]
],
);
},
),
),
);
}
}
및 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),
),
);
}),
),
],
);
}
}
참조 솔루션
방법 1:
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.
방법 2:
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
방법 3:
this is an open issue of the library but you can still achieve partially by the below code.
carousel.dart</p>
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. :)
방법 4:
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);
},
);
},
),
),
(by ovi_b、Shatanik Mahanty、jbarat、Sanket Vekariya、philipp‑robin)