本文章翻译自Yari D’areglia
在使用了 Twitter 的 iOS 应用一段时间后,我开始以开发者的眼光,注意到它副标题的移动,控件的交互非常有意思。这激起了我的好奇心,Twitter 开发者是怎么实现的?
让我们先大概看下效果图,是不是相当的优雅?看起来就像系统默认的图,但是如果你仔细看的话,你会发现更多。视图有重叠,整体随着 ScrollView 滑动放大缩小,协调流畅地变换… 我被迷住了,大爱这效果了。
让我们开始试试!
首先,先看看这次实现的最终效果图:
结构描述
在我们探究代码之前,先看看 UI 的结构。
先查看 Main.storyboard 文件。这 ViewController 可以看到两个物体。第一个是头部(包括白色 label),第二个是 scrollView,包含头像和其他相关信息(用户名和关注按钮)。最后底部有一个叫做 Sizer 的 view ,只是为了增加 ScrollView 的内容,以便能够竖直地滑动。
就像你看到的,这个结构非常的简单。只要你注意到头部是在 ScrollView 之外的,而不是和其他视图放在一起。这样使整个结构更加的灵活,便于修改。
LET’S CODE
如果你看了最终的效果图,有2个效果你应该可以轻松实现:
- 1)下拉(当 ScrollView 已经滑到最顶部)
- 2)上下滑动
第二个过程要被分解成4个步骤
- 2.1)上滑,header 的图片上移,直到高度和导航栏一样,然后大小保持不变。
- 2.2)上滑,头像变小
- 2.3)当 header 固定,头像移动到 header 背后
- 2.4)当写着用户姓名的 label 到达 header,一个新的白色 label 就显示在 header 的中间。header 的图片产生毛玻璃效果。
打开 ViewController.swift,让我们开始一步步实现。
创建控制器
很明显,首先我们需要获得 Scrollview 的偏移 offset,这通过 UIScrollViewDelegate
的代理方法 scrollViewDidScroll
很容易实现。
对 view 实现大小变换,最简单就是使用 Core Animation
实现同类型的3D变换,将新的值赋予 layer.transform
属性即可。
这里有一篇关于 core Animation 的文章,有兴趣的话可以看看:
这是在 scrollViewDidScroll
方法下代码的前几行:var offset = scrollView.contentOffset.y
var avatarTransform = CATransform3DIdentity
var headerTransform = CATransform3DIdentity
这里我们获得竖直方向上的 offset,然后初始化 2个 transformation,后面会用到。
下拉
开始下拉操作:if offset < 0 {
let headerScaleFactor:CGFloat = -(offset) / header.bounds.height
let headerSizevariation = ((header.bounds.height * (1.0 + headerScaleFactor)) - header.bounds.height)/2.0
headerTransform = CATransform3DTranslate(headerTransform, 0, headerSizevariation, 0)
headerTransform = CATransform3DScale(headerTransform, 1.0 + headerScaleFactor, 1.0 + headerScaleFactor, 0)
header.layer.transform = headerTransform
}
首先确认 offset.y
是否是负值的,这意味着下拉操作开始,header图片开始放大。
剩下的就是简单的数学计算了。
headerScaleFactor 通过比例关系计算而来。也就是说偏移量为 header高度的2倍,那么 ScaleFactor 就为2.0。
只要注意它的顶部到屏幕边缘的距离是固定的,图片按比例放大。
第二个部分我们需要处理上下滑动。让我们看看如何一个一个实现。
header(第一阶段)
现在当前的偏移是大于0的。头部需要随着偏移竖直地滑动,知道达到预定的高度(这个在后面“毛玻璃”会讲到)。headerTransform = CATransform3DTranslate(headerTransform, 0, max(-offset_HeaderStop, -offset), 0)
这次代码相当简单。只要移动header到预定的位置。
说起来惭愧,我太懒了,把一些值写死,比如 offset_HeaderStop。应该有更优雅地写法来计算控件的位置。
头像(avatar)
头像缩放跟我们的下拉操作是相同的逻辑。但在这个例子,头像的底部固定而不是顶部。这里代码很相似,除了缩放动画。// Avatar -----------
let avatarScaleFactor = (min(offset_HeaderStop, offset)) / avatarImage.bounds.height / 1.4 // Slow down the animation
let avatarSizeVariation = ((avatarImage.bounds.height * (1.0 + avatarScaleFactor)) - avatarImage.bounds.height) / 2.0
avatarTransform = CATransform3DTranslate(avatarTransform, 0, avatarSizeVariation, 0)
avatarTransform = CATransform3DScale(avatarTransform, 1.0 - avatarScaleFactor, 1.0 - avatarScaleFactor, 0)
如你所见,我们使用了 min
函数去停止头像缩放,当 header 变换停止的时候。
这里,我们根据偏移来判断哪个图层放在最前面。if offset <= offset_HeaderStop {
if avatarImage.layer.zPosition < header.layer.zPosition{
header.layer.zPosition = 0
}
}else {
if avatarImage.layer.zPosition >= header.layer.zPosition{
header.layer.zPosition = 2
}
}
白色label
这个是白色 label 的动画let labelTransform = CATransform3DMakeTranslation(0, max(-distance_W_LabelHeader, offset_B_LabelHeader - offset), 0)
headerLabel.layer.transform = labelTransform
这里有2个写死的变量(羞愧。。):当偏移量为 offset_B_LabelHeader,黑色用户名的 label 到达了 header 的底部。
distance_W_LabelHeader 是 header 底部到白色 label 中心的距离。
变换计算的逻辑是:当黑色 label 接触到 header,白色 label 显示,当到达 header 的中心停止。max(-distance_W_LabelHeader, offset_B_LabelHeader - offset)
毛玻璃
最后一个效果是头部的毛玻璃。我找了3个不同的库去解决这个问题,甚至尝试过超级简单的 openGL ES -_-。但是实时更新毛玻璃效果总会存在延迟现象。
机智的我发现,只要计算毛玻璃效果一次,然后覆盖在未转化的图片上,改变它的透明度即可。相信 Twitter 的开发者也是这么实现的。
在 viewDidAppear
下,我们计算出了毛玻璃效果的 header,然后设置透明度为0,隐藏它:// Header - Blurred Image
headerBlurImageView = UIImageView(frame: header.bounds)
headerBlurImageView?.image = UIImage(named: "header_bg")?.blurredImageWithRadius(10, iterations: 20, tintColor: UIColor.clearColor())
headerBlurImageView?.contentMode = UIViewContentMode.ScaleAspectFill
headerBlurImageView?.alpha = 0.0
header.insertSubview(headerBlurImageView, belowSubview: headerLabel)
blurred view 这个 view 使用了第三方库 FBBlurView
在 scrollViewDidScroll
方法下,我们根据 offset 更改透明度即可。headerBlurImageView?.alpha = min (1.0, (offset - offset_B_LabelHeader)/distance_W_LabelHeader)
这个计算的逻辑就是要使最大值不超过1,毛玻璃效果必须在黑色label到达头部的时候开始变化,当白色label在最终位置的时候停止变化。
这就是全部
希望你能享受这个教学过程。学习如何重新实现有趣的动画效果对我来说相当有意思。
你可以在这里下载 demo