4.4 动画与过渡:枝叶摇曳


4.4 动画与过渡:枝叶摇曳

本节摘要:浇水成功那一刻,界面若能"颤一下、弹一圈",用户才确信操作生效了——动画是界面的反馈语法。本节讲 UIView 动画的两段式闭包(改什么、多久)、弹簧参数的手感调节、能隐式动画与不能的属性清单、过渡动画 transition 与关键帧组合,顺带补上 CALayer 这层地基:动画真正的执行者在渲染服务里,不在你的代码里。

让浇水动作被看见

先看最朴素的反馈:

// 没有动画:瞬间到位,用户不确定点没点上 waterButton.transform = CGAffineTransform(scaleX: 0.8, y: 0.8) statusLabel.alpha = 0

UIView 动画把"瞬间"拉长成"过程",语法是两段式闭包——第一段声明"动画期间改什么",时长与曲线写在参数里:

// 基本形态:浇水按钮按下 → 缩小弹回 + 状态文案淡入 UIView.animate(withDuration: 0.2, animations: { self.waterButton.transform = CGAffineTransform(scaleX: 0.85, y: 0.85) // 按压感 }) { _ in // 完成回调:接续动作 UIView.animate(withDuration: 0.35) { self.waterButton.transform = .identity // 弹回原尺寸 self.statusLabel.alpha = 1 // 文案淡入 self.statusLabel.textColor = .systemGreen } } // 视觉效果:按钮缩一下弹回,倒计时文案同时浮现——一次操作,两段节奏

弹簧动画是 iOS 手感的招牌。参数只有两个:阻尼(damping,弹几下)与初速度(velocity):

// 浇水成功:状态标签从中心弹出并带回弹 statusLabel.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) statusLabel.alpha = 0 UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.55, // 阻尼 0.55:回弹一两次后停稳 initialSpringVelocity: 0.4, // 初速:像被轻推一把 options: [.curveEaseOut]) { self.statusLabel.transform = .identity self.statusLabel.alpha = 1 } // 阻尼手感对照:1.0 无回弹(沉稳);0.3 弹得欢(俏皮);0.1 橡皮筋(慎用)

哪些属性放进闭包就能动?记一张白名单:

能隐式动画 说明
frame / bounds / center 位置与大小
alpha 淡入淡出
transform 缩放、旋转、位移
背景色 颜色渐变

不能直接动的:层级顺序(要把视图移到上层用 transition 配合)、UILabel 的 text(文字本身不做形变动画,通常用淡入淡出包装)、约束对象本身(动画的是约束改完后的布局结果——把约束改动放进 animations 闭包,布局系统会在动画期间逐帧求解)。

图 4-4 五条缓动曲线与它们的性格

图 4-4 五条缓动曲线与它们的性格

过渡与关键帧:换内容、演多幕

属性动画管"同一个东西变形",过渡动画管"内容换血"——换文字、换图片、加删视图这类无法插值的突变:

// 过渡:浇水文案从"还剩 2 天"换成"已浇水" UIView.transition(with: statusLabel, duration: 0.3, options: .transitionCrossDissolve) { // 交叉溶解 self.statusLabel.text = "已浇水,3 天后再来" self.statusLabel.textColor = .systemGreen } // 常用过渡风格:翻页 .transitionFlipFromLeft、上推 .transitionCurlUp // 关键帧:一次浇水动效拆成三幕 UIView.animateKeyframes(withDuration: 1.1, delay: 0) { UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.25) { // 第一幕:按钮压缩 self.waterButton.transform = CGAffineTransform(scaleX: 0.8, y: 0.8) } UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.5) { // 第二幕:弹回放大 self.waterButton.transform = CGAffineTransform(scaleX: 1.15, y: 1.15) } UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25) {// 第三幕:稳回原位 self.waterButton.transform = .identity } } // 注意时间参数是 0...1 的相对值:0.25 段在 1.1 秒总长里就是 0.275 秒

地基一分钟:CALayer 与渲染服务

UIView 动画有个容易忽略的真相:动画期间你的属性"已经是终值"。animate 闭包执行完,layer 就到了终点;你看到的中间过程是渲染服务(独立的渲染进程)按曲线回放的"电影"。两个推论值得记住:

UIView.animate(withDuration: 2) { self.logo.center = CGPoint(x: 400, y: 300) } print(self.logo.center) // 立刻就是 (400.0, 300.0)——眼睛看到的还在飞,属性的账已结清 // 推论一:动画中途交互"摸"到的是 layer 的显示位置,逻辑位置已是终点 // 推论二:想取消动画,改的是 presentationLayer(显示层)的事务,不是再赋一次值

UIView 是layer 的管理者,动画、圆角、阴影这些"像素级"的事务其实都记在 CALayer 账上——4.5 节画进度环时会再碰到它。

案例:列表页"浇水成功"的全流程动效

背景:用户左滑标记已浇水(4.1),行内容变了但界面冷冰冰。操作:reload 改成过渡动画,行的图标加一次弹跳。结果与解读:

// 改造前:tableView.reloadRows(at: [indexPath], with: .automatic) // 改造后:图标的弹簧强调 + 行内容交叉溶解 let cellIcon = tableView.cellForRow(at: indexPath)?.imageView UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.6) { cellIcon?.transform = CGAffineTransform(scaleX: 1.4, y: 1.4) // 图标放大 } completion: { _ in UIView.animate(withDuration: 0.3) { cellIcon?.transform = .identity // 弹回 } UIView.transition(with: self.tableView, duration: 0.25, options: .transitionCrossDissolve) { self.tableView.reloadRows(at: [indexPath], with: .none) // 溶解中换数据 } } // 控制台无输出;视觉:图标弹一下,行的倒计时文案溶解成"还剩 N 天"

解读:动效的排布像鼓点——先强调(弹跳)再换内容(溶解),顺序错了信息就乱;completion 链是天然的节拍器,上一幕结束再开下一幕。变式:批量浇水时用关键帧让各行依次入场(每行延迟 0.05 秒的 delay 参数阶梯),列表像波浪一样刷新;动效时长总预算控制在半秒到一秒,再长用户会觉得 App 慢而不是 App 美。

⚠️ 常见坑:在动画闭包里做条件分支或数据修改。animations 闭包只放"可动画的属性变更",数据逻辑放闭包外——闭包可能在动画取消后不再执行,业务状态不能押在放映机上。

本节要点回顾

  • 两段式语法:animations 声明变化,completion 接续节拍,时长曲线写在参数;
  • 弹簧两参数:阻尼管回弹次数、初速度管起手,反馈动效首选;
  • 白名单:frame、alpha、transform、背景色可隐式动画;文字与层级要用过渡包装;
  • 过渡管突变、关键帧管多幕,关键帧时间是 0 到 1 的相对值;
  • 属性即终点:动画是渲染服务放的电影,业务逻辑别进放映机。

能动的都会动了,最后一件事:画出系统库里没有的东西——自定义视图与绘制。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U