iOS滚动视差效果的实现(翻译)

原文链接:Gorgeous parallax scrolling with UITableViewCells

原作者:Krishan

译者:真珠奶茶小土逗

视差滚动是一个非常强大的武器,它可以帮助我们实现非常炫酷的展示效果。如果不能够正确使用,它将会搞得我们非常恼火,但是当我们实现它的时候,它就会展示出它非凡的魔力。对我而言很幸运,我喜欢钻研一些新奇的玩意儿,因此我已经迫不及待想要实现它了😝。

今天我将展示我是如何在一个TableView的Cell中实现视差滚动的效果。我之前没有看到像我这样使用布局约束实现的方法,所以我觉得我应该拿出来和你们一起分享。如果你不想听我在这里长篇大论,你可以直接查看我在github上的示例项目

现在我们假定你已经设置好了一个TableView并且已经有了一个包含Image的UITableViewCell的子类,我这里把它定义为ImageCell。注意:一定要将Image的显示设置为Aspect Fill,处理视差滚动这个设置至关重要!

我的没有添加视差滚动的基础工程看起来像这样:

初始工程示例.gif

我们希望每个cell的视差偏移依赖于cell在TableView中的位置,因此我们将在TableView中调用一个滚动监听方法来使cell的背景Image发生偏移。

这就是我们的目标:

目标.jpg

当cell(红色矩形框)在屏幕底部的时候,Image固定在cell的顶部,当cell在屏幕顶部的时候,Image固定在cell的底部。

所以,首先让我们来看一下如何在TableView中监视一个cell的位置。将下面的代码加入到你的已经创建了TableView的ViewController中:

func scrollViewDidScroll(scrollView: UIScrollView) {
    if (scrollView == self.tblMain) {
        for indexPath in self.tblMain.indexPathsForVisibleRows() as! [NSIndexPath] {
            self.setCellImageOffset(self.tblMain.cellForRowAtIndexPath(indexPath) as! ImageCell, indexPath: indexPath)
        }
    }
}

注意:我的UITableView使用Outlet连接,叫做tblMain

上面的那个方法监视Table的滑动进度,查看哪个cell是可见的,然后对可见的cell调用setCellImageOffset方法。下面是setCellImageOffset方法的具体实现:

func setCellImageOffset(cell: ImageCell, indexPath: NSIndexPath) {
    var cellFrame = self.tblMain.rectForRowAtIndexPath(indexPath)
    var cellFrameInTable = self.tblMain.convertRect(cellFrame, toView:self.tblMain.superview)
    var cellOffset = cellFrameInTable.origin.y + cellFrameInTable.size.height
    var tableHeight = self.tblMain.bounds.size.height + cellFrameInTable.size.height
    var cellOffsetFactor = cellOffset / tableHeight
    cell.setBackgroundOffset(cellOffsetFactor)
}

这个方法有一点复杂,下面我们将逐步说明:

1.我们找到了cell在TableView中的frame;
2.根据表的父视图的坐标计算cell的frame,这是为了让我们在屏幕上获取cell的位置而不是它在列表中的位置(列表中每个cell都是固定的);
3.从顶部获取cell的偏移,这本来应该只是在表格中cell的y坐标,但是我们在此基础上添加cell的高度,目的是即使cell的顶部已经超过边缘,它也可以保持视差;
4.获取表格的可见高度。同样的在Table原有高度的基础上添加cell的高度,即使cell部分移出屏幕,图像也可以保持移动;
5.我们通过将表格的可见部分中的cell的位置除以表的可见部分的总高度,计算我们要偏移背景的程度(一个比值,范围从0到1)。

哇,还是比较困难的!现在我们需要在ImageCell中实现setBackgroundOffset方法来更新图像,现在的ImageCell看起来像这样:

class ImageCell: UITableViewCell {
    @IBOutlet weak var imgBack: UIImageView!
    //...(other stuff like title)...

首先是连接图像顶部和底部的布局约束。如果你使用了顶部+高度组合来添加约束(或底部+高度),则需要稍微调整以下步骤。(译者注:下面的操作步骤是都以顶部+底部的约束来实现的)

接下来找到图像的顶部和底部的约束,并将它们连接到类中的Outlets,现在的代码应该类似这样:

class ImageCell: UITableViewCell {
    @IBOutlet weak var imgBack: UIImageView!
    @IBOutlet weak var imgBackTopConstraint: NSLayoutConstraint!
    @IBOutlet weak var imgBackBottomConstraint: NSLayoutConstraint!

    let imageParallaxFactor: CGFloat = 20

    var imgBackTopInitial: CGFloat!
    var imgBackBottomInitial: CGFloat!
    ...
}

你们敏锐的眼睛👀 可能已经注意到我在上面添加了一些额外的变量。 imageParallaxFactor是一个控制变量,我们将使用它来定义我们想要的视差多少,后面我们再说这个有趣的变量😋。imgBack *初始变量用于跟踪约束的起始值。现在我们来设定一下。

我们可以在UITableViewCell的awakeFromNib方法中对cell进行初始化,像下面这样(感谢/u/lyinsteve指出这一点):

override func awakeFromNib() {
    self.clipsToBounds = true
    self.imgBackBottomConstraint.constant -= 2 * imageParallaxFactor
    self.imgBackTopInitial = self.imgBackTopConstraint.constant
    self.imgBackBottomInitial = self.imgBackBottomConstraint.constant
}

让我们捋一下这个代码块:

1.允许cell裁剪它的边界,这符合我这里的实际情况,因为我的图像占用了整个cell的背景,所以如果你的单元格更小,你将需要不同的裁剪;
2.我们将底部约束设置为原始值减去2 *视差量。这具有延长图像的效果;
3.最后记录约束的初始值。

现在来实现setBackgroundOffset方法:

func setBackgroundOffset(offset:CGFloat) {
    var boundOffset = max(0, min(1, offset))
    var pixelOffset = (1-boundOffset)*2*imageParallaxFactor
    self.imgBackTopConstraint.constant = self.imgBackTopInitial - pixelOffset
    self.imgBackBottomConstraint.constant = self.imgBackBottomInitial + pixelOffset
}

这是滚动视差的核心部分。在这里,我们移动图像的顶部和底部约束,使其与偏移上下移动,移动的核心原则是:

  • 在cell的偏移量/Table的高度=0时(单元格即将从屏幕顶部滚动):
    top = 2 * imageParallaxSize
    bottom = 0
  • 在cell的偏移量/Table的高度=1时(单元格即将从屏幕底部消失):
    top = 0
    bottom = 2 * imageParallaxSize

(译者注:这个核心原则可能比较难以理解,我也看了很久,在这里说一下我对这个原则的简单看法来帮助大家更好地理解作者的思路:作者实现视差滚动的原理是通过监测cell的滚动拿到一个滚动后的位置和原位置的差值,将这个差值除以Table的高度得到一个比值,将这个比值通过上述算法,即setBackgroundOffset(offset:CGFloat)得到一个约束的偏差值,我们在滚动cell时改变Image的上下约束即可实现视差滚动的效果,下面我逐行分析一下这个核心方法的代码:
1.偏差比值有可能大于1,而之前我们设置的比值是0到1的,所以需要处理一下传入的偏差比值,将其控制在0到1之间;
2.根据上一步算出的偏差比值计算Image约束的偏差值,imageParallaxFactor是一个控制偏差大小的变量,至于为什么要用1-boundOffset,这个说实话我也不是特别清楚,在我看来直接使用boundOffset反而才是正确的,但是通过测试发现两种方式都可以实现我们想要的效果,目前这块儿比较疑惑,如果有读者可以理解的话欢迎反馈给我,大家共同进步😉;
3.减小上边界约束;
4.增加下边界约束。)

现在运行这个项目,你会发现并不是我们想要的效果😓,它本应该是很酷炫的。剩下的唯一一个问题就是:视图的首次加载和您在屏幕上的首次滑动之间有一个突然的移动。这是因为背景图像的初始位置仍然是我们在storyboard中指定的(以及我们上面的细微调整)。这些单元格在它们放置在桌面视图中时已经被偏移了,但是由于没有滚动,背景还没有偏移,所以我们来解决一下。

下面的解决方法是很棒的。我们需要调用一个很方便的方法:tableView: willDisplayCell: forRowAtIndexPath。这个方法在第一次显示cell的时候被调用来完成视图的初始化。所以在这里,我们将根据cell放置的位置来偏移cell的背景View:

func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
    var imageCell = cell as! ImageCell
    self.setCellImageOffset(imageCell, indexPath: indexPath)
}
最终结果.gif

(如果您在gif效果图中看不到视差,请关注标题和相对于背景的位置😉)

注意:我将视差设置为了一个较高的值(70),目的是为了让视差效果在gif图中更好地展示,我强烈不建议在实际项目中这么做,视差的值设置为0.1*cell的高度是比较合适的👍。

很棒,我们已经完成了!如果您觉得看这些长篇大论太过麻烦,您可以看一下下面这两个主要的类,它们比较简短,您可以从中快速找到或获取您想要的东西😛:

ViewController on GitHub

ImageCell on GitHub

感谢阅读!像往常一样,欢迎留下您的评论和想法!

(译者注:本文翻译已获得作者授权)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,623评论 4 59
  • 昨天下午开始,保亭下大雨了。 下午去医院办事,路上滴滴答答下毛毛雨,以为海南岛上的气候 ,猫儿脸,说变就变,一片云...
    潇萍阅读 542评论 1 0
  • 昨天晚上看红楼,又重温了“平儿理妆”这个场景,一时间思绪纷飞,今天忍不住动笔,与大家聊一聊由红楼而引发的古人化妆这...
    玄枵馆主阅读 809评论 0 4
  • 老板,我们现在遇到一个问题(1-2 句话),它会造成什么影响(1-2 句话),我们已经采取了什么措施(1-2 句话...
    明亿阅读 242评论 0 0