重新创建本机iOS滚动和动量

我们在Unity中重新创建了iOS原生滚动条,并充满了动力。 我不会显示很多代码,但是我将与您分享Apple本身使用的原理和不可思议的数字。

我可以肯定,我不是唯一一个因在UX时代的2017年并不是每个卷轴都相同而感到高兴的人。 再次考虑,对所有内容进行标准化可能是一个错误的决定,尽管您可能会认为一种涡旋物理比另一种更好,但这实际上是一个见解的问题。 我老板的观点是,我们需要在iOS移动应用程序中实现iOS滚动物理的克隆。

您为什么要在移动应用中使用U nity

—这就是我每天问自己的问题。

不管为什么,我们都解决了这个问题。 但是我们发现的结果会让您感到惊讶! 是的,我们用C#做到了这一点-但谁在乎,底层算法很重要,而不是语言。

不想听所有的戏吗?

只需滚动到底部。

NGUI和Unity UI

从一开始,我们就知道不可能在Asset Store上找到完成的产品/插件并仅仅实施它,但我也知道我们的老板是一位老派的高级程序员,并且不会拒绝。 因此,我们研究了NGUI和本机Unity UI滚动视图的物理特性。 这太可怕了。

虽然我们无法完全检查Unity UI的内部工作原理,但通过观察我们可以学到很多东西。 简而言之,开发人员似乎很着急,没有花时间对其他平台如何处理UI元素滚动进行适当的研究。 也许他们没有被告知要照顾? 毕竟,Unity UI不能用于完整的移动应用程序(在我们的例子中是社交网络)。 没有平台是相同的,但是到目前为止,我们知道Android和iOS在以下几件事上都达成共识:

  1. 在视口之外禁用或重用元素
  2. 建立动力并坚持到底
  3. 有一些弹簧机制或类似的实现
  4. 水平滚动和垂直滚动不应共存

让我们使用字体粗细:1700 对于第一个。 苹果和谷歌有一个很好的理由来解决所有使东西消失或失效的麻烦,即性能问题。 当您使用Unity UI滚动包含10个项目的游戏内菜单时,就纯fps性能而言,它表现良好。 但是,一旦您开始用各自的个人资料图片来表示从Facebook API渲染的所有1244个联系人,那么一切都会变得一团糟。 内存和CPU上升,而fps下降。 我发现iOS使用了一种很酷的技术-它会跟踪元素何时离开视口(为了大家,让我们称之为它)以及何时离开,将元素清空,填充新数据并附加到列表的底部。 因此,列表永远不会有超过12–13个元素一直在被重用。 我们认为此功能是必须的,但稍后会介绍更多。

当然,Unity UI不会这样做,正如我的同事Miloš告诉我的那样,NGUI似乎是一个半开发的系统,从未在任何地方使用过(呵呵,对吗?)。

至于它们的动力和春季选择,我们整天都在对它们进行自定义,但它们从未按预期工作。

将研究转移到网络上

在无奈之下,我在StackOverflow上发布了一个问题(里面有扰流板):

需要帮助剖析并基于PastryKit重新创建完美的滚动缓动
对于Web,我通常只使用本机滚动机制。 它们快速,可靠并且不涉及编码。 但是… stackoverflow.com

对于那些由于剧透而选择不读取S / O线程的人来说,这就是麻烦所在。 我问是否有人对苹果的动力物理有任何想法,而我得到的回馈是使我们“不使用Unity.UI”或“仍然像95岁时仍在使用NGUI”的人感到烦恼。 很成熟

我认为就足够了,所以我开始在其他地方寻求帮助,年长的同事很少对此事发表任何评论,并且网络论坛上满是询问相同问题的人(需要多种语言),但没有得到答案或像我一样得到类似的回应。 我记得有些JS库试图模仿这种行为。 有一次,Apple不允许将固有动量应用于网络上的overflow:scroll-y元素,而在移动设备上获取position:fixed标头的唯一方法是将整个主体包装成两个具有溢出滚动的同级div。 (目前,这似乎已经解决了)因此,开发人员投掷了一堆写得不好的JS,并创建了称为iScroll的东西。 如果您不关心基础代码,那么这是一个很棒的55kb库,可自动创建固定标头,滚动溢出窗口以及在移动设备上进行更多操作。 太棒了。 如果您需要黑匣子类型的东西。 这是我们不需要的。

但是没有人喜欢魔术数字,因为魔术数字总是指向开发人员的绝望。

输入iScroll和替代品

因此,我们检查了图书馆,发现了一堆神奇的数字和奇怪的解决方案。 简而言之,大多数代码都用于触摸识别以及在触摸,点击,滑动等之间进行切换。不要相信我的意思,就像几个月前一样,从这个角度来看,一切似乎都变得混乱不堪,但是这是我记得的一些原始代码:

  final_distance = swipe_px +(swipe_px * 35 * 0.015) 
time_y =滑动时间

因此,他们会在y秒钟内将大量数字混在一起,然后将屏幕滚动这么多。 令人惊讶的是,该图书馆的确表现出色。 总体感觉几乎与真实交易一样好。 但是没有人喜欢魔术数字,因为魔术数字总是指向开发人员的绝望。 这很快就会被发现。

当我们实施此解决方案时,我们注意到它的行为与预期不同。 好吧,我说过,宽松政策有所不同,让我们现在解决它。 因此,我们进行了深入研究,发现iScroll使用了一些跨越JS代码46行的自定义书面缓动。 哇。 即使我们愿意,也无法在Unity中使用简单的贝塞尔曲线来再现它,而且我们也不想这么做。 我的意思是,看看这堆代码:

  fn:函数(k){ 
如果((k / = 1)<(1 / 2.75)){
返回7.5625 * k * k;
} else if(k <(2 / 2.75)){
返回7.5625 *(k-=(1.5 / 2.75))* k + 0.75;
} else if(k <(2.5 / 2.75)){
返回7.5625 *(k-=(2.25 / 2.75))* k + 0.9375;
}其他{
返回7.5625 *(k-=(2.625 / 2.75))* k + 0.984375;
}
}

深入兔子孔,进入PastryKit

这个星球上似乎没人知道苹果的PastryKit,这正是我们所需要的。 苹果已经消除了它的所有痕迹,并且开发人员从未真正追上它,因此几乎找不到它,并且由于App Stores在2009年成为一件事情,如今它已经毫无用处。但是除了不可读之外,这个JS框架还是是一件很美的事情,它属于某些计算机历史博物馆,因为它已经比它更早了,而且移动Web应用程序的确回来了(Cordova,Electron,Appcelerator,Xamarin,React Native等)。

我从没听说过PastryKit,但是我偶然在偶然的情况下偶然发现了Daring Fireball的John Gruber的这篇文章及其解释。

闪回:自从我在苹果公司担任ACMT几年以来,我知道格鲁伯的名字,而且我清楚地记得iPhone 2G出世并改变了许多事情的时期。 当时它缺少的一些东西是语言,商店,应用程序和开发人员。 仅仅一年半之后,App Store出现了,两年后,Google Play出现了。 因此,在这个时候,史蒂夫·乔布斯坚信,这个新的HTML5标准将淘汰Adobe Flash(还记得吗?),一个人需要使用的所有应用程序都应该使用HTML5和JS编写。 他们甚至制作了自己创建早期版本《 iPhone用户指南》时使用的框架,以迫使所有人使用与iOS本机可用的相同UX原理。 他们或其他任何人都没有再次提及它。 猜猜名字吗? PastryKit。

但是,除了完全无法理解之外,这个JS框架还很漂亮,属于某个计算机历史博物馆,因为它已经过时了,移动Web应用程序的确回来了

我想知道地球上是否有人知道苹果是如何建立动力的,那就是苹果。 因此,我剖析了两天(我从未说过自己有什么好处,甚至没有寻求帮助),但发现仅足以理解其原理。 一切都由大量事件监听器处理,因为命名约定很复杂,就像removeFromSuperview一样其他var名称仅被命名为xky 老实说,当我第一次看到代码时,我确定我将无法确定到底发生了什么,因为我的断点从ln 43到450到1300到3000+到22。当我发现一个模式时,对滚动机制的简单性感到惊讶。

  const PKScrollViewDecelerationFrictionFactor = 0.95; 
  this.decelerationVelocity.y * = PKScrollViewDecelerationFrictionFactor; 

哇。 iScroll作者遇到的所有麻烦都陷入了低谷,Apple用0.95的魔术常数解决了它。 说实话,还有其他规则和验证,但基本滚动贝塞尔曲线仅设置为0.95。

让我们看看我从代码中学到了什么:

  1. 苹果和我们其他人一样绝望,但是它更加复杂。
  2. 从每种情况下的后备数量来看,在苹果公司工作必须是程序员的噩梦。
  3. 当我说所有可能的情况时,我是认真的。

现在我们已经有了第一印象,让我们看看我真正学到的东西:

 基本上,当触摸持续时,苹果可以让您以1:1的速度移动屏幕。 
 在触摸端,Apple将通过划分用户已滑动的像素数和用户已滑动的时间来获得动力。 如果像素数小于10或时间小于0.5,则动量将被限制为零。 
 无论如何,一旦我们知道了动量(速度),他们将在每帧中将其乘以0.95,然后将屏幕移动那么多。 
 弹跳似乎固定为我不费心计算的某个速度,我只是在认为合适的情况下手动设置它。 

如此愚蠢的简单优雅,它伤害了我的眼睛。

Unity滚动

非常简单,我相信我什至不必为此发布我们的C#代码。 但是,这就是我们所做的。 Miloš克隆了NGUI的ScrollView类,并基于此数据创建了自定义缓动。

 速度* = speedCoeficient; 

至于容器的重用,我们决定使用一种软方法,并在不可见的容器上使用SetActive方法添加了GameObject禁用功能。 基本上,在确定下一帧的距离时,我们还将检查是否有任何GameObject不可见,如果可见,我们将禁用它们。 似乎需要大量CPU工作,但可以节省大量内存和fps。

如果有人需要我,我会在我的蝙蝠车中

结论

在那里。 我真的相信,iScroll开发人员社区中的某人会读到这篇文章,骂我对他们的代码不好,然后利用这些知识来改进他们的代码。

如果比我有更多经验的人对PastryKit代码感兴趣,那也将是非常不错的,我相信我们可以比我在有限的时间和知识下所能学到的更多知识。