自定义UICollectionView布局实战:CardsAgainst白卡滑动渐隐效果的实现原理
2026/8/22 14:58:23 网站建设 项目流程

自定义UICollectionView布局实战:CardsAgainst白卡滑动渐隐效果的实现原理

【免费下载链接】CardsAgainstAn iOS game for horrible people项目地址: https://gitcode.com/gh_mirrors/ca/CardsAgainst

CardsAgainst 是一款用 Swift 编写的 iOS 卡牌对战游戏(An iOS game for horrible people),它基于 Multipeer Connectivity 实现局域网内的多人组局。除了联机能力,这个项目还有一个值得学习的小亮点:自定义 UICollectionView 布局——白卡列表在向上滑动时,卡片会从屏幕顶部以"渐隐"方式淡入,而不是生硬地出现。今天我们就用通俗的方式拆解这个滑动渐隐效果的实现原理,带你看懂 iOS 自定义布局的核心套路。

一、先认识这个"白卡渐隐"效果

游戏进行到答题环节时,玩家会看到一张黑卡(题目)和下方一叠白卡(选项)。当白卡数量较多、需要上下滑动浏览时,新滑入视野的卡片并不是直接"蹦"出来的,而是从顶部透明处缓缓变淡入变深,整个过程只有约 120 点的过渡区域,配合缓动曲线,视觉上非常柔和。

这个效果的全部代码量不到 50 行,全部集中在一个文件里:

  • 布局核心:CardsAgainst/Controllers/WhiteCardFlowLayout.swift
  • 卡片外观:CardsAgainst/Views/WhiteCardCell.swift
  • 列表集成与滚动逻辑:CardsAgainst/View Controllers/GameViewController.swift

二、实现自定义 UICollectionView 布局的两大抓手

在 UIKit 中做自定义布局,最常见的起点是继承UICollectionViewFlowLayout(流式布局),然后只重写你关心的两个方法。CardsAgainst 正是这样做的:

1. 重写layoutAttributesForElements(in:):给每张卡"算透明度"

这个方法负责返回可见区域内所有单元格的布局属性。WhiteCardFlowLayout先调用父类拿到标准布局结果,然后对每张卡做一次判断:

  • 把卡片的y坐标换算到父视图坐标系(collectionView.convert(...));
  • 如果卡片位置仍藏在顶部内容留白(contentInset)之内,说明它"还藏在屏幕上方";
  • 计算它距离露出的程度progress(过渡区域固定为 120 点),再用自定义缓动函数换算成alpha,范围从 0.05 到 1:
let difference = topContentInset - yOriginInSuperview let progress = difference / transitionRegion attributes.alpha = easeInOut(min(progress, 1), b: 1, c: -0.95, d: 1)

也就是说:藏得越深,越透明;滑得越多,越不透明。这就是"渐隐"的本质——不是动画,而是每帧根据位置实时计算透明度。

2. 重写shouldInvalidateLayout(forBoundsChange:):让布局"跟着手指走"

这是最容易被忽略、却最关键的一行:

override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { return true }

它告诉系统:滚动(bounds 变化)时也要重新计算布局。因为layoutAttributesForElements里的透明度依赖滚动位置,只有持续重算,卡片才会随手指实时淡入淡出。默认流式布局会在内容不变时跳过重算以省性能,而这里我们主动要求"每次都算"——好在只是简单数学运算,代价极低。

三、渐隐效果的灵魂:手写 easeInOut 缓动函数

线性淡入会显得机械,好的渐隐需要一个"先慢、再快、再慢"的节奏。WhiteCardFlowLayout.swift顶部自己实现了一个经典的三次缓动函数easeInOut

  • 进度t在前半程走t*t*t,起步更缓;
  • 后半程对称加速收尾。

progress喂给这个函数,得到的alpha曲线就比线性插值自然得多。这个小函数虽然只有几行,却是很多商业 App 中"手感好"的秘密之一。

四、在游戏主界面中的集成方式

效果最终如何落地?看GameViewController.swift的集成方式(约第 34 行起):

  • 创建UICollectionView时直接传入WhiteCardFlowLayout()作为布局对象,零额外配置;
  • 通过contentInset.top把黑卡题目区域的空间让给列表——注意:渐隐过渡的"顶部"正是这个 contentInset 区域,所以布局代码里用的是collectionView.contentInset.top + 20
  • 每次补充新卡、删除已选卡时,用performBatchUpdates配合reloadData()刷新,滚动后列表会scrollRectToVisible回顶,让渐隐效果在每一轮都自然重演;
  • 白卡单元格WhiteCardCell本身只负责外观:圆角 8 点的浅色背景 + 自动换行的 UILabel,用 Cartography 约束文字边距。

这种"布局归布局、外观归 Cell、时机归 ViewController"的分工,正是自定义布局项目的清晰架构。

五、新手抄作业清单:5 个关键点

  1. 继承UICollectionViewFlowLayout,只重写需要的钩子方法,不要从零写UICollectionViewLayout
  2. convert(_:to:)统一坐标系再比较位置,否则 contentInset、嵌套滚动会让坐标计算全乱;
  3. 过渡区域(transitionRegion)是个魔法数字,按视觉调整,本例是 120 点;
  4. 记得重写shouldInvalidateLayout(forBoundsChange:)返回true,否则效果只在首次加载时出现一次;
  5. min(progress, 1)做截断,避免卡片滑出区域后progress越界算出异常透明度。

掌握这套"位置 → 进度 → 缓动 → alpha"的公式,你完全可以把它迁移到自己的项目里:商品列表的淡入、新闻流的渐显、卡片堆叠的入场动画,都是同一个原理的变体。想进一步学习,可以直接阅读 WhiteCardFlowLayout.swift、WhiteCardCell.swift 与 GameViewController.swift 这三个文件,全部核心逻辑一目了然。

【免费下载链接】CardsAgainstAn iOS game for horrible people项目地址: https://gitcode.com/gh_mirrors/ca/CardsAgainst

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询