How to make 'stacked card list view' in flutter?

4,725

You can achieve a similar behaviour with SliverPersistentHeader and a CustomScrollView, and you can wrap your cards in GestureDetector to modify their height by changing the value of SliverPersistentHeaderDelegate's maxExtent parameter. Here is a small app I wrote that achieves something that might look like what you are looking for:

import 'package:flutter/material.dart';
import 'dart:math' as math;

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Stacked list example',
      home: Scaffold(
          appBar: AppBar(
            title: Text("Stacked list example"),
            backgroundColor: Colors.black,
          ),
          body: StackedList()),
    );
  }
}

class StackedList extends StatelessWidget {
  final List<Color> _colors = Colors.primaries;
  static const _minHeight = 16.0;
  static const _maxHeight = 120.0;

  @override
  Widget build(BuildContext context) => CustomScrollView(
        slivers: _colors
            .map(
              (color) => StackedListChild(
                minHeight: _minHeight,
                maxHeight: _colors.indexOf(color) == _colors.length - 1
                    ? MediaQuery.of(context).size.height
                    : _maxHeight,
                pinned: true,
                child: Container(
                  color: _colors.indexOf(color) == 0
                      ? Colors.black
                      : _colors[_colors.indexOf(color) - 1],
                  child: Container(
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.vertical(
                          top: Radius.circular(_minHeight)),
                      color: color,
                    ),
                  ),
                ),
              ),
            )
            .toList(),
      );
}

class StackedListChild extends StatelessWidget {
  final double minHeight;
  final double maxHeight;
  final bool pinned;
  final bool floating;
  final Widget child;

  SliverPersistentHeaderDelegate get _delegate => _StackedListDelegate(
      minHeight: minHeight, maxHeight: maxHeight, child: child);

  const StackedListChild({
    Key key,
    @required this.minHeight,
    @required this.maxHeight,
    @required this.child,
    this.pinned = false,
    this.floating = false,
  })  : assert(child != null),
        assert(minHeight != null),
        assert(maxHeight != null),
        assert(pinned != null),
        assert(floating != null),
        super(key: key);

  @override
  Widget build(BuildContext context) => SliverPersistentHeader(
      key: key, pinned: pinned, floating: floating, delegate: _delegate);
}

class _StackedListDelegate extends SliverPersistentHeaderDelegate {
  final double minHeight;
  final double maxHeight;
  final Widget child;

  _StackedListDelegate({
    @required this.minHeight,
    @required this.maxHeight,
    @required this.child,
  });

  @override
  double get minExtent => minHeight;

  @override
  double get maxExtent => math.max(maxHeight, minHeight);

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    return new SizedBox.expand(child: child);
  }

  @override
  bool shouldRebuild(_StackedListDelegate oldDelegate) {
    return maxHeight != oldDelegate.maxHeight ||
        minHeight != oldDelegate.minHeight ||
        child != oldDelegate.child;
  }
}

Here is how it looks like in action:

Stacked list example .gif

And here is a really good article about Flutter's slivers that might help you in this regard:

Slivers, demystified

Hope this helps you get in the right direction.

Share:
4,725
kasahara Seiji
Author by

kasahara Seiji

Updated on December 09, 2022

Comments

  • kasahara Seiji
    kasahara Seiji over 1 year

    I want to build ui similar to this link in flutter.

    https://github.com/loopeer/CardStackView/blob/master/screenshot/screenshot1.gif

    enter image description here

    Key ideal features are followings.

    • Behave like list view, but cards should be stacked at the top of screen.
    • List can have infinite items. So old cards should be recycled to save memory.
    • I also want to set different size to each card.

    First, I found some 'tinder' like ui like following and tried them. https://blog.geekyants.com/tinder-swipe-in-flutter-7e4fc56021bc

    However, users need to swipe each single card, that required user to swipe many times to browse list items.

    And then I could somehow make a list view whose items are overlapped with next ones.

    import 'package:flutter/material.dart';
    
    class StackedList extends StatelessWidget {
      List<ItemCard> cards = [];
    
      StackedList() {
        for (int i = 0; i < 20; i++) {
          cards.add(ItemCard(i));
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('title')),
          body: Container(
            child: ListView.builder(
              itemBuilder: (context, index) {
                return Align(
                  alignment: Alignment.topCenter,
                  heightFactor: 0.8,
                  child: cards[index],
                );
              },
              itemCount: cards.length,
            ),
          ),
        );
      }
    }
    
    class ItemCard extends StatelessWidget {
      int index;
    
      ItemCard(this.index);
    
      @override
      Widget build(BuildContext context) {
        return Container(
          decoration: const BoxDecoration(
            boxShadow: [
              BoxShadow(color: Colors.black, blurRadius: 20.0),
            ],
          ),
          child: SizedBox.fromSize(
            size: const Size(300, 400),
            child: Card(
              elevation: 5.0,
              color: index % 2 == 0 ? Colors.blue : Colors.red,
              child: Center(
                child: Text(index.toString()),
              ),
            ),
          ),
        );
      }
    }
    

    However items don't stop at the top of screen, which is not exactly what I want. I guess I can achieve this effect by customizing ScrollController or ScrollPhysics but I'm not sure where I should change.