1.给每张牌贴标签

步骤:

  • 标签1:记录每张牌是否上升
  • 标签2:记录每张牌是否翻转
  • 初始化每张牌

注意: 牌是有很多张的,我们不能只用一个变量来记录所有牌,这也是第一种写法错误的原因

//写法1
bool isCardUp = false;
//写法2
List<bool> isCardUp = [];
//写法3
Map<int,bool> isCardUp = {};

选择2还是3,首先要弄懂List和Map的区别
List是按位置顺序找东西,没有名字(列表)
Map是按名字找东西,也就是键值对(映射)
如下

Map<int,bool> isCardUp = {
// 记录“每张牌是否上升”
  0: false, // 0号牌:不上升
  1: true,  // 1号牌:要上升
  2: false, // 2号牌:不上升
  3: true,  // 3号牌:要上升
};

这里我们的核心需求是按照牌的编号记录状态,而不是按顺序记录
比如我们记录22号牌上升,用Map只写isCardUp[22]=true,这一行就行,但是用List,要先给前22号填值,至少写22行
所以选写法3

class _DrawTarotcardState extends State<DrawTarotcard> {
  // 【关键1:给每张牌贴2个标签】
  // 标签1:记录每张牌“是否上升”(true=上升,false=不上升)
  Map<int, bool> isCardUp = {};
  // 标签2:记录每张牌“是否翻转”(true=翻转,false=不翻转)
  Map<int, bool> isCardTurn = {};

  
  void initState() {
    super.initState();//执行父类的基础初始化
    // 初始化:给0-9号牌(共10张)都贴“不上升、不翻转”的标签
    for (int i = 0; i < 10; i++) {
      isCardUp[i] = false;
      isCardTurn[i] = false;
    }

		
Widget build(BuildContext context) {
  // 这里没有重置逻辑,状态改了就改了
  return Scaffold(/* 你的卡牌页面 */);
  }

为什么for写在initState里面,不写在build里面?

原因:initState是页面的开机初始化函数,只执行一次,而build()会被flutter频繁调用,例如setState后本来isCardUp[2]=true,但是setState刷新页面后,又调用build(),把isCardUp[2]变成false了

2.点击卡牌要做的事

步骤:

  • 先上升
  • 后翻转

卡牌是先上升,等上升动画做完以后,再翻转,所以我们的这个函数要加上async,它就是用来标记这个函数有要等待的操作的
async必须和await配对使用

void TapCard(int index) async {
//上升
setState((){
isCardUp[index] = true;
});
//等待300毫秒
await Future.delayed(const Duration(milliseconds:300));
//翻转
setState((){
IsCardTurn[index] = true;
});
}

3.AnimatedContainer实现上升变大(隐式)

变大很简单,但是怎么上升呢?
用margin!margin是组件和父容器/其他组件之间的空隙,通过top:负数,可以实现上升

AnimatedContainer(
duration:Duration(millisecond:300),
margin:EdgeInsets.only(top:isSelected!?-2000,left:10,
width: isSelected ? 300 : 120,
height: isSelected ? 460 : 240

4.AnimatedSwitcher实现翻转(隐式)

AnimatedSwitcher(
  duration: const Duration(milliseconds: 500), // 翻转时长
  // 翻转效果(固定写法,不用懂,直接用)
  transitionBuilder: (child, anim) {
    return Transform.rotate(angle: anim.value * 3.14, child: child);
  },
  // 核心:根据“翻转标签”换图片/颜色
  child: isCardTurn[i]!
      ? Container(key: ValueKey('front'), ...) // 正面
      : Container(key: ValueKey('back'), ...), // 背面
)

Plus:抽出的三张牌牌按三角形性质固定

步骤

    1. 定义三角形的位置+把抽到的牌数和位置对应

具体也就是要实现第一张放顶部,第二张放左下,第三张放右下–>也就是说我们要给每张牌绑定抽取序号
例如selected = [3,7,12]
通过selected.asMap()
变成{0:3,1:7, 2:12}

selected.asMap()的作用是把只有值的列表(List),变成带有序号的"键(Key)值(Value)对集合"(Map)

现在我们得到的是Map()这个整体,但是我们要实现把这三张牌和三个位置分别绑定,如何拿到单个的牌呢?
通过Map的方法extries,extries可以把整体拆分成独立的键值对对象,然后我们再用一个map(),这个是遍历方法,可以逐个处理上面单独的Entry对象,类似for

selected.asMap().extries.map((entry){}).toList()l
//得到三个Entry对象
//Entry(key:0,value:3)
// Entry(key:1, value:7)
// Entry(key:2, value:12)

如果上面的这个代码被嵌套在Stack的children里面,结尾要带上**.toList()**,children里面只能是列表,而用了map()以后,处理完就是一个对象,不是列表了,使用加.toList()把结果变成列表

Widget buildSimpleTriangleCards() {
  // 直接定义3张牌的固定位置(单位:像素),想改位置只动这里!
  // key=选中顺序(0/1/2),value=[left, top] (left=距离屏幕左,top=距离屏幕顶)
  final trianglePos = {
    0: [180.0, 200.0],  // 第1张:顶部居中(手机屏幕宽约390,180=中间偏左,200=顶部往下200)
    1: [80.0, 350.0],   // 第2张:左下(左80,顶350)
    2: [280.0, 350.0]   // 第3张:右下(左280,顶350)
  };

  return Stack(
    children: selected.asMap().entries.map((entry) {
      int selectOrder = entry.key; // 选中顺序:0=第1张、1=第2张、2=第3张
      int cardIndex = entry.value; // 抽中的牌索引

      // 取当前牌的固定坐标
      double left = trianglePos[selectOrder]![0];
      double top = trianglePos[selectOrder]![1];

      // 固定位置渲染牌
      return Positioned(
        left: left,
        top: top,
        child: Image.asset(
          BackImage[cardIndex]!,
          width: 120, // 牌的宽度固定
          height: 200, // 牌的高度固定
          fit: BoxFit.cover,
        ),
      );
    }).toList(),
  );
}
    1. 满足条件就执行三角布局(条件渲染)
// Stack的children里
if (selected.isNotEmpty) buildSimpleTriangleCards(),
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐