Flutter 仿探探卡片滑动效果(上下左右)

        
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,"向右")
          ],
      ),
    );
  }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小曾老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值