ovi_b
ovi_b

Reputation: 169

Create a Flutter Carousel Slider with dynamic heigth for each page

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

Answers (5)

whatsevr Official
whatsevr Official

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

philipp-robin
philipp-robin

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

Shatanik Mahanty
Shatanik Mahanty

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

Sanket Vekariya
Sanket Vekariya

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

jbarat
jbarat

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

Related Questions