zoukankan      html  css  js  c++  java
  • 纪念一个曾经的软件产品(四)——点击检定,惯性,加速度,弹簧及动画效果

    [回目录]

    六、点击检定,惯性,加速度,弹簧及动画效果

    6.1,点击检定

    由于没有使用控件,那如何判定手指(鼠标)点在哪个元素上面?——这个得自己做这个判断。

    我给每个界面元素设定了一个点击检定框,这个框其实是一个矩形,要判断一个点是否在一个矩形之内是很容易的,实现起来是不是应该很简单?实际上比你想的要稍微难那么一点点,因为我得考虑位置偏移。这涉及到用户自定义界面的问题,如下图这个皮肤(皮肤会在以后提到)的首页上显示的快捷方式的位置跟默认的首页是不一样的:

    所以在做点击检定的时候要把不同的个性化因素及其它位置偏移的因素给考虑进去,不能将点击检定的那个矩形写死在代码中。

    6.2,点击及拖拽

    对于点击,只要用过电子产品,都不会不知道,比如Windows的最基本的控件之一的按钮,你鼠标按下去,抬起来,就是一个点击。

    点击在不同场合下意义有很大不同,比如在桌面上点击一个图标是选择这个图标,实际上还包括了其它很多你想不到的动作,比如取消对其它图标的选择,如果有的话;关闭上下文菜单,如果有的话;使得活动窗口失去焦点,如果有的话,如果点击的图标已经被选中,那再次点击它的文本区则是重命名……好多规则,真正做过UI的软件工程师应该都了解过这些细节,做UI我个人认为是相当不容易的,细节太多,外人看起来是那么理所当然的东西内部却蕴含了太多的错综复杂的逻辑,而且老板还不会听你去解释那些东西……除了技术本身之外,UI还是谁都想插上一脚的地方,这进一步加大了UI开发的难度——我在这里“吐槽”一下应该无伤大雅吧,呵呵,好吧,言归正传……

    点击动作在移动设备上通常表示一个“启动”,或者“选中”,例如你在iPhone的桌面上点击一个图标,就启动了一个App,因为在移动设备上要用手指完成类似鼠标的“双击”是很困难的事。(你见过带鼠标的手机吗?Samsung i780就是带鼠标的,那玩意儿还真可以轻松“双击”)那如何将一个手指按下去,再抬起来的动作检定为一个“点击”?通常是:按下去,没有移动,就直接抬起来,这是一个点击。但,不移动,那是很难的,在触屏上,你按下之后,手指不可避免地会出现轻微的移动,即便你没感觉到,那这个动作就变成了一个“拖拽”,而不是点击了,所以,必须设定一个“最小拖拽距离”检定,如果小于这个距离,就不算拖拽,还是算点击。

    (点击/拖拽检定示意图)

    如上图,手指按下处上下左右这个方形区域,是非拖拽检定区,手指在这个区域内发生的轻微的移动不算拖拽,也不会在界面上显示出拖拽效果,如果这个时候把手指放开,那就判定为一个点击动作,只有手指移动超出了这个区域才算拖拽,算作拖拽之后,直到手指放开都一直认为是拖拽状态。

    这个最小拖拽距离是多少呢?不同的DPI,这个值也要作对应的调整,192DPI是72像素,128DPI是48像素,96DPI是36像素,看吧,都是为了适用不同的分辨率,工作量还真不小。

    6.3,苹果引发的UI设计革命

    说实在的,在苹果之前,我做梦也不会想到UI可以这么做,如今你看看满大街的苹果安卓,一切都习以为常了,不就这么一个界面么?可如果苹果不先把这样的界面做出来,你能想得到?就像牛顿第一定律,很简单对吧,我相信你初中就学会了,但如果不是牛顿先提出来,你敢说你能自己领悟出来么?

    最具革命性的当属“划屏”这个操作,你需要滚动窗口的内容,在Windows下的做法是怎样?使用右侧的滚动条或者用鼠标滚轮对吧,手机上通常是没有鼠标的,更别说滚轮了,那只能用滚动条,我一点都不认为这个有什么问题啊,看Windows Mobile 6.5之前的版本,不都这么来的么?

    (Windows Mobile的滚动条)

    这个滚动条默认是很细的,我常常点不准,所幸的是我可以通过修改注册表来调整其宽度,现在看来,这是多么糟糕的用户体验啊!苹果一出,一切都变了,你要滚动窗口内容对吧,直接用手指在上面拖拽就行了。试试看?而且还有惯性效果,手指放开后,窗口内容还继续滚动哦……我第一次看到这个的时候觉得很是震撼,谁想出来的!

    我初步想了想:如果手指碰到的地方正好是空白处,应该没什么问题,但如果手指碰到了图标呢?会不会像Windows桌面上点中一个图标然后拽到别的位置去?事实上,苹果设计了一套全新概念的操作方式,“划屏”这个动作不会引起我所担心的拖拽图标的结果,放心划就是了,拖拽图标有另外的操作方法。苹果的发明几乎成了之后的触屏操作的标准,无数软件开发商和设备制造商都借鉴了苹果的设计(包括SoSoPi)。微软直到Windows Mobile 6.5才加入类似的功能,而且做得很“应付式”,这是它迅速没落的一个原因。

    备注:拖拽图标这种操作方式其实也是苹果发明并最先使用于其Macintosh产品中,比微软的Windows更早

    6.4,惯性、加速度和弹簧

    什么是惯性,什么是加速度?——不要用初中学的物理知识来回答,直接用图:

    手指松开,滚动仍然继续,因为“惯性”,而手指松开后滚动速度会慢下来直到停止,因为“加速度”。一切看起来都是那么自然,但我们如何告诉手机绘制这样的效果呢?

    首先,要得知手指的速度,准确说是手指离开屏幕那一瞬间的速度,这个怎么算?触屏可没有能力直接给出这个速度啊,我是根据手指的移动轨迹来的,我在全局范围内定义了一个有限队列,这个队列最多只有20个元素,如果超过了20个元素,那最先进去的元素就被移除,这个队列其实是一个静态数组,效率是非常高的。我用这个队列来记录所有的“WM_MOUSEMOVE”事件的坐标,(手指移动,其实也就是产生“WM_MOUSEMOVE”事件),也就是说我将最近的20个手指移动的坐标都给记录下来,发生的时间(毫秒)也记录下来,用这些坐标和时间(其实关键是分析最后的两个点的坐标和时间)来分析出用户手指的速度。值得注意的是:x轴(横向)的速度和y轴(纵向)的速度是分开计算的。例如:x轴速度为正,y轴速度为负的话表明用户是向右下方向划的。

    速度出来了,如果没有加速度,那么界面就会一直滚动,直到尽头,加速度也就是给这个速度做一个“衰减”(加速度为负嘛),让它每次减少一点,直到速度变为0,表面上看这并不是什么难做的功能,事实上却需要你经过许多次校对才有比较理想的操作感,这个只有实际用上许多会才能体会,但即便我经过了许多的校正,不少用户还是反映不够好,每个人的感觉都不太一样嘛,最后我还把这个做成了可配置的,我提供了一个“动画调整”的界面,供用户自行调整:

    这个界面没有使用贴图,上面的东西都是直接用线条和文字绘出来的。

    最后说一下弹簧,先看看什么是弹簧:

    • 第一种情况是,惯性滚动的时候滚动到头了,我们并不立即停止,而是让内容滚动“过头”一点,再弹回去;
    • 第二种情况是,用户把滚动的内容拉过头了,松手的时候我们得让内容弹回去;

    大家不难想到,第二种情况其实就是第一种情况的“再弹回去”,所以设计程序的时候是按照第一种情况去考虑。

    弹簧分为两个阶段:

    • 第一个阶段是减速,比前面提到的减速要快得多,滚动时候的那个减速是相对较慢的,而弹簧的减速则很快,我在程序中是将加速度设为滚动时候的8倍,这样“过头”的滚动就能快速停止,当速度变为0的时候,进入第二阶段;
    • 第二阶段是回弹,回弹的过程也是一个带减速的过程,开始回弹的时候我会根据当前的“过头”的距离计算出一个回弹速度,“过头”距离越大,回弹速度越快,模拟实际上弹簧的效果嘛,再给它一个反方向的加速度,基本上效果就出来了。

    弹簧效果同样经过了大量的调整,才让老吴感觉比较“爽”,而代码中却早已充满了许多“修正系数”……做UI可真不容易啊!对了,所有这些效果的实现都得考虑DPI。

    6.5,动画过程实现

    提一下具体的实现,也许很多人都想到了,对,就是用Timer,手指松开之后,就设置一个20ms间隔的Timer来重绘界面,根据速度、加速度及状态来计算绘制的偏移量,并适当调整每次的速度、加速度及状态,直到动画过程结束,结束Timer。

    如果分析到细节,那就更复杂一些。

    考虑这种情况:滚动的过程中用户再次按下屏幕,貌似说:“停!”,这时候是不是得马上停下来?是的,这时候得让Timer结束掉,当用户再次抬起手指时候,再重新启用Timer,如果再进一步,你会发现更多的问题,因为程序中的Timer可能不止一个,而且可能都同时参与了绘制,比如你在界面上显示时钟的话,那估计你得隔个几秒钟得检查一次时间的分钟是否发生了变化,如果是,那就重绘界面,但同时这个时候你的手指正按在屏幕上滚动着其中的内容,这样很容易引起混乱,虽然这种混乱瞬现即逝,但总归给人感觉不太好。

    再例如:手指松开后屏幕的内容正在惯性地滚动,停止前你手指按下了滑块区的滑块,惯性滚动是不是得停止?如果你切换了模块,Timer应该被终止,但如果你没有切换模块,而是直接松开手指,那么前面的惯性滚动是否要继续?如果回答都是“是”,那么我就得让Timer“暂停”,而不是终止,对吧?说到这里我想你大致应该理解了“外人看起来是那么理所当然的东西内部却蕴含了太多的错综复杂的逻辑”这句话的意思。

    (惯性滑动的“暂停”)

    更糟糕的情况是有极小的概率会出现死锁,我至今也没修正这个bug。

    用手指带动屏幕内容的滚动是一方面,还有另一种动画是连贯的,如日历的翻页:



    (日历翻页效果)

    这种动画我们是不希望中间受到打断的,我称之为“原子动画”,在原子动画过程中,一切用户屏幕操作将会被忽略。

    解决这些用户操作和UI渲染的问题是相当繁琐的,我现在也在想是否有一种更清晰的思路来实现这些绘制?我不太清楚,但现在开发UI大多时候都不需要考虑这些,因为开发包里有专门的针对动画效果的解决方案,不必自己用一个Timer去一点点绘制。

    6.6,滚屏的绘制方式

    考虑到大多数软件都是上下滚屏,(比如网页,通常只需要垂直滚动查看,而不需要水平滚动)SoSoPi也采用了这种上下滚屏的机制。现在,关键问题是如何提高绘图的效率?

    方法虽多,但思路也就那么一种:减少绘制

    如:假设你屏幕上的内容的高度是10000,而你的屏幕的实际高度是800,当前的偏移量是“-5600”,那你实际上要绘制的东西是从5600这个高度到6400这个高度的范围,如果全部绘制,那当然慢得离谱了,这就是基本的思路。

    现在再具体一点,究竟从哪里开始绘制?5600到6400?恐怕不是这样,因为我们通常要绘制文字,文字有行高,我们只能一行行绘制,没办法绘制半个字或者1/3个字,假设行高是100,要绘制的文字的位置是5550,也就是说,文字虽然在5600之前,但却要把一部分给显示出来,那这行文字是否应该绘制?——那是肯定的,别说一半,就是只显示一个像素,也得把它绘制出来,所以我们的方法就转变为:判断哪些元素会显示在界面上,把这些要显示的元素且只把这些要显示的元素绘制出来。这是示意图:

    (滚屏绘制示意图)

    这种方法贯穿了整个SoSoPi,那就是:只画需要显示的

    [回目录]

  • 相关阅读:
    10_SpringBoot集成TkMybatis插件
    Mysql-YUM安装
    docker数据拷贝
    jquery实现倒计时功能
    CentOS 7.2使用源码包编译安装MySQL 5.7.22及一些操作
    vue中开发webSocket
    YARN 与Maprd 配置
    js实现轮播图2
    DOM
    js五星好评
  • 原文地址:https://www.cnblogs.com/guogangj/p/3002095.html
Copyright © 2011-2022 走看看