I am trying the package dynamic_stack_card_swiper
from pub.dev
because of the following requirements:
Initially, I tried the builtin CarouselView
but I couldn't figure out a way to adjust the height dynamically as per the active card. So, I searched the internet for help, and found suggestion to this dynamic_stack_card_swiper
package.
The Problem: The example given on the packages page in pub.dev
is too complicated for me to understand. The Usage section in the Readme tab shows a simple example, but I think that's incomplete because it does not work. I've tried a couple of things but couldn't understand how this works.
Question: How can I fix the code given below? The DynamicStackCardSwiper
widget does not render anything and also there are no error messages. Note: I've wrapped DynamicStackCardSwiper
with SizedBox
only to see if this would render something, but that didn't happen either. Ideally, I wouldn't want to wrap within SizedBox because of the requirement I've stated above.
import 'package:dynamic_stack_card_swiper/dynamic_stack_card_swiper.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: MyHomePage());
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> _myListOfStrings = ["one", "two", "three"];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Dynamic Stack Card Swiper"),
backgroundColor: Colors.blue.shade700,
),
backgroundColor: Colors.amber,
body: SingleChildScrollView(
child: Padding(
padding: EdgeInsetsGeometry.all(10.0),
child: SizedBox(
height: 300.0,
child: DynamicStackCardSwiper<List<String>>(
cardBuilder: (BuildContext context, List<String> item) {
return Container(
alignment: Alignment.center,
color: Colors.blueAccent,
child: Center(
child: Text(
// item[0],
"Hello world",
style: TextStyle(color: Colors.black),
),
),
);
},
),
),
),
),
);
}
}
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(home: MyHomePage());
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> _myListOfStrings = ["one", "two", "three", "four"];
refresh() {
setState(() {
_myListOfStrings = ["one", "two", "three", "four"];
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Swipe Card Queue"),
backgroundColor: Colors.blue.shade700,
),
backgroundColor: Colors.amber,
body: Column(
children: [
Expanded(
child: Center(
child:
_myListOfStrings.isEmpty
? InkWell(
onTap: refresh,
child: const Text(
"No more cards!",
style: TextStyle(fontSize: 28),
),
)
: Stack(
alignment: Alignment.center,
children:
_myListOfStrings
.asMap()
.entries
.toList()
.reversed
.map((entry) {
final index = entry.key;
final value = entry.value;
return DisappearOnExitWidget(
onDisappear: () {
setState(() {
_myListOfStrings.removeAt(index);
});
},
child: AnimatedPadding(
duration: const Duration(
milliseconds: 300,
),
padding: EdgeInsets.only(
right: 20.0,
left: 20.0,
top: 40.0,
bottom: 40.0 - (index * (_myListOfStrings.length * 2)),
),
child: Card(
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(20),
),
color: Colors.blue.shade700,
child: Center(
child: Text(
value,
style: const TextStyle(
fontSize: 32,
color: Colors.white,
),
),
),
),
),
);
})
.toList(),
),
),
),
],
),
);
}
}
class DisappearOnExitWidget extends StatefulWidget {
final Widget child;
final VoidCallback? onDisappear;
final Duration animationDuration;
final Curve animationCurve;
final double boundaryThreshold;
const DisappearOnExitWidget({
super.key,
required this.child,
this.onDisappear,
this.animationDuration = const Duration(milliseconds: 300),
this.animationCurve = Curves.elasticOut,
this.boundaryThreshold = 0.0, // How far outside before disappearing
});
@override
State<DisappearOnExitWidget> createState() =>
_DisappearOnExitWidgetState();
}
class _DisappearOnExitWidgetState extends State<DisappearOnExitWidget>
with TickerProviderStateMixin {
late AnimationController _springController;
late AnimationController _fadeController;
late Animation<Offset> _springAnimation;
late Animation<double> _scaleAnimation;
Offset _dragOffset = Offset.zero;
Offset _startPosition = Offset.zero;
bool _isDragging = false;
bool _isDisappearing = false;
double _opacity = 1.0;
Size _screenSize = Size.zero;
final GlobalKey _widgetKey = GlobalKey();
@override
void initState() {
super.initState();
_springController = AnimationController(
duration: widget.animationDuration,
vsync: this,
);
_fadeController = AnimationController(
duration: Duration(milliseconds: 400),
vsync: this,
);
_springAnimation = Tween<Offset>(
begin: Offset.zero,
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _springController,
curve: widget.animationCurve,
),
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 0.5,
).animate(
CurvedAnimation(
parent: _fadeController,
curve: Curves.easeOut,
),
);
}
@override
void dispose() {
_springController.dispose();
_fadeController.dispose();
super.dispose();
}
void _onPanStart(DragStartDetails details) {
setState(() {
_isDragging = true;
_startPosition = details.localPosition;
});
_springController.stop();
}
void _onPanUpdate(DragUpdateDetails details) {
if (_isDisappearing) return;
setState(() {
_dragOffset = details.localPosition - _startPosition;
_opacity = _calculateOpacity();
});
}
double _calculateOpacity() {
if (_screenSize == Size.zero) return 1.0;
final RenderBox? renderBox =
_widgetKey.currentContext?.findRenderObject() as RenderBox?;
if (renderBox == null) return 1.0;
final widgetSize = renderBox.size;
final screenWidth = _screenSize.width;
final screenHeight = _screenSize.height;
// Get widget's top-left position relative to screen
final globalPosition = renderBox.localToGlobal(Offset.zero);
final widgetLeft = globalPosition.dx;
final widgetTop = globalPosition.dy;
final widgetRight = widgetLeft + widgetSize.width;
final widgetBottom = widgetTop + widgetSize.height;
final isOutOfBounds =
widgetLeft <= 0 ||
widgetTop <= kToolbarHeight + 20 ||
widgetRight >= screenWidth ||
widgetBottom >= screenHeight;
if (isOutOfBounds) {
return 0.0;
}
return 1.0;
}
void _onPanEnd(DragEndDetails details) {
setState(() {
_isDragging = false;
});
if (_opacity == 0.0) {
_disappear();
} else {
_animateBack();
}
}
void _animateBack() {
setState(() {
_opacity = 1.0;
});
_springAnimation = Tween<Offset>(
begin: _dragOffset,
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _springController,
curve: widget.animationCurve,
),
);
_springController.forward(from: 0).then((_) {
if (mounted) {
setState(() {
_dragOffset = Offset.zero;
});
}
});
}
void _disappear() {
if (_isDisappearing) return;
setState(() {
_isDisappearing = true;
});
_fadeController.forward(from: 0).then((_) {
widget.onDisappear?.call();
if (mounted) {
setState(() {
_dragOffset = Offset.zero;
_opacity = 1.0;
_isDisappearing = false;
});
_fadeController.reset();
}
});
}
@override
Widget build(BuildContext context) {
_screenSize = MediaQuery.of(context).size;
return GestureDetector(
onPanStart: _onPanStart,
onPanUpdate: _onPanUpdate,
onPanEnd: _onPanEnd,
child: AnimatedBuilder(
animation: Listenable.merge([_springController, _fadeController]),
builder: (context, child) {
Offset currentOffset =
_isDragging
? _dragOffset
: (_isDisappearing
? _dragOffset
: _springAnimation.value);
double currentScale =
_isDisappearing ? _scaleAnimation.value : 1.0;
return Transform.translate(
offset: currentOffset,
child: Transform.scale(
scale: currentScale,
child: Container(
key: _widgetKey,
child: widget.child,
),
),
);
},
),
);
}
}
i think that DisappearOnExitWidget will does what you ask without any external package