1、创建一个文件SwipeCard.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
/// Card左右选择回调
typedef SwipeCardSelected = Function(bool right);
/// 实现SwipeCard效果
class SwipeCard extends StatefulWidget {
final List<Widget> children;
final SwipeCardSelected cardSelected;
final int slideType;//0 左右滑动 1上下左右滑动
final SwipeCardOrientation orientation;
SwipeCard(this.cardSelected, {this.children = const <Widget>[],this.slideType = 0,this.orientation = SwipeCardOrientation.bottom});
@override
State<StatefulWidget> createState() {
return _SwipeCardState();
}
}
enum Direction { left,//向左滑动
right,//向右滑动
stay //无需滑动
}
//展示方向
enum SwipeCardOrientation{
left,
right,
bottom,
top
}
class _SwipeCardState extends State<SwipeCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
double _moveX = 0, _moveY = 0;
double _scale = 0;
double _upX = 0, _upY = 0, _upScale = 0;
Direction _direction = Direction.stay;
int current = 0;//当前下标
_SwipeCardState();
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 300),
lowerBound: 0,
upperBound: 1.0);
_controller.addListener(() {
setState(() {
double width = context.size!.width;
if (_direction == Direction.right) {
_scale = _upScale + (1 - _upScale) * _controller.value;
_moveX = _upX + (width - _upX) * _controller.value;
} else if (_direction == Direction.left) {
_scale = _upScale + (1 - _upScale) * _controller.value;
_moveX = _upX + (-width - _upX) * _controller.value;
} else {
_scale = _upScale + (0 - _upScale) * _controller.value;
_moveX = _upX + (0 - _upX) * _controller.value;
_moveY = _upY + (0 - _upY) * _controller.value;
}
});
});
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
if (_direction == Direction.left) {
widget.cardSelected(false);
} else if (_direction == Direction.right) {
widget.cardSelected(true);
}
//有滑动的情况下进行下标++
if(_direction != Direction.stay){
current++;
if(current >=widget.children.length){
current =0;
}
}
setState(() {
_moveX = 0;
_moveY = 0;
_scale = 0;
});
}
});
}
@override
void dispose() {
super.dispose();
_controller.dispose();
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// final double right = constraints.maxWidth * 0.025;
//第一层宽高
double width = constraints.maxWidth;
double height = constraints.maxWidth;
//移动比例
//上下布局使用maxHeight 计算移动比例
double proportion = constraints.maxHeight * 0.025;
//左右布局使用maxWidth 计算移动比例
if(widget.orientation == SwipeCardOrientation.right || widget.orientation == SwipeCardOrientation.left){
proportion = constraints.maxWidth * 0.025;
}
//计算第二层卡片x,y 默认向下布局
double layer1X = constraints.maxWidth * 0.05;
double layer1Y = (constraints.maxHeight * 0.05) + proportion ;
double width1 = constraints.maxWidth - 2 * proportion;
double height1 = constraints.maxHeight + 2 * proportion;
//缩放比例
double layer1Scale = 0.95;
//计算第三层卡片x,y 默认向下布局
double layer2X = constraints.maxWidth * (1 - 0.95 * 0.95) ;
double layer2Y = (constraints.maxHeight * (1 - 0.95 * 0.95)) + proportion * 2;
double layer2Scale = layer1Scale * layer1Scale;
double width2 = constraints.maxWidth - 3 * proportion;
double height2 = constraints.maxHeight + 3 * proportion;
//向上布局
if(widget.orientation == SwipeCardOrientation.top){
layer1Y = (constraints.maxHeight * 0.05) - proportion * 3 ;
layer1X = constraints.maxWidth * 0.05 - proportion;
width1 =constraints.maxWidth - 2 * proportion;
height1= constraints.maxHeight - 2 * proportion;
height = constraints.maxHeight + 2 * proportion;
layer2Y = (constraints.maxHeight * (1 - 0.95 * 0.95)) - proportion * 6 ;
layer2X = constraints.maxWidth * (1 - 0.95 * 0.95) - proportion;
width2 =constraints.maxWidth - 3 * proportion;
height2= constraints.maxHeight -3 * proportion;
}else if(widget.orientation == SwipeCardOrientation.bottom){
layer1Y = (constraints.maxHeight * 0.05) + proportion * 3 ;
layer1X = constraints.maxWidth * 0.05 - proportion;
width1 =constraints.maxWidth - 2 * proportion;
height1= constraints.maxHeight - 2 * proportion;
height = constraints.maxHeight + 2 * proportion;
layer2Y = (constraints.maxHeight * (1 - 0.95 * 0.95)) + proportion * 2 ;
layer2X = constraints.maxWidth * (1 - 0.95 * 0.95) - proportion;
width2 =constraints.maxWidth - 3 * proportion;
height2= constraints.maxHeight +3 * proportion;
}else if(widget.orientation == SwipeCardOrientation.right){
layer1X = constraints.maxWidth * 0.05 + proportion *2;
// layer1Y = (constraints.maxHeight * 0.05) ;
width = constraints.maxWidth - proportion;
height1 = constraints.maxHeight - 3 * proportion;
layer2X = constraints.maxWidth * (1 - 0.95 * 0.95) + proportion * 4;
layer2Y = (constraints.maxHeight * (1 - 0.95 * 0.95)) + proportion ;
height2 = constraints.maxHeight - 4 * proportion;
}else if(widget.orientation == SwipeCardOrientation.left){
layer1X = constraints.maxWidth * 0.05 - proportion *3;
// layer1Y = (constraints.maxHeight * 0.05) ;
width = constraints.maxWidth - proportion ;
height1 = constraints.maxHeight - 3 * proportion;
layer2X = constraints.maxWidth * (1 - 0.95 * 0.95) - proportion * 6;
layer2Y = (constraints.maxHeight * (1 - 0.95 * 0.95)) + proportion;
height2 = constraints.maxHeight - 4 * proportion;
}
if (_scale > 1) {
_scale = 1;
}
List<Widget> children = [];
if (widget.children.length > 2) {
children.add(Opacity(
opacity: 0.8 * 0.8 + 0.8 * 0.2 * _scale,
child: Transform.scale(
scale: layer2Scale + (layer1Scale - layer2Scale) * _scale,
alignment: Alignment.topLeft,
child: Transform.translate(
offset: Offset(layer2X + (layer1X - layer2X) * _scale,
layer2Y + (layer1Y - layer2Y) * _scale),
child: SizedBox(
child: widget.children[(current+2)%(widget.children.length)],
//right
// width: constraints.maxWidth - 2 * proportion,
// height: constraints.maxHeight - 3 * proportion ,
//bottom
width: width2,
height: height2 ,
),
),
),
));
}
if (widget.children.length > 1) {
children.add(Opacity(
opacity: 0.8 + 0.2 * _scale,
child: Transform.scale(
scale: layer1Scale + (1 - layer1Scale) * _scale,
alignment: Alignment.topLeft,
child: Transform.translate(
offset: Offset(layer1X + (0 - layer1X) * _scale,
layer1Y + (0 - layer1Y) * _scale),
child: SizedBox(
child: widget.children[(current+1)%(widget.children.length)],
//right
// width: constraints.maxWidth - 2 * proportion,
// height: constraints.maxHeight - 2 * proportion,
//bottom
width: width1 ,
height: height1,
),
),
),
));
}
if (widget.children.length > 0) {
children.add(Opacity(
opacity: _moveX == 0 ? 1: 0.8 + 0.2 *_scale,
child: Transform.scale(
scale: 1,
child: Transform.translate(
offset: Offset(_moveX, _moveY),
child: SizedBox(
child: widget.children[(current)%(widget.children.length)],
//right
// width: constraints.maxWidth- 2 * proportion,
// height: constraints.maxHeight - proportion,
//bottom
width: width,
height: height,
),
),
),
));
}
return GestureDetector(
child: Stack(
children: children,
),
onPanUpdate: _onPanUpdate,
onPanEnd: (DragEndDetails? details) {
setState(() {
_upX = _moveX;
_upY = _moveY;
_upScale = _scale;
if (_isToRight()) {
_direction = Direction.right;
} else if (_isToLeft()) {
_direction = Direction.left;
} else {
if (details != null) {
if (details.velocity.pixelsPerSecond.dx > 1000) {
_direction = Direction.right;
} else if (details.velocity.pixelsPerSecond.dx < -1000) {
_direction = Direction.left;
} else {
_direction = Direction.stay;
}
} else {
_direction = Direction.stay;
}
}
if(widget.children.length == 1) {
_direction = Direction.stay;
}
_startAnim();
});
},
onPanCancel: () {
setState(() {
_moveX = 0;
_moveY = 0;
_scale = 0;
});
});
});
}
void _onPanUpdate(DragUpdateDetails details) {
if(widget.slideType == 0) {
_moveX += details.delta.dx;
}else if( widget.slideType == 1) {
_moveX += details.delta.dx;
_moveY += details.delta.dy;
}
var size = context.size;
// var offset = details.localPosition;
var distance = widget.slideType == 1? _moveY / size!.width :_moveX / size!.width;
setState(() {
_scale = distance < 0 ? -distance : distance;
});
}
bool _isToRight() {
double width = context.size!.width;
return _upX > width / 4;
}
bool _isToLeft() {
double width = context.size!.width;
return _upX < -width / 4;
}
void _startAnim() {
_controller.reset();
_controller.forward(from: 0);
}
}
2、在创建一个demo.dart文件
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:test_flutter/utils/SwipeCard.dart';
class SwipeCardDemo extends StatefulWidget {
const SwipeCardDemo({super.key});
@override
State<SwipeCardDemo> createState() => _SwipeCardDemoState();
}
class _SwipeCardDemoState extends State<SwipeCardDemo> {
Widget getList(slideType,SwipeCardOrientation swipeCardOrientation,text){
List<Widget> listWidget = [];
List<MaterialColor> colorss=[Colors.blue,Colors.red,Colors.amber,Colors.blueGrey,Colors.deepPurple];
for(var i=0;i<10;i++){
Widget card1 = Card(
elevation: 5,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
side: BorderSide(
color: Color.fromARGB(50, 0, 0, 0),
width: 0.1,
),
),
shadowColor:Color.fromARGB(80, 0, 0, 0),
child: Container(
width: double.infinity,
color: colorss[i % colorss.length],
height: 130,
child: Text("我是$text布局第$i页",style:const TextStyle(fontSize: 30,color: Colors.black),),
));
listWidget.add(card1);
}
return Container(
alignment: Alignment.center,
padding: EdgeInsets.only(left: 10,right: 10),
child: SwipeCard((bool right){
print("SwipeCard = ${right}");
},children:listWidget,slideType: slideType,orientation: swipeCardOrientation),
width: double.infinity,
height: 140,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [
Padding(padding: const EdgeInsets.only(top: 10),),
getList(0,SwipeCardOrientation.bottom,"向下"),
Padding(padding: const EdgeInsets.only(top: 10),),
getList(0,SwipeCardOrientation.left,"向左"),
Padding(padding: const EdgeInsets.only(top: 10),),
getList(0,SwipeCardOrientation.top,"向上"),
Padding(padding: const EdgeInsets.only(top: 10),),
getList(1,SwipeCardOrientation.right,"向右")
],
),
);
}
}