mirror of
https://github.com/flutter/samples.git
synced 2025-11-08 13:58:47 +00:00
213 lines
5.3 KiB
Dart
213 lines
5.3 KiB
Dart
// Copyright 2019 The Flutter team. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/physics.dart';
|
|
|
|
class CardSwipeDemo extends StatefulWidget {
|
|
const CardSwipeDemo({super.key});
|
|
static String routeName = 'misc/card_swipe';
|
|
|
|
@override
|
|
State<CardSwipeDemo> createState() => _CardSwipeDemoState();
|
|
}
|
|
|
|
class _CardSwipeDemoState extends State<CardSwipeDemo> {
|
|
late List<String> fileNames;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_resetCards();
|
|
}
|
|
|
|
void _resetCards() {
|
|
fileNames = [
|
|
'assets/eat_cape_town_sm.jpg',
|
|
'assets/eat_new_orleans_sm.jpg',
|
|
'assets/eat_sydney_sm.jpg',
|
|
];
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(title: const Text('Card Swipe')),
|
|
body: Padding(
|
|
padding: const EdgeInsets.all(12.0),
|
|
child: Center(
|
|
child: Column(
|
|
children: [
|
|
Expanded(
|
|
child: ClipRect(
|
|
child: Stack(
|
|
children: [
|
|
for (final fileName in fileNames)
|
|
SwipeableCard(
|
|
imageAssetName: fileName,
|
|
onSwiped: () {
|
|
setState(() {
|
|
fileNames.remove(fileName);
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
ElevatedButton(
|
|
child: const Text('Refill'),
|
|
onPressed: () {
|
|
setState(() {
|
|
_resetCards();
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class Card extends StatelessWidget {
|
|
final String imageAssetName;
|
|
|
|
const Card({required this.imageAssetName, super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AspectRatio(
|
|
aspectRatio: 3 / 5,
|
|
child: DecoratedBox(
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(20.0),
|
|
image: DecorationImage(
|
|
image: AssetImage(imageAssetName),
|
|
fit: BoxFit.cover,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class SwipeableCard extends StatefulWidget {
|
|
final String imageAssetName;
|
|
final VoidCallback onSwiped;
|
|
|
|
const SwipeableCard({
|
|
required this.onSwiped,
|
|
required this.imageAssetName,
|
|
super.key,
|
|
});
|
|
|
|
@override
|
|
State<SwipeableCard> createState() => _SwipeableCardState();
|
|
}
|
|
|
|
class _SwipeableCardState extends State<SwipeableCard>
|
|
with SingleTickerProviderStateMixin {
|
|
late AnimationController _controller;
|
|
late Animation<Offset> _animation;
|
|
late double _dragStartX;
|
|
bool _isSwipingLeft = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controller = AnimationController.unbounded(vsync: this);
|
|
_animation = _controller.drive(
|
|
Tween<Offset>(begin: Offset.zero, end: const Offset(1, 0)),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return SlideTransition(
|
|
position: _animation,
|
|
child: GestureDetector(
|
|
onHorizontalDragStart: _dragStart,
|
|
onHorizontalDragUpdate: _dragUpdate,
|
|
onHorizontalDragEnd: _dragEnd,
|
|
child: Card(imageAssetName: widget.imageAssetName),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Sets the starting position the user dragged from.
|
|
void _dragStart(DragStartDetails details) {
|
|
_dragStartX = details.localPosition.dx;
|
|
}
|
|
|
|
/// Changes the animation to animate to the left or right depending on the
|
|
/// swipe, and sets the AnimationController's value to the swiped amount.
|
|
void _dragUpdate(DragUpdateDetails details) {
|
|
var isSwipingLeft = (details.localPosition.dx - _dragStartX) < 0;
|
|
if (isSwipingLeft != _isSwipingLeft) {
|
|
_isSwipingLeft = isSwipingLeft;
|
|
_updateAnimation(details.localPosition.dx);
|
|
}
|
|
|
|
setState(() {
|
|
final size = context.size;
|
|
|
|
if (size == null) {
|
|
return;
|
|
}
|
|
|
|
// Calculate the amount dragged in unit coordinates (between 0 and 1)
|
|
// using this widgets width.
|
|
_controller.value =
|
|
(details.localPosition.dx - _dragStartX).abs() / size.width;
|
|
});
|
|
}
|
|
|
|
/// Runs the fling / spring animation using the final velocity of the drag
|
|
/// gesture.
|
|
void _dragEnd(DragEndDetails details) {
|
|
final size = context.size;
|
|
|
|
if (size == null) {
|
|
return;
|
|
}
|
|
|
|
var velocity = (details.velocity.pixelsPerSecond.dx / size.width).abs();
|
|
_animate(velocity: velocity);
|
|
}
|
|
|
|
void _updateAnimation(double dragPosition) {
|
|
_animation = _controller.drive(
|
|
Tween<Offset>(
|
|
begin: Offset.zero,
|
|
end: _isSwipingLeft ? const Offset(-1, 0) : const Offset(1, 0),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _animate({double velocity = 0}) {
|
|
var description = const SpringDescription(
|
|
mass: 50,
|
|
stiffness: 1,
|
|
damping: 1,
|
|
);
|
|
var simulation = SpringSimulation(
|
|
description,
|
|
_controller.value,
|
|
1,
|
|
velocity,
|
|
);
|
|
_controller.animateWith(simulation).then<void>((_) {
|
|
widget.onSwiped();
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller.dispose();
|
|
super.dispose();
|
|
}
|
|
}
|