swipable_stack 0.8.1 copy "swipable_stack: ^0.8.1" to clipboard
swipable_stack: ^0.8.1 copied to clipboard

A widget for stacking cards, which users can swipe horizontally and vertically with beautiful animations.

example/lib/main.dart

import 'dart:math';

import 'package:example/card_label.dart';
import 'package:flutter/material.dart';
import 'package:swipable_stack/swipable_stack.dart';

class SwipeDirectionColor {
  static const right = Color.fromRGBO(70, 195, 120, 1);
  static const left = Color.fromRGBO(220, 90, 108, 1);
  static const up = Color.fromRGBO(83, 170, 232, 1);
  static const down = Color.fromRGBO(154, 85, 215, 1);
}

extension SwipeDirecionX on SwipeDirection {
  Color get color {
    switch (this) {
      case SwipeDirection.right:
        return Color.fromRGBO(70, 195, 120, 1);
      case SwipeDirection.left:
        return Color.fromRGBO(220, 90, 108, 1);
      case SwipeDirection.up:
        return Color.fromRGBO(83, 170, 232, 1);
      case SwipeDirection.down:
        return Color.fromRGBO(154, 85, 215, 1);
    }
    return Colors.transparent;
  }
}

const _images = [
  'images/image_5.jpg',
  'images/image_3.jpg',
  'images/image_4.jpg',
];

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: Home(),
    );
  }
}

class Home extends StatefulWidget {
  const Home({Key key}) : super(key: key);

  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  SwipableStackController _controller;

  void _listenController() {
    setState(() {});
  }

  @override
  void initState() {
    super.initState();
    _controller = SwipableStackController()..addListener(_listenController);
  }

  static const double _bottomAreaHeight = 100;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return Scaffold(
      body: SafeArea(
        child: Stack(
          children: [
            Positioned.fill(
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: SwipableStack(
                  controller: _controller,
                  stackClipBehaviour: Clip.none,
                  onSwipeCompleted: (index, direction) {
                    print('$index, $direction');
                  },
                  horizontalSwipeThreshold: 0.8,
                  verticalSwipeThreshold: 0.8,
                  overlayBuilder: (
                    context,
                    constraints,
                    index,
                    direction,
                    swipeProgress,
                  ) {
                    final opacity = min(swipeProgress, 1.0);

                    final isRight = direction == SwipeDirection.right;
                    final isLeft = direction == SwipeDirection.left;
                    final isUp = direction == SwipeDirection.up;
                    final isDown = direction == SwipeDirection.down;
                    return Stack(
                      children: [
                        Opacity(
                          opacity: isRight ? opacity : 0,
                          child: CardLabel.right(),
                        ),
                        Opacity(
                          opacity: isLeft ? opacity : 0,
                          child: CardLabel.left(),
                        ),
                        Opacity(
                          opacity: isUp ? opacity : 0,
                          child: CardLabel.up(),
                        ),
                        Opacity(
                          opacity: isDown ? opacity : 0,
                          child: CardLabel.down(),
                        ),
                      ],
                    );
                  },
                  builder: (context, index, constraints) {
                    final itemIndex = index % _images.length;
                    final imagePath = _images[itemIndex];
                    return ClipRRect(
                      child: Stack(
                        children: [
                          Positioned.fill(
                            child: Container(
                              decoration: BoxDecoration(
                                borderRadius: BorderRadius.circular(14),
                                image: DecorationImage(
                                  image: AssetImage(imagePath),
                                  fit: BoxFit.cover,
                                ),
                                boxShadow: [
                                  BoxShadow(
                                    offset: const Offset(0, 2),
                                    blurRadius: 26,
                                    color: Colors.black.withOpacity(0.08),
                                  ),
                                ],
                              ),
                            ),
                          ),
                          Align(
                            alignment: Alignment.bottomCenter,
                            child: Container(
                              height: 200,
                              width: double.infinity,
                              decoration: BoxDecoration(
                                borderRadius: BorderRadius.vertical(
                                  bottom: Radius.circular(14),
                                ),
                                gradient: LinearGradient(
                                  begin: Alignment.topCenter,
                                  end: Alignment.bottomCenter,
                                  colors: <Color>[
                                    Colors.black12.withOpacity(0),
                                    Colors.black12.withOpacity(.4),
                                    Colors.black12.withOpacity(.82),
                                  ],
                                ),
                              ),
                            ),
                          ),
                          Padding(
                            padding: const EdgeInsets.symmetric(horizontal: 12),
                            child: Column(
                              mainAxisAlignment: MainAxisAlignment.end,
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text(
                                  'Sample No.${itemIndex + 1}',
                                  style: theme.textTheme.headline6.copyWith(
                                    color: Colors.white,
                                  ),
                                ),
                                SizedBox(height: _bottomAreaHeight)
                              ],
                            ),
                          ),
                        ],
                      ),
                    );
                  },
                ),
              ),
            ),
            Align(
              alignment: Alignment.bottomCenter,
              child: Padding(
                padding: const EdgeInsets.symmetric(horizontal: 8),
                child: SizedBox(
                  height: _bottomAreaHeight,
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      _BottomButton(
                        color: _controller.canRewind
                            ? Colors.amberAccent
                            : Colors.grey,
                        child: const Icon(Icons.refresh),
                        onPressed: _controller.canRewind
                            ? () {
                                _controller.rewind();
                              }
                            : null,
                      ),
                      _BottomButton(
                        color: SwipeDirectionColor.left,
                        child: const Icon(Icons.arrow_back),
                        onPressed: () {
                          _controller.next(
                            swipeDirection: SwipeDirection.left,
                          );
                        },
                      ),
                      _BottomButton(
                        color: SwipeDirectionColor.up,
                        onPressed: () {
                          _controller.next(
                            swipeDirection: SwipeDirection.up,
                          );
                        },
                        child: const Icon(Icons.arrow_upward),
                      ),
                      _BottomButton(
                        color: SwipeDirectionColor.right,
                        onPressed: () {
                          _controller.next(
                            swipeDirection: SwipeDirection.right,
                          );
                        },
                        child: const Icon(Icons.arrow_forward),
                      ),
                      _BottomButton(
                        color: SwipeDirectionColor.down,
                        onPressed: () {
                          _controller.next(
                            swipeDirection: SwipeDirection.down,
                          );
                        },
                        child: const Icon(Icons.arrow_downward),
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    _controller.removeListener(_listenController);
    _controller.dispose();
  }
}

class _BottomButton extends StatelessWidget {
  const _BottomButton({
    Key key,
    @required this.onPressed,
    @required this.child,
    @required this.color,
  }) : super(key: key);

  final VoidCallback onPressed;
  final Icon child;
  final Color color;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 64,
      width: 64,
      child: ElevatedButton(
        style: ButtonStyle(
          shape: MaterialStateProperty.resolveWith(
            (states) => RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(100),
            ),
          ),
          backgroundColor: MaterialStateProperty.resolveWith(
            (states) => color,
          ),
        ),
        onPressed: onPressed,
        child: child,
      ),
    );
  }
}
297
likes
140
pub points
93%
popularity

Publisher

verified publisherheavenosk.com

A widget for stacking cards, which users can swipe horizontally and vertically with beautiful animations.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, sprung

More

Packages that depend on swipable_stack