首页

爱奇艺 VR 设计实战案例:界面文字篇

涛涛

本系列文章旨在由浅入深、由理论到实践地介绍 VR 设计。无论你在做哪个领域的设计,都能通过这个系列了解 VR 设计的特点。本文是第一集,深入分析 VR 界面的文字设计。


文章来源:优设    作者:爱奇艺XRD

爱奇艺 VR 设计实战案例:空间布局篇

涛涛

本系列文章旨在由浅入深、由理论到实践系统地介绍了本团队在近几年的设计工作中的一些经验总结和重点思考。本系列面向广大设计师,不论你目前在做什么领域/哪个端的设计,都可以了解到 VR 端和其他端的异同。希望给正在看文章的你带来收获。

团队介绍:爱奇艺 XRD——爱奇艺 VR/AR/XR 设计团队,目前主要负责爱奇艺 VR app 的设计工作(包括各个 VR 设备端和手机端)。

初步认识:空间里的界面

1. VR与其他端的区别

传统的数字终端(手机、电脑、pad 等),用户界面存在于二维的物理屏幕里,也就是在一个平面里。而屏幕和界面通常都是长方形的。

在 VR 中,有空间感的不仅仅是虚拟场景,用户界面也是布局在三维空间里的。「屏幕边界」的概念被打破了,设计师的画板不再是各类手机不同尺寸的屏幕,而是一个「无限的」空间。界面本身也可以是三维的、打破传统的,不再必须是一个个的长方形。

真正的 z 轴

这不同于在 2D 屏幕终端上,元素只拥有(x、y)坐标点的属性,而并没有一个 z 轴的概念。Z 轴,也就是深度概念,是通过设计师利用阴影、动效等视觉效果,「模拟」出来的前后关系。

在 VR 中看到的内容物(包括 UI 界面、场景、模型、视频资源等)有真实概念的前后关系,每个物件摆在一个具体的(x、y、z)坐标点上。物件拥有绝对位置,物件和物件之间有相对位置,物件和 camera(指用户观测点)之间有一个具体的距离。

角度和曲面

除了 z 轴坐标,因为 VR 界面存在在空间里,所以还拥有一个属性就是角度,这包含了在(x、y、z)三个轴上旋转的角度。每一个物件也可以不再是一个平面,而是曲面的、三维的。

角度和位置共同决定了,当用户看向某个物件时,这个物件的样子。

「有多大?」

一个物件在 VR 中「有多大」很难简单描述清楚。在传统端只需标注某个 UI 元素的「大小」「间距」,描述单位是像素。而在设计 VR 时。需要从多个维度定义一个元素:「大小」「(x、y、z)位置」「(x、y、z)角度」。同时,「有多大」还跟用户观测点的位置息息相关。

2. VR基本术语认知

在介绍下文理论之前,让我们先认识两个常见的 VR 术语:

FOV:Field of View,视场角

视场角的大小决定了光学仪器的视野范围。在 VR 中,视场角是 VR 硬件设备的一个属性,设备 FOV 是一个固定值。

在我们团队日常工作用语中,通常也用来指代用户的视角范围、界面元素的角度范围(如,「某面板水平 FOV 是 60°」)

DOF:Degree of Freedom,自由度

物体在空间内具有 6 个自由度,即沿 x、y、z 三个直角坐标轴方向的移动自由度和绕这三个坐标轴的转动自由度 。

  • 3DOF 的手柄/头显:只有绕 x、y、z 轴的 3 个转动自由度,没有移动自由度,共 3 个自由度。通俗地说,3DOF 手柄可以检测到手柄在手中转动,但检测不到手柄拿在右手还是左手。
  • 6DOF 的手柄/头显:同时有绕 x、y、z 轴的 3 个转动自由度,和三个轴方向的 3 个移动自由度,共 6 个自由度。通俗的说,是完全模拟真实物理世界的,可以看的手柄的实际位置和转动方向。

理论:人眼的视野与视角

虽然说 VR 是一个 360° 全景三维空间,但对于目前大多数 VR 的使用场景来说,可供设计师创作的区域其实已被大大缩小了。

目前国内市面常见的可移动的 VR 设备(非主机类),如小米 VR 一体机、Pico 小怪兽系列、奇遇 VR、新上市的华为 VR Glass,标配均为 VR 头显配合3DOF手柄的硬件模式。对应此类 VR 硬件模式,常见的用户使用场景为:「坐在椅子上+偶尔头转+身体不动」,好比「坐在沙发上看电视」。即使用户使用一个 6DOF 的 VR 硬件,支持空间定位可以在房间里走动,但对于爱奇艺 VR 这类观影类 app 来说,「坐在沙发上看电视」仍是主要的使用场景。

因此, 主要的操作界面还是需要放在「头部固定情况下的可见范围内 」。这样用户无需费劲转头,就可以看到主要内容、操作主要界面。

那么,什么是「头部固定情况下的可见范围 」呢?我们需要先学习一些人机工程学,来了解一些人眼 在不同情况下的可视范围。

1. 水平视野(x轴)

(此图的中心点 为用户观测点。)可以看出,

头部固定的情况下,双眼能看到的最大范围约为 124°。但还要考虑到一个限制,目前 VR 硬件设备的 FOV 并未达到这个值,通常在 90°~100°。而其中,能看清晰的范围只有眼前中心处的 60°。这相差的范围可以理解为「余光」,在人眼中成像是不清晰的,但仍在视野范围里。——这个范围极大程度上决定了每一个 UI 面板(包括其中的图片、文字等)适合占据的空间,也决定了 VR 中视频播放窗的最小和最大值。

头部转动但身体不动的情况下,脖子舒适转动能看到的范围约有 154°,脖子转动到能看到的范围约有 204°。一些次要内容可以放在这个区域,用户需要转动头部才能看到,但也不用太费力。

偏后方的区域范围,必须移动身体才能看到,因此只能放一些「没看见也没关系」的内容。比如环境、彩蛋等。

2. 垂直视野(y轴)

在放松状态下,头部与眼睛的夹角约为 105°。也就是说,当头部竖直向前时,眼睛会自然看向水平线下 15° 的位置。头部固定仅眼球转动时的最佳视野是(上)25° 和(下)30°。应将操作界面和主要观看内容放在此范围内。

垂直方向最大视野范围是(上)50° 和(下)70°。这个范围也是超过了 VR 硬件设备的 FOV。

另外,点头比抬头舒适。现实世界中,我们通常都是低头状态比抬头多,比如玩手机、看书或看笔记本电脑时。所以在 VR 中,比起偏上方区域,应考虑更多利用起偏下方的区域。

3. 深度感知(z轴)

(见本章图1)

0.5m 之内的物件,双眼很难对焦,因此不要出现任何物体。(这个值对于全景 3D 视频的拍摄 意义较大,应该尽量规避离镜头过近的物体出现)

有立体感的范围在 0.5m~10m,这里应该放置主要内容,尤其是可操作的内容。

10m~20m 之间,人眼仍能感觉出立体感,但有限。此范围仍适合放置可以体现沉浸感的 3D 场景/模型。

超过 20m 的距离,人眼很难看出立体感了,物体和物体的前后关系不再明显。因此适合放置「仅仅作为背景」存在的内容,也就是环境。(值得注意的是,因为反正也感知不出立体感,所以此范围的环境可以处理为全景球(即一个球面),来代替 3D 模型,这大大降低了功效。)

4. 视角和视距

在现实世界中,每个信息媒介都有一个预设好的观测距离。例如,握在手中的手机,距人眼大约 20cm。放在桌面上的电脑主机显示屏,距人眼大约60cm。客厅墙上的电视,距人眼大约 5m。在马路另一头的广告牌,距人眼可达几十米。

内容在预设好的观测距离上,对应预设好的大小。例如,手机上的正文文字只有几毫米大,而广告牌上的文字需要好几米大。

而在我们(观测者)看来,这些文字都阅读舒适。甚至其实看起来是「一样大」的。

这是因为它们拥有同样的视角——被视对象两端点到眼球中心的两条视线间的夹角。具体举例来说,在 1m 处看一个 10cm 的物体,和 在 2m 处看一个20cm 的物体,在 3m 处看一个 30cm 的物体,这 3 个物体「看起来是一样大的」,他们具有相同的视角。但我们仍然清楚地知晓谁近谁远,是因为眼睛对焦在不同的距离上,也就是视距不一样。

在 VR 中,不能再像移动端那样用「像素」来度量一个物件的大小,而是通过「视角」。而视角是由物件的「大小」、「位置」、「旋转角度」共同决定的。在「用户观测点不动」的使用场景下,「位置」实际上就是与观测点的「视距」。

界面的「旋转角度」应遵守一个原则——一个便于观看和操作的界面,应该尽量垂直于视线、面向用户观测点。也就是说,当你确定好一个界面的「位置」后,就自然会有一个对其来说的「最佳旋转角度」。

在 VR 中,一个物件的视角由其「大小」和「视距」两个变量影响。当确定了「最佳视角」、「最小可阅读视角」时,这就决定了「需要在不同距离上放置的信息内容,各自应该放多大」。

实践:爱奇艺VR app 的假面布局

有了理论基础后,接下来就是不断实践。

首先需要读者了解的是,我们团队设计的对象是爱奇艺 VR app——是在 VR 设备上的爱奇艺,是爱奇艺的第四个端。不仅包含爱奇艺全网 2D 内容,还拥有海量 VR 全景视频、3D 大片。选片和观影相关功能齐全。在体验上主要打造有沉浸感的 VR 影院观影,并突出 VR 特色内容。

本文章针对于 VR 一体机内的爱奇艺 VR app 设计展开讨论,但我们同时也支持手机端 app,若感兴趣可以在各大应用商店搜索下载。

我们学习的大量二手资料,给开展实际工作创造了基础。然而最终设计效果其实是在反复验证、试错后决定的。

当根据理论资料开始做实践,却发现实际效果差强人意时,我们的建议是——注重实践。原因之一是,目前 VR 界从技术到产品设计仍旧处在实验性阶段,远未达到移动端设计规范的成熟性,国内外的相关理论经验总结,都还没有绝对定论的程度。Oculus 的设计指南中,都是建议「实验,实验,再实验」。之二是,能搜集到的二手资料,不完全是建立在「人带着 VR 设备手握手柄」这种使用场景上,所以导致实际结果「不是那么回事」。

1. 模块化界面布局

基于「坐着不动+头部转动」的使用场景,和「自然视角偏下」、「低头比抬头舒适」的理论。

我们采取「从正视角出发,向左、右、下延伸」的布局思路。正视角放置当前界面的核心内容,左右两侧放置辅助信息内容;在必要时,可加入下部模块。此类模块化布局适用于所有界面,只是具体的面板尺寸、旋转角度可以有所不同。根据每个界面需要承载的内容,因地制宜地合理规划。

图为我们使用的几种常见 UI 布局。

2. 界面的FOV

基于人眼水平和垂直方向的视野范围的理论,同时参考主要适配的硬件设备属性(目前 VR 设备的 FOV 都小于人眼的视野范围),即:

  • 小米 VR 一体机:FOV≈90°(实际)
  • Pico 小怪兽 2:FOV=101°(官方数据)
  • 华为 VR Glass:FOV=90°(官方数据)

——我们决定了爱奇艺 VR 中主要界面的 FOV。

选片主界面 FOV (左-中-右 布局)

△ 「实际截图」爱奇艺 VR 选片主界面

水平方向上:

  • 中部面板:两边边界在「头部固定时清晰 FOV」内。
  • 两侧面板:近中心的边界均出现在「头部固定时可见 FOV」内,即在默认可见的视角范围内。两边边界在「头部轻微转动时清晰 FOV」内,即用户只需轻微左右转动头部便可查看全部内容。

垂直方向上:

  • 面板在「头部固定时清晰 FOV」内。用户无需上下转头。
  • 该页面可左右滑动,用户可以只关注中部的面板。

影厅播控页面 FOV(左-下-右布局)

在爱奇艺 VR 中,观看非全景的 2D/3D 视频,用户会置身于一个电影院影厅场景中。视频画面会出现在影厅屏幕上。

影厅播控页面(操控台页) 指播放视频时(包括影厅场景影片和全景影片)的操作界面。

△ 「实际截图」爱奇艺 VR 影厅播控页面

影厅播控页面采用「左-下-右」布局。包括 3 个功能区块:相关推荐(左)、操控台播控(下)、详情 & 选集(右)。该页面在视频屏幕(或全景视频)的前方,靠单击触摸板来呼出和关闭。

这 3 个面板的边界均在正视角「头部固定时可见 FOV」之内,也就是保证了,当用户注视影片本身并呼出该面板时,能看到所有面板。而用户仅需轻微转动头部,就可看到完整的面板。

视频播放窗的最大/最小值

视频播放窗 即「影厅屏幕」,被我们设定了屏幕大小可调的功能,以此来适应不同用户的观影习惯。屏幕大小可在指定范围内平滑调整。

屏幕最小值(50°):完整的屏幕范围都在「头部固定情况下清晰 FOV」。

屏幕最大值(76°):屏幕范围在「头部固定情况下可见 FOV」,即「充满视野」。此状态的观感类似 IMAX 影厅。

垂直方向上:距水平线偏上而不是偏下。这里其实和理论值发生了冲突。理论资料中,人眼最舒适的对焦点在「水平线向下 15°」。在老版本中,我们曾经将主 UI 和视频屏幕都按理论值摆在了偏下方,但实际效果并未令人感到舒适,反倒明显感知到「内容都偏下了」。这就是上文所说,「当理论导致实践差强人意」时,我们选择了不断实验,以实际效果为准。另外,本场景下方有影厅座椅等实际模型,并且还有操控台播控的 UI。综上,影厅屏幕被放在了水平偏上的位置。

3. 深度距离与层级(z轴)

根据前文理论提到的,z 轴距离的合适范围(0.5m~20m)比起水平和垂直 FOV 来说,数值范围要大得多,也就是说可试验的范围很大。因此在界面距离这一点上,我们进行了大量反复的实践——最终决定了当前版本中各级页面的深度层级和具体数值,如下图:

跟随式提示:

  • 适合于临时性的提示内容,几秒后自动消失。如 toast。
  • 与 camera(用户观测点)锁定,跟随 camera 移动。不可交互。
  • 保证让用户注意到,又不至于遮挡视线。

阻断式提示:

  • 适用于必须让用户看到且用户必须对其处理的提示。如弹窗。
  • 正视角区域固定显示,不跟随 camera 移动。有按钮可以交互。
  • 需要在其它操作界面之前。

辅助操作界面:

  • 适用于重操作的界面,而非重展示的界面。如播控按钮组、键盘,而非视频列表。
  • 通常在平视视线偏下的位置。(模拟「在手边」的位置)

减少眼球调焦,缩小距离差

值得注意的是,在我们的老版本中,不同层级的界面曾经被摆放的距离差距很大。如,toast 在 1.75m,主 UI 在 3m,而视频屏幕在 12m。之所以改动至上图数值,主要是为了达到「在看向不同层级界面时,尽量减少眼球调焦的程度」的目的。

眼球在看向不同距离上的内容时,需要对焦到不同的平面,距离差距越大,眼球需要调整得越大。如果频率高的话,容易导致(一部分)人的眼球疲劳。

Z轴上的获焦效果

VR 独有的「z 轴」,不仅允许了界面可以被放在不同距离上,还支持了利用 z 轴上的位移和旋转 来表达获焦效果。控件被获焦时,只需要在 z 轴上轻微的位移或轻微的角度旋转,便可体现出与众不同的有趣效果。

文章来源:优设    作者:爱奇艺XRD

如何设计交互缺省页?

涛涛

缺省页指页面的信息内容为空或信息响应异常的状态;设计缺省状态的作用不仅是引导用户在异常边界状态的操作提示,同时也是安抚用户体验情绪的重要场景;更重要的是为边界场景营造出良好用户体验。通过分析缺省状态产生的原理,从而更为准确地把握交互缺省页的设计原则。

哪些状态需要缺省页

谈到缺省页面可能是设计师最容易忽略输出的范围,可能直到对接的开发同学提出来,「这个页面,如果没有数据的时候,该怎么显示啊?」。为了更好地把控设计缺省页交互状态,首先要了解缺省页出现的原理。App 页面内容(包括图片、文字、数据字段等等)都是请求服务器数据,顺利返回后,正常显示到客户端页面。在了解清楚基础实现逻辑后,就可以开始梳理、整理缺省状态的设计思路。

△ 图1 缺省状态的场景梳理图

缺省状态包括:系统层、信息层、空白层。

系统层:指当用户请求服务器时,返回提示请求提交失败,并检测到失败原因时呈现的页面;例如:加载失败、服务器异常、无网络等;页面一般会有重新请求的快捷按钮。文案上可做失败原因的细分描述,也可节约成本使用网络异常的统一文案。

信息层:请求服务器数据成功,但返回的数据异常的页面;例如:内容已删除、内容已下架、内容不存在;文案内容以提示数据类型的缺失为主。显示形式除了常有的全屏缺省图,还会出现在数据列表下单一内容缺失的缺省模块化的情况,例如:单一作品在书架上显示已下架。

空白层:请求服务器数据成功,但显示无数据;内容页在无数据时需要缺省状态进行表达;例如:页面空数据、搜索无结果等。空白页面属于正常网络显示场景,所以一般会在缺省页附带有相似属性模块的用户引导,争取用户重复消费的目标,满足用户的操作的诉求。

最后根据每个不同的缺省状态,梳理产品相对应的场景。逐一根据场景特点来设计页面内容。那缺省页的设计有哪些表现形式呢?

缺省页的表现形式

没有用心设计的缺省页无法给用户带来良好用户体验,并可能给用户带来困扰,如下图:某小众直播平台的拉新邀请页面,无邀请记录状态下没有任何有效反馈信息,用户不能明确得知到底是网络问题还是账号同步出错,亦或者是没有一次邀请。正确的缺省页设计内容理应明确表达出符合用户心理预期的视觉场景表达(图形);和使用易理解和语法恰当地表达当前的异常状态(标题)甚至于引导用户解决问题的文案描述。

△ 图5 缺省页的错误示范

1. 视觉图案+文案

此类缺省设计形式一般应用于表达系统性无响应或初始空白态的缺省场景。视觉图案一般使用 app 吉祥物或主色调延展出的 icon 或插画来表示缺省状态;文字:通常为「标题」或「标题+描述」结构;标题通常是表达出现缺省的原因;描述文案则说明结束缺省状态的解决办法,如「请检查网络是否顺畅」 等等。

2. 视觉图案+文案+引导

此类缺省设计形式一般运用于需要用户引导操作来达到业务目标的缺省场景。在视觉图案+文案的基础上加入引导模块,主要作用于避免用户在数据边界的状态下,会因为无法达到操作目的而提高的跳出率。引导模块的内容包括:相似属性内容,相似行为目标按钮或解决缺省状态操作按钮,加入引导,用户进行某项行为或者感知某些信息,对于功能的教学和使用频率的提升有着重要作用。引导模块的形式也是日新月异,逐渐变成新用户业务引导的作用,不仅限于页面平铺,也可以做成固定气泡微动效,例如:抖音的发布缺省页。

缺省页的设计技巧

缺省页除了常规的提示型设计方法,还有许多其他的设计技巧,帮助用户在遇到困难,更好地安抚用户的情绪。这些设计技巧有些是替代原来的缺省内容,让用户有更多的消费空间与深度。有些是拓展缺省状态的补充内容,让用户不容易跳出页面,增加用户的消费时长。具体如下:

1. 使用推荐内容

缺省状态中的空白层非常影响边界情况的用户体验,提出一种假设,是否可以刻意推荐相同属性的内容呢?这样的界面既不会显得苍白无力又可以留住用户的注意力。相似性的内容也可以解决用户目标的迫切性。所以说,这种方法非常适合内容型产品中使用。例如:新用户在打开电商产品的购物车时候,理应是空白无消费行为的操作记录。那么平台方通过用户画像与热门排行算法推荐了一个商品流。这样可以解决用户无目标性挑选的诉求,增加消费时长。至于产品如果确定用户画像的推荐算法,可以通过获取第三方登录的个人基本数据之后,才给我推荐了数据库内相对应标签的热门商品,这样推荐的精准度也会高些。

2. 使用缓存

是否使用缓存内容代替缺省状态?根据产品特性来判断,工具类、金融类等同类型产品不适合使用缓存;因为用户交互操作的数据必须保持实时性与真实性。而内容型、电商类等类型产品适合使用缓存来代替缺省状态;理由:用户消费内容的转化路径是先消费后转化的行为特点,不存在系统操作门槛,且缓存内容可以代替产品的缺省状态,安抚用户操作失败所带来跳出率过高的风险。

3. 情感化表达

当缺省页给到用户时,通常省时省力的做法就是老老实实告诉用户当前的状态,最多配上一个具有通识性的灰色 icon。但是,秉持着以用户体验为己任的时代,我们其实可以把缺省内容表达得更加生动形象一些。在这里会加入一些情感化的表达,而不是仅仅只是做到准确的目标而已,比如加上活泼的插图故事,或者把文案写得更加拟人化、戏剧化一些。这些配图在让用户明白当前的状态的同时,往往也能引发用户会心一笑,从而弥补空白页面带来的失落感甚至可以带给用户一些正面的情感。如下图:

4. 提供新任务

通常缺省页的引导模块都着眼于解决当前任务。如果碰到没有解决方案的情况(例如:404,服务器崩溃等)可以提供给用户具有情感共情的新任务,让他们暂时忘记无法达到目标的挫败感,又有体谅的情怀。帮助建立正向积极的品牌价值观。例如:访问腾讯网时访问失败的时候,网页除了显示 404 状态之外,还会显示腾讯「宝贝回家」的公益寻人计划。将缺省页与公益内容相结合,不仅改善到用户缺省状态。也贯彻腾讯价值观「用户为本,科技向善」的输出。一个好的缺省页也可以承担社会责任,让公益传播到每个角落。

△ 图10 腾讯网404公益任务缺省页

结语:作为设计师有时会听到需求方表述「这种极少出现的情况,我们可以暂且不管它。」但是细节见真章,所有优秀的体验设计都必须照顾到方方面面的缺省情况。让每个用户的流量价值发挥到最大,产生相互信任的良好的品牌关系。这样的平台生态是良性的,这样的产品会更有流量转化的商业化价值。

文章来源:优设    作者:腾讯动漫TCD

uni-app uni.request接口封装

seo达人

uni-app uni.request接口封装

今天在做uni-app项目时,发现在uni-app中 调取后台接口需要大量的重复编辑,就在想能不能封装一个如同Vue项目中的this.$axios.get(url,data).then();格式,这样就减少了很多代码重复!!

封装为如同this.$axios.get(url,data).then();格式

第一步、

我们先在index首页中的组件部分,创建一个js文件;





第二步、

我们在uni-app的入口文件中引入request.js文件;

在入口文件中挂载到uni-app实例上;





第三步、

开始接口封装:

(以下为js文件代码)



//先把接口暴露出去

export default{

//我们先定一个uni-app方法 以便于以下操作使用uni-app调取接口时便利

request(options){

///我们使用Promise方法来实现调用接口时后面多个.then()的方法

//只有Promise能实现如同$axios后面连续多个.then()的方法

return new Promise((reslove,reject)=>{

uni.request({

...options,

success:res=>{

//判断我们在使用封装的自定义时第三个参数是否为native

//当native为true时 我们返回原数据

if(options.native){

reslove(res)

}

//当native为false时 我们直接返回data中的数据

if(res.statusCode === 200){

reslove(res.data)

}else{

//加入接口参数错误或接口地址错误时 我们返回原错误提示

reject(res)

}

}

})

})

},

//在方法中 第二个参数和第三个参数用ES6新语法来添加默认值

//接口调取get方法

get(url,data={},options={}){

//我们把传过来的参数赋给options,这样我们在使用uni-app

//this.request()方法时 传递一个参数就可以

options.url = url;

options.data = data;

options.method = 'get';

//调用上面自己定义的this.request()方法传递参数

return this.request(options)

},

//接口调取post方法

post(url,data={},options={}){

options.url = url;

options.data = data;

options.method = 'post';

return this.request(options)

}

}



这样我们就已经封装完成啦,接下来就是 在页面内使用!

第四步、

我们可以在页面中来调取已经封装好的自定义事件啦



例一:

个人建议使用ES6新语法 箭头函数 不然使用this还要重新在外面声明定义,太麻烦了,使用箭头函数就会方便很多



// 已封装好的接口方法

//本案例调取接口时 没有参数上传 直接调用的

//这样使用方法时只传递了一个参数,也就是接口地址

//第二个参数没有写,默认为空;假如有参数的话 可以直接填写

//后面的参数都为接口内已经定义好的默认值:{}空对象

//里面的res为接口返回数据中的data里面的内容

this.$H.get('/api/getIndexCarousel.jsp').then(res=>{

//res打印出来是接口返回数据中data里面的数据

console.log(res)

//赋给数据区的变量,方便本页面使用

this.swiperData = res

});



例二、



// 已封装好的接口方法

//本案例使用时 传递了三个参数

//第一个为:接口地址

//第二个为:调取接口传递的参数,方法使用时不用传参,写空对象就好

//第三个为:自定义事件中 native 的属性 若为true 则返回原数据

//若想返回原数据,必须要填写第二个参数,若没有参数,也要写空对象

//因为方法调用时 是按照传参顺序调用的,若不写 参数传递就会出错

this.$H.get('/api/getIndexCarousel.jsp',{},{

native:true

}).then(res=>{

//res打印出来的数据是接口返回来的原数据

console.log(res)

//赋给数据区的变量,方便本页面使用

this.swiperData = res

});




每天学习一个Android中的常用框架——1.Litepal

seo达人

文章目录

1.简介

2.特性

3.演示

3.1 集成

3.2 配置

3.3 创建数据库

3.4 升级数据库

3.5 插入数据

3.6 查询数据

3.7 更新数据

3.8 删除数据

4.版本异同

5.源码地址

1.简介

Litepal——作为带我入行的第一本教学书籍《Android第一行代码》的作者郭霖老师所写出来的持久化框架,几乎算是我接触Android世界之后第一个遇到的框架,故将该框架列为一系列学习框架博客的首位。

根据Litepal的GitHub主页:Litepal,可以看到该框架的一些简介:



LitePal is an open source Android library that allows developers to use SQLite database extremely easy. You can finish most of the database operations without writing even a SQL statement, including create or upgrade tables, crud operations, aggregate functions, etc. The setup of LitePal is quite simple as well, you can integrate it into your project in less than 5 minutes.



事实上,正如这段简介所说,集成Litepal相当简单,不需要超过五分钟时间。使用Litepal,也适合对sql语言还不熟悉的开发者快速上手。



2.特性

让我们继续浏览Litepal的GitHub主页,可以发掘Litepal的一些特性:



Using object-relational mapping (ORM) pattern.

Almost zero-configuration(only one configuration file with few properties).

Maintains all tables automatically(e.g. create, alter or drop tables).

Multi databases supported.

Encapsulated APIs for avoiding writing SQL statements.

Awesome fluent query API.

Alternative choice to use SQL still, but easier and better APIs than the originals.

More for you to explore.

用大白话来描述的话,可以列举如下:



Litepal使用了ORM(对象关系映射)模型

Litepal几乎是无配置的,仅需极少的配置文件

Litepal几乎包括所有的CRUD操作,也支持多张表格的操作

Litepal可以仅调用api进行CRUD操作而避免编写sql语句

总之,看到Litepal具有这么多良好的特性,读者是否心动了呢。理论的话不多说,我们现在就开始正式地使用Litepal进行数据库的相关操作

PS:如果有曾经学习过Java的ORM框架——Mybatis的读者,应该不会对Litepal的使用太陌生,因为它们都使用了xml文件进行相应的配置



3.演示

3.1 集成

现在Android框架的集成相比于IDE还为ADT的时代,要方便了许多。原因是现在的主流IDE是Android Studio,而AS默认使用了Gradle进行版本的配置管理,这让集成框架变得简单了许多。

在build.gradle下,添加以下语句,然后重新sync,即可将Litepal集成到你的项目中:



implementation 'org.litepal.android:java:3.0.0'

1

当然,目前Android的主流开发语言,除了Java之外,还有Kotlin,Litepal同样具有Kotlin版本的(这里的演示仅针对Java,Kotlin版本的异曲同工)依赖:



implementation 'org.litepal.android:kotlin:3.0.0'

1

可以根据个人需求进行配置。



3.2 配置

集成了Litepal之后,要想正式使用它还需要进行一些配置



在assets目录下新建litepal.xml,作为Litepal的全局配置文件,相应的条目信息已作出注释,代码如下:

<?xml version="1.0" encoding="utf-8"?>

<litepal>

    <!--  数据库名  -->

    <dbname value="androidframelearn"/>



    <!--  数据库版本号  -->

    <version value="1"/>



    <!--  指定映射模型  -->

    <list>

       

    </list>



    <!--  指定文件的存储方式  -->

    <!--  <storage value="external" />-->

</litepal>



在你的应用下配置Litepal,有两种方式可以实现:

修改清单文件,将你的应用名修改为:android:name="org.litepal.LitePalApplication"

新建一个自己写的MyOwnApplication类,然后将清单文件中的应用名定位到该类,即:android:name="com.example.MyOwnApplication",然后再编写MyOwnApplication类,代码如下:

public class MyOwnApplication extends Application {



@Override

public void onCreate() {

    super.onCreate();

    LitePal.initialize(this);

}

...

}



两种方式亦可,Litepal的作者建议若使用第二种方式,需要尽快地调用LitePal.initialize(this);所以将其放在onCreate()方法是最好的。



3.3 创建数据库

刚才在介绍的时候已经说过,Litepal采取的是对象关系映射(ORM)的模式,那么什么是对象关系映射呢?简单点说,我们使用的编程语言是面向对象语言,而使用的数据库则是关系型数据库,那么将面向对象的语言和面向关系的数据库之间建立一种映射关系,这就是对象关系映射了。

不过你可千万不要小看对象关系映射模式,它赋予了我们一个强大的功能,就是可以用面向对象的思维来操作数据库,而不用再和SQL语句打交道了,不信的话我们现在就来体验一下。像往常使用SQLiteOpenHelper类,为了创建一张Book表需要先分析表中应该包含哪些列,然后再编写出一条建表语句,最后在自定义的SQLiteOpenHelper中去执行这条建表语句。但是使用LitePal,你就可以用面向对象的思维来实现同样的功能了,定义一个Book类,代码如下所示:



package com.androidframelearn.dao_litapal;



import org.litepal.crud.LitePalSupport;



public class Book extends LitePalSupport {

    private int id;

    private String author;

    private double price;

    private int pages;

    private String name;

    public int getId(){

        return id;

    }

    public void setId(int id){

        this.id = id;

    }



    public String getAuthor(){

        return author;

    }

    public void setauthor(String author){

        this.author = author;

    }



    public double getPrice(){

        return price;

    }

    public void setPrice(double price){

        this.price = price;

    }



    public int getPages(){

        return pages;

    }

    public void setPages(int pages){

        this.pages = pages;

    }



    public String getName(){

        return name;

    }

    public void setName(String name){

        this.name = name;

    }

}



这里使用标签来声明我们要配置的映射模型类,注意一定要使用完整的类名。不管有多少模型类需要映射,都使用同样的方式配置在标签下即可。

没错,这样就已经把所有工作都完成了,现在只要进行任意一次数据库的操作,BookStore.db数据库应该就会自动创建出来。为了更好地演示代码,我们将布局文件所需要的功能一次性编写好,activity_main.xml代码如下:



<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    tools:context=".MainActivity"

    android:orientation="vertical">



    <Button

        android:id="@+id/btn_db_create"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="创建数据库"/>



    <Button

        android:id="@+id/btn_db_query"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="查询数据"/>



    <Button

        android:id="@+id/btn_db_insert"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="插入数据"/>



    <Button

        android:id="@+id/btn_db_update"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="更新数据"/>



    <Button

        android:id="@+id/btn_db_delete"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="删除数据"/>



</LinearLayout>





接下来,修改MainActivity,除了给按钮注册点击事件,还需要编写不同的方法代表不同的逻辑,其中,创建数据库的方法代码如下:



private void createDBbyLitePal() {

        btn_db_create.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                Log.i(TAG,"创建数据库成功");

                LitePal.getDatabase();

            }

        });

    }



仅仅通过点击按钮,调用LitePal.getDatabase();这句api,就可以创建出数据库,让我们实际进入项目中尝试一下吧!点击该按钮,然后查看控制台,如图所示:



出现该句日记,说明数据库创建成功,接下来我们看看这个数据库是否按照我们所设置好的格式创建出来了,进入data/data/你的项目包名/databases,即可查看到该数据库已经放置到该目录下,如图所示:





3.4 升级数据库

事实上,若想对现有数据库进行升级,也是可以实现的。以前我们使用SQLiteOpenHelper来升级数据库的方式,虽说功能是实现了,但你有没有发现一个问题,,就是升级数据库的时候我们需要先把之前的表drop掉,然后再重新创建才行。这其实是一个非常严重的问题,因为这样会造成数据丢失,每当升级一次数据库,之前表中的数据就全没了。

而使用Litepal,就可以很好地避免这个问题。假设我们现在有一张新的表Category要加进去,同样编写它的实体类,代码如下:



package com.androidframelearn.dao_litapal;



public class Category {

    private int id;

    private String categoryName;

    private int categoryCode;

    public int getId(){

        return id;

    }

    public void setId(int id){

        this.id = id;

    }



    public String getCategoryName(){

        return categoryName;

    }

    public void setCategoryName(String categoryName){

        this.categoryName = categoryName;

    }



    public int getCategoryCode(){

        return categoryCode;

    }

    public void setCategoryCode(int categoryCode){

        this.categoryCode = categoryCode;

    }

}



改完了所有我们想改的东西,只需要记得在litepal.xml将版本号加1就行了。当然由于这里还添加了一个新的模型类,因此也需要将它添加到映射模型列表中。修改litepal.xml中的代码,如下所示:



<?xml version="1.0" encoding="utf-8"?>

<litepal>

    <!--  数据库名  -->

    <dbname value="androidframelearn"/>



    <!--  数据库版本号  -->

    <version value="2"/>



    <!--  指定映射模型  -->

    <list>

        <mapping class="com.androidframelearn.dao_litapal.Book"/>

        <mapping class="com.androidframelearn.dao_litapal.Category"/>

    </list>



    <!--  指定文件的存储方式  -->

    <!--  <storage value="external" />-->

</litepal>



重新运行一下程序,再次创建数据库,就可以完美地完成数据库的升级了。这里的调试可以使用sqlite工具,这里不再赘述。



3.5 插入数据

在讲述本节时,首先回顾一下之前添加数据的方法,我们需要创建出一个Contentvalues对象,然后将所有要添加的数据put到这个Contentvalues对象当中,最后再调用SQLiteDatabase的insert() 方法将数据添加到数据库表当中,步骤相当繁琐。

而使用LitePal来添加数据,这些操作可以简单到让你惊叹!我们只需要创建出模型类的实例,再将所有要存储的数据设置好,最后调用一下save()方法就可以了。

同样地,修改MainActivity,增加插入数据的事件方法,代码如下:



private void insertDatabyLitePal() {

        btn_db_insert.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                Book book = new Book();

                book.setName("The Da Vinci Code");

                book.setauthor("Dan Brown");

                book.setPages(454);

                book.setPrice(16.96);

                book.save();

                Log.i(TAG,"插入数据成功");

            }

        });

    }



同样运行程序,查看控制台,如图所示:



当点击查询数据(下一节将介绍该逻辑)时,控制台打印刚刚插入的数据,如图所示:





3.6 查询数据

使用Litepal同样可以很轻易地查询数据,当然了,由于篇幅限制,这里仅仅贴出最简单的查询方式,至于关联查询等稍复杂的查询方式,可以去GItHub上参考Litepal的官方文档进行相关调用即可。

同样地,修改MainActivity,增加查看数据的事件方法,代码如下:



private void queryDatabyLitePal() {

        btn_db_query.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                List<Book> books = LitePal.findAll(Book.class);

                for (Book book : books){

                    Log.i(TAG,"查询数据成功");

                    Log.d("MainActivity","书名是"+book.getName());

                    Log.d("MainActivity","书的作者是"+book.getAuthor());

                    Log.d("MainActivity","书的页数是"+book.getPages());

                    Log.d("MainActivity","书的价格是"+book.getPrice());

                }

            }

        });

    }



相关的运行结果上一小节以贴出,这里不再重复。



3.7 更新数据

更新数据要比添加数据稍微复杂一点,因为它的API接口比较多,这里我们只介绍最常用的几种更新方式。

首先,最简单的一种更新方式就是对已存储的对象重新设值,然后重新调用save()方法即可。那么这里我们就要了解一个概念,什么是已存储的对象?

对于LitePal来说,对象是否已存储就是根据调用model.isSaved()方法的结果来判断的, 返回true就表示已存储,返回false就表示未存储。那么接下来的问题就是,什么情况下会返回true,什么情况下会返回false呢?

实际上只有在两种情况下model.isSave()方法才会返回true, 一种情况是已经调用过model. save()方法去添加数据了,此时model会被认为是已存储的对象。另一种情况是model对象是通过LitePal提供的查询API查岀来的,由于是从数据库中查到的对象,因此也会被认为是已存储的对象。

由于查询API相对复杂,因此只能先通过第一种情况来进行验证。修改MainActivity中的代码,如下所示:



private void updateDatabyLitePal() {

        btn_db_update.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                Book book = new Book();

                book.setName("The Lost Symbol");

                book.setauthor("Dan Brown");

                book.setPages(510);

                book.setPrice(19.95); // 第一次设置商品价格

                book.save();

                book.setPrice(10.99); // 第二次设置商品价格

                book.save();

                Log.i(TAG,"更新数据成功");

            }

        });

    }



可以看到,我们做了跟插入数据类似的事情,但是我们对数据的价格进行了设置,运行程序,如图所示:



可以看到,除了刚刚插入的数据,还有第二条刚刚更新过后的数据。然而这种更新方式只能对已存储的对象进行操作,限制性比较大,接下来我们学习另外一种更加灵巧的更新方式,可以调用以下api:



book.updateAll("name = ? and author = ?","The Lost Symbol","Dan Brown");

1

这里仅贴出其中一条api,其他的可以参考官方文档,这里不再赘述。



3.8 删除数据

使用Litepal删除数据的方式主要有两种,第一种比较简单,就是直接调用已存储对象的delete()方法就可以了,对于已存储对象的概念,我们在之前已经学习过了。也就是说,调用过save()方法的对象,或者是通过LitePal提供的查询API查出来的对象,都是可以直接使用delete()方法来删除数据的。这种方式比较简单,我们就不进行代码演示了,下面直接来看另外一种删除数据的方式。

代码如下:



private void deleteDatabyLitePal() {

        btn_db_delete.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                LitePal.deleteAll(Book.class,"price < ?","15");

                Log.i(TAG,"删除成功");

            }

        });

    }



运行程序,删除过后,按照代码逻辑,已经删除掉了所有price小于15的条目,如图所示:





4.版本异同

之前阅读了郭霖老师所著《Android第一行代码 第二版》时,所记载的Litepal版本为:



compile 'org.litepal.android:core:1.4.1'

1

而的Litepal版本(Java版本,另有Kotlin版本,导入的依赖稍有不同)为:



implementation 'org.litepal.android:java:3.0.0'

1

新旧版本的主要区别是一些类名的划分,例如老板本的DataSupport变成了LitePalSupport,除此之外,一些api的名称也稍有变动,读者在使用时最好可以参考GitHub上的官方文档,及时更新代码,做到与时俱进。



5.源码地址

AFL——Android框架学习


TinyUI-TUIListView最简单的使用

seo达人

      在TinyUI简介的博客中提到其特点中包含一条,即多数大控件的使用方法和android一直,除了语言差异之外,本篇我们就介绍列表控件TUIListView最简单的使用方法。



        列表组件/控件作为目前Android/iOS的APP中最常用的控件,该控件的设计同时参考Android、windows、Qt等使用的经验进行筛选,最终选择了Android的ListView设计,其他平台的列表中使用难以程度或设计上略逊于Android,因为Android给与了开发者最大的发挥控件,你可以在列表中可以显示任何控件。



        TUIListView中的每一行每一列你可以放置任何UI组件,使用TUIListView需要配合TUIAdapter进行使用,而TinyUI和Android一样提供了内置的简单使用的TUISimpleAdapter。TUISimpleAdapter主要用于显示文本(即每一行都是只能显示文字),如果需要在列表中显示其他UI组件,则需要自定义一个Adapter,关于自定义Adapter将在后续进行详细讲解。



        本篇既然是TUIListView最简单的使用,我们就使用TUISimpleAdapter来显示文本你列表,TUISimpleAdapter最好只用于数据步发生变化的情况,因为其存放的数据使用了C++标准库的vector容器,而非使用list容器,vector容器的特点是访问速度快,但其缺点是vector的内存是连续的,因此内容发生变化可能会造成内存申请和拷贝的动作;而list容器使用的双向链表,其特点是插入数据快,但访问速度慢。



        本篇我们仍然使用上一篇中自定义的MyWindow来显示TUIListView。



使用方法/步骤

  1. 定义listView和andapter



            MyWindow中包含TUISimpleAdapter.h的头文件,并定义listView和adapter



    MyWindow.h


    ifndef MY_WINDOW_H

    define MY_WINDOW_H

    include <TUIWindow.h>

    include <TUISimpleAdapter.h>

     

     

     

    class MyWindow : public TUIWindow

    {

    public:

        MyWindow(TUIWindow* parent = nullptr);

        virtual ~MyWindow();

     

        void onShow();

        void onClose();

     

    private:

        TUIListView listView;

        TUISimpleAdapter adapter;

    };

     

    endif // !MY_WINDOW_H

     


  2. 填充数据,并把adapter设置到listView中



    MyWindow.cpp


    include "MyWindow.h"

     

     

     

    MyWindow::MyWindow(TUIWindow* parent)

        : TUIWindow(parent)

    {

        setContentView(&this->listView); // 把listView作为当前窗口的内容视图

     

     

        vector<string> data; // 使用vector<string>类型的data存放数据

     

        for (int32_t i = 0; i < 20; i++)

        {

            data.push_back(to_string(i)); // 生成0~20的数值-转换成字符串,放到data中

        }

     

        this->adapter.setData(data); // 把data设置到adapter中

     

        this->listView.setAdapter(&this->adapter); // 把adapter设置到listView,作为listView数据来源和操作对象

    }

     

    MyWindow::~MyWindow()

    {

    }

     

    void MyWindow::onShow()

    {

    }

     

    void MyWindow::onClose()

    {

    }

    到目前为止窗口显示列表控件已全部完成,接下来和上一篇一样调用MyWindow的show()方法即可显示,最终结果如下图所示:


call、apply、bind 原理实现

seo达人

目录

  1. call 的模拟实现
  2. apply 的模拟实现
  3. bind 的模拟实现
  4. 三者异同



    学习并参考于:



    JavaScript深入之call和apply的模拟实现



    JS的call,apply与bind详解,及其模拟实现





    (一)call的模拟实现

    call 用法 : MDN Function.prototype.call()



    call() 方法使用一个指定的 this 值和可选的参数列表来调用一个函数。



    call() 提供新的 this 值给当前调用的函数/方法。


  5. call 实现主要思路:

    将函数设为对象的属性



    执行该函数



    删除该函数



    另外还有考虑:



    call 函数还能给定参数执行函数

    this 参数不传,或者传null,undefined, this指向window对象

    函数是可以有返回值的
  6. 实现:

    Function.prototype.myCall = function () {

      if (typeof this !== 'function') {

        throw new TypeError('error!')

      }

      let context = arguments[0] || window   //this 参数可以传 null,当为 null 的时候,视为指向 window

      context.fn = this  // 首先要获取调用call的函数,用this可以获取

      let args = [...arguments].slice(1) //从 Arguments 对象中取值,取出第二个到最后一个参数   

      let result = context.fn(...args)  //函数是可以有返回值的

      delete context.fn

      return result

    }


  7. 测试:

    // 测试一下上面实现的myCall

    var value = 2;



    var obj = {

        value: 1

    }



    function bar(name, age) {

        console.log(this.value);

        return {

            value: this.value,

            name: name,

            age: age

        }

    }



    bar.call(null); // 2



    console.log(bar.myCall(obj, 'kevin', 18));

    // 1

    // Object {

    //    value: 1,

    //    name: 'kevin',

    //    age: 18

    // }



    (二)apply 的模拟实现

    apply 用法:MDN Function.prototype.apply()



    apply() 方法使用一个指定的 this 值和可选的参数数组 来调用一个函数。



    apply 的实现跟 call 类似。


  8. 实现:

    Function.prototype.myApply = function () {

      if (typeof this !== 'function') {

        throw new TypeError('error!')

      }

      let context = arguments[0] || window

      context.fn = this

      let result = arguments[1] ? context.fn(...arguments[1]) : context.fn()

      delete context.fn

      return result

    }


  9. 测试:

    var foo = {

        value: 1

    }

    function bar(name, age) {

        console.log(name)

        console.log(age)

        console.log(this.value);

    }

    bar.myApply(foo, ['black', '18']) // black 18 1



    (三)bind 的模拟实现

    bind 用法:MDN Function.prototype.bind()



    bind()方法会创建一个新函数,称为绑定函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。



    bind是ES5新增的一个方法,不会执行对应的函数,而是返回对绑定函数的引用。


  10. 实现:

    Function.prototype.customBind = function () {

      if (typeof this !== 'function') {

        throw new TypeError('error!')

      }

      const that = this   // 首先要获取调用bind的函数,用this获取并存放在that中

      let context = arguments[0] || window

      const args = [...arguments].slice(1)

      return function() {

        return that.apply(context, args.concat([...arguments]))

      }

    }



    (四)三者异同
  11. 相同:

    改变函数体内 this 的指向
  12. 不同:

    call、apply的区别:call方法接受的是参数列表,而apply方法接受的是一个参数数组。

    bind不立即执行。而call或apply会自动执行对应的函数。


JavaScript 的简述与基础语法

前端达人

目录

JavaScript

  1. JS 发展历史
  2. JS 的特点
  3. JS 的组成
  4. JS 的基础语法

    a. 两种引入方式 type src

    b. 三种输出方式 console.log document.write alert

    c. 变量声明 var let const typeof undefined

    d. 数据类型简介 string number boolean object undefined

    e. 运算符 + - * / % = < > && || !

    i. 全等符与不全等符 === !==

    f. 流程控制语句

    i. 条件语句 if else switch case default break

    ii. 循环语句 while dowhile fori forin forof



    JavaScript

    • JS 用于完成页面与用户的交互功能;

    1. JS 发展历史
    JavaScript 在 1995 年由 Netscape 公司的 Brendan Eich,在网景导航者浏览器上首次设计实现而成。因为 Netscape 与 Sun 合作,Netscape 管理层希望它外观看起来像 Java,因此取名为 JavaScript。但实际上它的语法风格与 Self 及 Scheme 较为接近;
    欧洲计算机制造联盟(ECMA)在 1997 制定脚本语言规范 ECMA Script1 (ES1),2009 年发布了 ECMA Script5(ES5),在 2015 年发布了 ECMA Script 2015(ES6),所有的浏览器的都支持 ES6;

  5. JS 的特点

    JS 设计模仿了 Java 语言,不同如下:

    JS 不需要编译,由浏览器直接解释执行;

    JS 是弱类型语言,JS 变量声明不需要指明类型,不同类型的数据可以赋值给同一变量;
  6. JS 的组成

    ECMA Script 是 JS 的基础语法;

    BOM(Brower Object Model)是浏览器对象模型;

    DOM(Document Object Model)是文档对象模型;
  7. JS 的基础语法

    a. 两种引入方式 type src




    <!DOCTYPE html>
    <html lang="zh">
    <head>
    <meta charset="UTF-8">
    <title>JS两种引入方式</title>
    </head>
    <body>
    <!-- JS两种引入方式:JS和CSS一样都需要引入到html页面中,浏览器才会解释执行,JS有两种引入方式:
        1.内嵌(内部)脚本:在script标签中写js代码,script标签推荐放置在body标签的底部,理论上和style标签一样可以放置位置任意;
        2.外部脚步:使用script标签的src属性引入外部js文件,使用注意事项: script标签使用了src属性以后内部的代码不再被浏览器解释执行,script引入外部脚本时不能使用自闭合格式 -->
    <!--告诉浏览器把解析器切换为js解析器 type="text/javascript"可以省略-->
    <script type="text/javascript"> document.write("<h1>内部脚本</h1>");//向body中追加html内容 </script>
    <script src="../js/外部脚本.js"></script>
    </body>
    </html>
    






    b. 三种输出方式 console.log document.write alert

    <!DOCTYPE html>
    <html lang="zh">
    <head>
    <meta charset="UTF-8">
    <title>JS三种输出方式</title>
    </head>
    <body>
    <!-- JS三种输出方式:
        1.输出到浏览器控制台;
        2.输出html内容到页面;
        3.浏览器弹框输出字符 -->
    <script>
    //1.输出到浏览器控制台
    console.log("1. 输出到浏览器控制台");//开发者专用
    //2.输出html内容到页面
    document.write("2. 输出html内容到页面");//向body中追加html内容
    //3.浏览器弹框输出字符
    alert("3. 浏览器弹框输出字符");//阻塞执行
    </script>
    </body>
    </html>
    


    c. 变量声明 var let const typeof undefined


    <!DOCTYPE html>
    <html lang="zh">
    <head>
    <meta charset="UTF-8">
    <title>JS变量</title>
    </head>
    <body>
    <!-- JS变量用来存放数据;
        es5以前变量声明使用var;
        es6之后变量声明使用let,常量声明使用const。他们用于替代es6的var声明方式;
     JS是弱类型语言: 
        声明变量时不知道变量的类型(undefined),只有在赋值之后js变量才确定类型;
        typeof(a) 或 typeof a 输出变量的类型;
        undefined表示变量未赋值,未知类型 -->
    <script>
    //字符串 Java声明 String str ="张三";
    let str ="张三";
    console.log(str);
    //整数 Java声明 int k = 5;
    let k = 5;
    console.log(k);
    //小数 Java声明 float f = 7.5;
    let f = 7.5;
    console.log(f);
    //常量 Java声明 final Integer PI = 3.14;
    const PI = 3.14;
    console.log(PI);
    //演示弱类型语言
    let a;//声明变量不需要指明类型
    console.log(typeof a);//undefined 未赋值类型,未知类型
    a = "你好";
    console.log(typeof a);//string
    a = 123;
    console.log(typeof a);//number
    a = true;
    console.log(typeof a);//boolean
    a = new Object();
    console.log(typeof a);//object
    </script>
    </body>
    </html>
    


    d. 数据类型简介 string number boolean object undefined


    <!DOCTYPE html>
    <html lang="zh">
    <head>
    <meta charset="UTF-8">
    <title>JS数据类型</title>
    </head>
    <body>
    <!-- JS数据类型,常用数据类型:
        1.string 字符串类型;
        2.number 数字.包括整数和小数类型;
        3.boolean 布尔类型.值只有true和false两个;
        4 object 对象类型,空对象使用null表示,有两种格式:
            new Object(); 
            JSON格式.例如:{name:"张三",age:18};
        5.undefined 变量未赋值 -->
    <script>
    //1. string 字符串
    let str = "你好";
    console.log(str);
    console.log(typeof str);//string
    // 2. number 数字
    let n = 123.456;
    console.log(n);
    console.log(typeof n);//number
    // 3. boolean 布尔类型
    let boo = false;
    console.log(boo);
    console.log(typeof boo);//boolean
    // 4. object 对象类型,空对象使用 null表示
    let obj = null;//或 new Object();
    console.log(obj);
    console.log(typeof obj);//object
    // 5. undefined 变量未赋值
    let u = undefined;
    console.log(u);//值是undefined
    console.log(typeof u);//类型是undefined
    // Object类型
    let stu = new Object();//创建一个js对象,js对象的属性想要直接加上
    stu.id = 1;
    stu.name = "刘一";
    stu.age = 18;
    console.log(stu);//{id: 1, name: "刘一", age: 18}
    console.log(typeof stu);//object
    // JS对象取属性值有两种方式:
    // 1. obj.key
    console.log(stu.name);//刘一
    // 2. obj["key"]
    console.log(stu["name"]); //刘一 == stu.name
    let b = "age";
    console.log(stu[b]);//可以取不定属性的值
    </script>
    </body>
    </html>
    


    e. 运算符 + - * / % = < > && || !


    i. 全等符与不全等符 === !==


    <!DOCTYPE html>
    <html lang="zh">
    <head>
    <meta charset="UTF-8">
       <title>JS运算符</title>
    </head>
    <body>
    <!--
    JS运算符
    js运算符和Java运算符基本相同
    只有一个特殊的比较运算符
    === 判断js变量的值和类型都相等才为true
    !== 不全等,判断js变量的值和类型有一个不等就为true
    -->
    <script> let a = 3;
    let b = "3";
    console.log(a == b);//true
    // 全等 运算符 ===
    console.log(a === b);//false
    // 不全等 运算符 !==
    console.log(a !== b);//true
    // 三元(三目)运算符 布尔表达式?真:假
    let str = a===b?"全等":"不全等";
    console.log(str);//不全等
    </script>
    </body>
    </html>
    


    f. 流程控制语句

    i. 条件语句 if else switch case default break


    <!DOCTYPE html>
    <html lang="zh">
    <head>
    <meta charset="UTF-8">
    <title>条件语句</title>
    </head>
    <body>
    <!-- 条件语句JS的条件语句和Java语法基本一样,但是对数据类型的真假判断有些区别 JS中对各种数据类型作为布尔值的特点:(重点掌握) 1. string 空字符串""为false,其余都为true 2. number 数字 只有0为false,其余数字都为true 3. boolean 布尔类型 值只有 true和false 两个
    循环语句
  8. object 对象类型 空对象null表示false,其它对象都是true 5. undefined 变量未赋值 为false 常用语法格式 if ... else if ... else switch case break default -->
    <script>
    //if ... else
    //if(true){
    //if(""){// string 只有空字符为假
    //if(0){number 只有0为假
    //if(false){//boolean false为假 true为真
    //if(null){//object null为假
    //if(undefined){//undefined永为假
    if("undefined"){//undefined永为假
    console.log("满足条件");
    }else{
    console.log("不满足条件");
    }

    //switch case break default
    let k =1;
    switch (k) {
    case 1:
    console.log("111");break;
    case 2:
    console.log("222");break;
    default: console.log("其它情况"); }
    </script>
    </body>
    </html>


    ii. 循环语句 while dowhile fori forin forof


    <!DOCTYPE html>
    <html lang="zh">
    <head>
    <meta charset="UTF-8">
    <title>循环语句</title>
    </head>
    <body>
    <!-- 循环语句
        while,do while,fori 和Java一样;
        forin
            1.遍历出数组中的索引和元素
            2.遍历出对象中的属性和元素
        forof 
            1.遍历出数组中的元素
        forin 与 forof 区别:
            1.forin可以遍历对象,forof不能遍历对象
            2.forin可以遍历出数组中的索引,forof只能遍历出数组中的元素 -->
    <script>
    //while 和Java一样
    let k=1;
    while (k<3){
        console.log(k++);
    }
    
    //do while 和Java一样
    k =1;
    do{
        console.log(k++);
    }while (k<3)
    
    //fori 和Java一样
    for(let i=0;i<3;i++){
        console.log(i);
    }
    
    //forin 可以遍历数组和对象
    let arr = ["刘一","陈二","张三"];//JS数组使用中括号[]定义
    let stu = {id:5,name:"李四",age:18};//JS对象使用大括号定义
        //1.forin 遍历出数组中的索引
    for(let index in arr){
        console.log(index);//数组的索引 0,1,2
        console.log(arr[index]);//数组中的元素
    }
        //2.forin 遍历出对象中的属性名key
    for(let k in stu){
        console.log(k);//字符串属性 id,name,age
        console.log(stu[k]);//对象中的属性值
    }
    
    //forof 可以遍历数组
    for(let e of arr){
        console.log(e);//数组中的元素
    }</script>
    </body>
    </html>
    
    
    
    
    
    ————————————————
    版权声明:本文为CSDN博主「Regino」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/Regino/article/details/105321573
    

原文链接:https://blog.csdn.net/Regino/article/details/105321573 





产品简化改版怎么做?

涛涛

编者按:这篇文章来自 Taras Bakusevych 的文章《How to simplify your design》

如今的数字产品,在不断迭代过程中,会加入更多功能,添加更多的技术支持,更新进阶的特性。而另一方面,企业对于构建简单实用的产品,也同样非常着迷。这当中当中毫无疑问是存在微妙的对抗和冲突的——如何让更好更多的信息,以更加轻量易用的方式呈现出来?这就涉及到产品的简化改版的技巧了。

到底怎么算是简单?

让产品变简单,其实一件困难的事情。

我们可以将「简单」定义为「更加易于理解或完成」,或者是「不困难」。值得一提的是,简单与否,它是主观的,对于一个人而言简单,对于另一个人而言不一定简单。通常,我们是通过下面的三个步骤来判断一件事情是简单还是困难:

在《简单法则》当中,John Maeda 提供了10条法则,这些法则能够帮你平衡商业、技术、设计所带来的复杂性——更通俗的来说,就是如何合理简化但是又能收获更多。

The Laws of Simplicity, John Maeda

Meada,作为麻省理工多媒体实验室的教授,他也是举世闻名的平面设计师。在书中,他非常慎重地探讨了「改进」这一概念,在他看来,它通常并不总意味着「更多」。无论是对于复杂度,还是简化,都只是相对概念。结合合理的培训,培养制造火箭的科学家也不是难事,但是总会有一些关键的因素,会让简单的事情变复杂,在设计中我们应该尽量去规避它们:

那么,我们在具体的产品改版中,要如何合理地简化呢?下面,是我结合这些原则来总结的21条建议:

1、创造有焦点价值的产品

有太多的产品试图让你更用户做更多的事情,每个企业都试图成为行业中的瑞士军刀。但是,如果你希望产更加简单,那么你需要有一个核心价值,并且确定产品真正针对的是谁,并非每个产品都应该成为 Facebook。你的产品的核心价值是什么?

2、删除所有不必要的内容

想要简化产品,最简单的方法,是经过深思熟虑之后再进行简化和删除。如果组件或者模块的功能、价值存疑,那么请删除。次要信息、不常用的控件、分散注意力的样式,都在这个范畴内。就这么简单。一旦采用这一的方式,你会立刻马上看到结果。当然,删除的时候,请务必深思熟虑。

「简单并非完全没有混乱,因为杂乱必然会伴随简化而存在。简单本质上是对产品的定位和目的有更精准的描述。而没有杂乱仅仅意味着这是一件不杂乱的产品,但是这并非简单。」

——乔纳森·艾维

3、将数据转换为有意义的样式

我们日常设计的绝大多数产品,都涉及到需要用户理解大量数据。当用户对于趋势和变化感兴趣的时候,请尽量使用可视化的方式来呈现信息,而不是一堆数字。尝试从数据中提取有效的内容,可视化地呈现在用户眼前,这才是有意义的简化。

4、提供对快速决策的支持

用户长期遭受复杂决策机制的折磨,帮助用户看清各种信息,更好的决策是简化的方向之一。席克定律曾经对此作出过非常经典的解释:用户做选择所需要花费的时间和精力往往会随着选项数量的增加而增加。因此,如果你希望用户决策变得简单,那么你需要简化选择,消除不必要的选项,来帮助用户进行选择。

5、太多选择会吓走用户

当前心理学理论和调研肯定了一件事,就是足够多的选择可能会给人带来积极的情绪,相应的衍生出一个流行的观点,就是选择越多越好,但是人类天性中的管理能力是不足以支撑过多选择的。

果酱实验是消费心理学当中最著名的实验之一:为消费者提供更少的选择,对于销售更加有利。这一点是至关重要的——更少的选择,能够带来更多的销量。

在这个实验当中,选择较少所带来的转化率几乎是更多选择的转化率的10倍。这是一个选择过载带来不良后果的一个重要范例——过多选择抑制了用户选择购买的想法。

6、在提供多种选择的时候,给用户以建议

如果多个选择总是无法避免,那么不妨给用户提供建议,或者告诉用户多数用户的选择。向用户清楚地传达选项之间的差别,这在定价策略上会带来更多积极的效果。

7、将用户注意力吸引到对的区域

当你了解用户流程的时候,就应该有针对性将用户注意力引导到的对的目标上去,找到关键区域,让用户在界面中优先注意到它们。

8、使用色彩和版式来呈现内容的结构层次

你应该经常会听到类似「可读性很差」这样的表述。有很多设计的确存在这样的问题。在设计当中,有太多的因素会影响信息的传达——字体的选取,大小变化、间距、大小写和配色,都会影响到层次结构,影响到用户汲取信息的方式。使用正确的配色和版式,让内容的层次结构更加清晰,最好还能反映出品牌特征,这样就更能增强吸引力和用户对它的印象了。

9、优化组织结构,便于管理

下面两张图当中,要你数清楚有多少个圆点,哪一张图会让你这个过程更快?

如同你所看到的,无序的点状方阵和有序的是截然不同的,后者的认知负担更低,识别速度更快。面对无序的信息和内容,我们需要逐个计数,但是面对有序的信息,则截然不同。

有组织的元素不仅有着更高的识别度,也更容易被记住。在数字产品当中,记住常见控件的位置和功能无疑是重要的。同样是上面这张图,如果不是计数,而是记住每个点的位置,你能不能做到?毫无疑问,缺少组织结构,这是一件艰难的事情。

10、给相关内容分组

在简化复杂页面结构的时候,对组件和内容进行分组通常是最为有效的方法之一。通过层级划分,用户每次需要处理的信息量都会更小,而不是在无关的组件中来回穿梭寻找。借助边框和卡片将相关度更高的内容整合到一起,这是非常便捷的方法。此外,关于关于分组,格式塔原理中的分组原则是非常值得借鉴的:接近性、相似性、连续性、闭合性和连通性。

11、拆分任务,按列布局

几乎任何产品当中都会涉及到不同类别的表单,这是获取用户信息的一种方式。有的时候,即使删除掉一些不必要的字段,表单依然会很长。因此,我们可以将巨大的表单或者任务拆分开来,这样一来,整个执行过程会变得简单不少。

设计表单的时候,尽量使用单列而非多列,这样更加顺畅、易于填写。用户无需思考接下来要填写什么,而是按照一条线继续操作下去即可。

12、透明清晰地展现路径和状态

不确定性让人感到焦虑,尽可能地在设计当中规避这种不确定性。这就是为什么要让用户在任何时候都清晰地知道他在流程中所处的位置,以及接下来会发生什么。总结之前所提供的信息也是个好主意,能够减轻用户的负担,避免反复检查之前的内容。

13、替用户进行计算

人脑对于涉及计算的部分通常会比较费力,对于这些不太占用计算力的内容可以交由系统来进行计算,和计算相比,人的大脑在处理具象化的信息的时候更加擅长。尝试利用系统,而不是将压力转嫁给用户。

14、用逐步展现来隐藏复杂性

渐进式的展开是用户体验设计当中的一种范式,这种方法能够让界面更容易被理解。当涉及到多屏、大量信息和操作的时候,这种逐步展现的机制,可以避免让用户感到不知所措,也可以隐藏一些无关的信息,直到最后用户可以清晰明白内在的关联性为止。逐渐展现的方法,遵循着「从抽象到具体」这样的一种概念,而 iOS 系统当中,所采用的导航方式,无疑就是这种思路的典范。一屏一个步骤,逐渐展现,最后给用户一个确切的结果。

15、善用通用的交互模式和设计范式

用户将绝大多数的精力都耗费在各种各样的其他数字产品上,这意味着用户对于其他的网站和APP的交互模式、使用方法都有着清晰的了解,对于特定的模式有具体的预期。因此,无论你是设计网站,某个 APP ,甚至是冰箱上的控制系统,都可以遵循既有的设计和交互模式。这并不是意味着停止创新,而是在很多事情上,你无需重新造轮子。

16、初次体验应当精简

在设计任何产品的时候,都应该让用户尽快感知到产品的价值。因此,除非是满足功能性的需求,在用户初次打开应用的时候,都尽量把其他的干扰去掉,因为这都是让用户了解产品价值的障碍。第一印象很重要,如果不满意,很多用户会立即离开。

如果你第一次尝试,即使是最简单的操作,可能也是有挑战性的,有时候新手使用 APP 的时候需要引导,但是最好的方法是尽量让体验和功能一目了然,搭配上下文环境的说明引导,而不是提供复杂繁琐的学习材料。

17、结合使用场景,巧用人体工程学

简约易用的产品大多能够合理地结合人体工程学的设计。绝大多数人可能会想到诸如汽车座椅、控制面板和手柄这样的案例,但实际其实远远不止。人体工程学适用于几乎所有涉及人的产品设计。

1954 年,心理学家 Paul Fitts 仔细了解了人的运动系统,并且提出了费茨定律——移动到目标所需要的时间取决于和目标之间的距离,并且和目标大小成反比。这个定律反馈到具体的界面设计上,就是尽可能让常用元素更大,并放置到更容易被触及的地方。

18、提供直接编辑功能和推荐参数

删除不必要的交互组件、视图或者是步骤,是简化过程中必不可少的部分呢。用户应该可以以最快的速度进行操作,比如在表单中直接操作,而不是在弹出框中进行编辑,这个被称为「流程状态」,「流程状态」不应当被弹出框打破。此外,对于很多需要填写的部分,最好提供参数推荐建议功能,就像搜索的实时推荐,让用户可以更为准确地输入。

19、使用智能默认值,减少认知负荷

智能默认值——或者说智能占位符是一个非常有用的策略。一方面可以帮助用户更快更精准的填写表单,另一方面,也给用户提供了相对准确的范例。只不过,默认参数的确定,需要设计师和开发团队对用户的使用场景等信息进行研究,通过测试和调研,确定用户使用状况,以此来确定默认参数应该是多少。如果需要明确地参数或者选项,那么可以将默认值设置为 90% 用户可能会选的选项,并辅以说明。

20、防止出错

出错信息会给用户带来很大的压力,为了避免用户陷入这样的状况,防止用户无法完成任务,请使用数据输入检查的功能,对于格式输入错误的表单和内容,对用户予以警报和提醒,避免错误发生。对于极为重要的操作,在用户提交之前,让用户二次确认信息,做好检查。对于某些强制性、破坏性乃至于不可恢复的操作,确保用户知道这一操作带来的影响。

21、无障碍设计

数字产品的可访问性是老话题了,要让产品和设计对于所有人——包括有视觉障碍的用户,都可以轻松地使用。最常见的,就是不要使用色彩来作为传达信息的唯一途径,确保文本和背景之间有足够的对比度,支持键盘导航操作等等。可访问性问题并不限于特定的群体,坚持不断的改善和提升,有助于每一个用户的体验。

结语

简化并创造易于理解的产品并不容易,但是这是一条几乎任何产品都要走的必经之路,简化的方法和技巧有很多,虽然零碎,但是它们最终会带着产品走向一条更好的道路。

文章来源:优设    作者:Taras Bakusevych

这份上万字的指南,帮你学会用栅格系统构建响应式设计

涛涛

今天,90% 的媒体互动都是基于屏幕的,通过手机,平板,笔记本电脑,电视和智能手表来与外界产生联系。多屏设计已成为商业设计中不可或缺的一部分,响应式设计正迅速成为常态。作为 UI 设计师,我们希望为我们的产品在不同尺寸下都能为用户提供良好的用户体验,栅格系统可以帮助我们做到这一点。

即使是我们只针对一个尺寸进行设计,我们也经常面临设计布局方面的问题。合理运用栅格系统可以帮助我们控制布局结构并实现一致和有组织的设计。栅格系统就像无形的胶水一样凝聚一个设计,即使元素看上去是彼此分离,但通过网格将它们连接在一起,实现良好的层次结构,位置关系和一致性。

设计师和开发者之间的协作过程中,栅格系统在前端开发中是被应用的很广泛一套体系,许多优秀的设计都使用了栅格系统,使用栅格系统可以加速开发并保证视觉还原。栅格系统虽然是传统设计方法中的一部分,但它仍旧能帮助我们去设计这个多终端的世界。看到这里,你可能非常想知道栅格系统在页面中是如何运作的,那么今天我们一起来学习并且实践我们的格栅系统。

「The grid system is an aid, not a guarantee. It permits a number of possible uses and each designer can look for a solution appropriate to his personal style. But one must learn how to use the grid; it is an art that requires practice.」

「栅格系统可以帮助我们设计,但却不能保证我们的设计。它有多种可能的用途,并且每个设计师都可以寻找适合其个人风格的解决方案。但是必须学习如何使用网格。这是一门需要实践的艺术。」

——Josef Müller-Brockmann《平面设计中的网格系统》作者

什么是栅格系统?

栅格系统可以让你依靠秩序与逻辑去完成设计。

早在 20 世纪初,德国、荷兰、瑞士等国的平面设计师们发现通过维持视觉秩序,从而使版面能更加清晰有效地传递信息,二战后这种理念在瑞士得到了良好的发展,直到 20 世纪 40 年代后期,第一次出现了使用网格进行辅助设计的印刷作品。由瑞士设计师大师 Josef Müller-Brockmann(约瑟夫·米勒-布罗克曼)所著的《平面设计中的网格系统》一书,自 1961 年出版以来畅销至今,对设计界有着深远的影响。史称 Swiss Typography Movement (瑞士新浪潮平面设计运动),后来成为全球风靡的 International Typographic Style (国际主义设计风格))。

△ 约瑟夫·米勒一布罗克曼 (Josef Muller-brockmann, 1914-1996)

瑞士的一位平面设计师和教师。1958 年任《新平面设计》(New Graphic Design)主编 1966 年被任命为 IBM 的欧洲设计顾问。布罗克曼因他的极简主义设计与简洁的排版、图形和色彩而闻名,他的设计对 21 世纪的众多平面设计师都产生了重大影响。

栅格系统的优势

1. 减少决策成本提高设计理解力

栅格系统在页面排版布局、尺寸设定方面给了设计者直观的参考,它让页面设计变得有规律,从而减少了设计决策成本;UI 设计也是需要理性的、客观的、具有数学逻辑美感的。熟练运用网格系统能够让你的设计更有秩序和节奏感,页面信息的展现更加清晰,提高阅读效率,从而提供给用户舒适的使用体验。加快认知速度。这意味着用户在使用产品完成特定的任务时,例如发送消息,预订酒店房间或乘车。用户能够连贯地理解并找到下一条信息或下一步要采取的步骤。

2. 响应化

因为人们使用不同类型的设备与产品进行互动,从智能手表的小屏幕到超宽屏电视,交互是流畅的,并且没有固定的尺寸。使用产品时,人们通常会在多个设备之间切换,以完成该产品的单个任务。所以响应式设计不应该是一种品,而是一种必需品。这意味着设计师不能再为单个设备的屏幕构建。多设备环境迫使设计人员根据动态网格系统进行思考,而不是固定宽度。使用网格可以跨不同屏幕尺寸的多个设备创建连贯的体验。

3. 加速团队协作设计

当多位设计师共同设计产品时,一个统一标准就变得尤为重要。如果没有一个统一的框架去约束的话,我们的产品的页面和组件的标准可能各式各样,这样的话整个产品的页面都会比较混乱。因此,网格系统有助于将界面设计工作分开,因为多位设计师可以在统一的布局下进行不同部分工作,并且无缝集成并保持连贯。

4. 加速开发并保证视觉还原

大多数设计项目的实施,涉及到设计者和开发者之间的协作。栅格化提高了页面布局的一致性和复用性;避免了设计师与开发者在细节上的反复沟通确认,从而提升了整个设计开发流程的效率、并能帮助开发者实现较为理想的设计还原。

栅格系统的基本构成

1. 列和槽(Columns and Gutters)

列(Columns) 和槽(Gutters)。列(Column)是内容的容器,水槽(Gutter)用来调节相邻两个列的间距,把控页面留白;列和列间距加上页面边距(Margin)加起来屏幕的水平宽度。列和列间距的内容区域(Content width)由 N个列和(N-1)个水槽组成。通常情况下,web 端采用 12 列,平板采用 8 列,手机采用 4 列。当然,你可以根据项目特点来设计你的网格系统,列和水槽的宽度我们可以利用 8 点网格系统来定义,下面会讲到。列的数量越多,页面就会被分割得越「碎」,在页面设计时就会越难把控,适用于业务信息量大、信息分组较多、单个盒子内信息体积较小的页面设计,列间距宽度数值对页面的影响,与外边距大体类似,即间距越大页面越轻松简单,反之亦然。用户已经习惯通过鼠标滚轮或滚动条(scrollbar)来纵向浏览页面内容,因此竖直方向可以无限延伸,所以栅格系统在竖直方向的栅格可以不体现出来,我们在执行设计时只要在水平方向保持规律的变化就可以了。

2. 页面边距(Side Margins)

页面边距就是内容区域(Content field)以外的空间,比较推荐的设计就是页面边距可以随着屏幕尺寸的增大而增大。页面边距在移动设备上通常是 12Px到 40Px 之间,在平板设备和桌面设备页面边距变化就相当多了。在响应式设计中,你选择了一个页面边距之后,缩小页面宽度时页面还是会有你设置的最小页面边距,直到到达下一个响应点(breakpoint)。当你增大页面宽度时,页面就有更多的页面边距,直到页面宽度到达下一个响应点(breakpoint)。

3. 模块(Field Elements)

模块就是你的设计区块,可以是一段文字,一张图片,或是其他更加丰富的元素。背景元素并不能算作是设计模块,所以并不需要遵循栅格系统。模块的定义是很灵活的,它可以是个小的单位或是元素,也可以是一个元素丰富的区块。

以 12 栅格系统为例,一个 12 栅格系统可以根据业务需要被 2 等分、3 等分、4 等分、6 等分、12 等分,还可以被 1:1:1、1:2:1、1:3:2、2:3:3、1:2、1:3、1:5、3:5 等不对称分割,具体采用哪种比例的组合需要我们根据自己业务需求来定。

4. 8 点网格(8pt spatial system)

栅格系统大的层面可以帮助设计者更好的进行版式设计与内容布局,而小的方面可以辅助设计师规范页面内各种元素的对齐与间距的设定。从用户体验角度来讲,这两者同等重要,从执行层面来讲,我们一般先做版式设计与布局,然后再填充内容、调整细节。

由于列跟水槽的宽度是以网格作为基本单位来增加或者减小,所以栅格化的重要一步就是需要先定义好栅格的原子单位「网格」的大小。目前最普适易用的就是 8 点网格。我们也可以利用 8 点网格法来制定产品中的间距,建立 8 点为一个单位的网格,使用 8 的倍数来定义模块的间距与元素的尺寸。8 点网格有如下几点优势:

  • 目前主流桌面设备的屏幕分辨率在竖直与水平方向基本都可以被 8 整除,使用 8 作为最小原子足够普适。可以确保不同布局之间的视觉一致性,同时可以灵活的适配多种尺寸的设计。以 8 为单位符合「偶数原则」。偶数原则可以在页面缩放中的避免类似于 0.5、0.75、1.25 等次像素的出现,从而使页面各类元素在大多数场景下都能有比较精致的细节表现。
  • 在网格系统中应该更加注重的是间距,而间距要遵循网格系统(例如使用 4、8、16、24、32 等和 8 具有规律的数字)同时在产品中的各类元素也要遵循这类原则(例如图标大小、组件大小等)。所以布局的水平和垂直节奏和各个组件的节奏会相互重叠,整体的设计将更加完整。
  • 开发工程师使用的前端开源组件库比如 Metronic、Antdesign 等也是基于 8 的原子单位来设计,因此如果设计师也使用以 8 为基本单位的栅格系统,开发与设计师相互对接就会更加方便,开发实现页面时也能更高品质地去还原我们的设计。

如果设计上没有立即可识别的间距系统时,这种设计可能会让用户感觉廉价、不一致,而且通常不值得信任。如果设计上遵循一个 8pt 网格系统时,节奏变得可预测和视觉上的愉悦。对于用户来说,这种体验是经过修饰和可预测的,这增加了用户对品牌的信任和喜爱。

无论有多少个设计师在协同合作,现在都有一个一致的间距规范,决策成本将大大降低。设计师可以轻松地从另一个设计师停止的地方开始设计,或者轻松地并行构建。我们定义下规范可以及时和开发同学沟通,因此可以为工程师节省时间。

5. 基线网格(Baseline Grid)

基线网格由密集的水平行组成,这些行提供文本的对齐和间距准则,类似于您在直纹纸上书写的方式。在下面的示例中,每 8px 行在红色和白色之间交替。

△ 基线网格

提示:将所有行高设置为基本单位(8x 或 4px)的增量非常重要,这样您的文本才能与基线网格完美对齐。

△ 字体行高

响应式设计

1. 什么是响应式?

设计师需要通过设计让内容在不同的平台上体验最大化,确保让用户在任何一个屏幕上看到内容的时候,会觉得这些内容就是为这个平台而设计的,而不是单纯的缩放而来。这种无缝的体验,才是跨屏幕设计的真正难点所在。想要制定一套针对不同设备和屏幕的设计方案,你需要一整套的策略。用户体验同时包含了性能、交互、效率等多方面内容,也就是说,对于一个线上的响应式页面,我们不仅要关注视觉上看到的,也要关注我们操作、使用时的感受,这些综合因素最终影响着用户使用时的效率与体验。

2. 响应式设计的核心步骤

确保核心的用户体验

虽然用户体验是无处不在的,但是对于特定产品,最核心的体验是存在的。产品通常是用来解决用户所面临的特定问题的,它的这一特质让产品变得有意义。关键的内容和关键的功能的组合,通常构成了产品的核心用户体验。如果你并没有想明白这个问题,不妨问问自己:用户需要完成哪些最常见/最重要的任务?找到问题的答案之后,你的产品就应当从各个方面、各个渠道,完整而全面地支撑这些功能,帮助用户完成这些任务。举个例子,Uber 的核心用户体验是随时随地叫车,无论设备的屏幕大小如何,你进行的设计全部都应该围绕着这个需求和功能来进行。叫车是 Uber 的核心功能,即使使用 Apple Watch 这种极小的屏幕尺寸都应该顺利地完成这个任务。

敲定你的产品所覆盖的设备类型

现在的移动端设备屏幕尺寸各不相同,单独为某一个设备设计内容无疑是不现实的。根据你的产品覆盖人群、受众分类、使用场景,综合考虑你的内容会优先呈现在哪些设备和平台上,然后有意识地筛选出常见的设备类型:手机,平板,桌面端,智能电视,智能手表……

不同的设备组合通常是基于不同的场景、需求和服务来构成的,用户会针对不同的屏幕进行不同模式的交互,甚至处理的内容也会有差异。比如说,在手机上,用户更加倾向于使用轻量级的任务,并且进行一定量的沟通和交流。在平板上,用户行为更多集中在内容消费上,并且目前平板的使用量被认为在逐步降低。桌面端依然是用户完成较为专业、复杂任务的首选平台,足以应付复杂多样的内容。了解各种设备类型和使用场景是用来构建用户体验的关键。

针对不同内容来匹配用户体验

并非所有的内容都符合不同设备的使用场景,比如智能手表就不适合展示大量的文本内容。你的产品所覆盖的设备组当中,每种设备的使用场景不同,应该匹配的用户体验也不一样。移动端用户和桌面端用户的需求就是不同的,场景差异也很大。以 Evernote 为例,它可以在多种不同类型的设备之间同步和切换,其桌面端版本就针对用户的内容需求进行了优化:Evernote 的桌面端应用程序针对阅读性的内容和多媒体进行了优化,而移动端的 Evernote 则强化了拍摄记录、图片和音频记录的功能:其次,不同的设备屏幕具备不同的输入方式,设计师如果忽略输入方式上的独特性,也常常会出现许多问题,这里就不扩展开来了。

优先为最小的屏幕做设计

一直以来,设计师都习惯从最大的屏幕着手设计,最后考虑最小的屏幕上的显示效果,这意味着绝大多数的设计都是从桌面端开始设计的,通常桌面端的内容和功能更全面。当桌面端的整体设计完成之后,再推进到其他设备端的设计。然而,在进行桌面端设计的时候,我们常常会遭遇「厨房水槽」困境:由于产品通常会牵涉到多个利益相关方,许多多余的功能会被加入进来。而实践经验表明,移动端优先的设计往往能够更好的专注于核心功能,更适合作为产品设计的起点。当你优先设计最小屏幕所需要的界面的时候,这种局面会强制你从最关键最重要的地方开始设计。这也是之前设计圈和产品开发领域一直所强调的「移动端优先」的策略的由来。在此之后,再进行平板、桌面和电视端的设计,就是一个自然地做加法的过程了。在绝大多数的案例当中,最小屏幕通常是手机屏幕。

测试你的设计

产品的测试环境并不一定都得是在现实世界中寻找,但是在尽可能让真实的用户来做可用性测试,并且在产品发布之前解决所有的用户体验上的问题。

3. 为何要利用栅格系统来进行响应式设计?

响应式可以响应的前提有两点:1、页面布局具有规律性、2、元素宽高可用百分比代替固定数值,而这两点正是栅格系统本身就具有的典型特点,所以利用栅格系统进行响应式的设计是顺理成章的,也比较快捷,所以响应式与栅格化天生一对好搭档。

如何建立栅格系统

第1步:确定列的数量

第一阶段先不要限制自己的列数。首先,创建一个低保真或高保真的原型。设计一些基本元素和用户流程。在此之后,就开始设计最优的列数和大小。如果在项目开始设计之后不得不改变我们的栅格系统,不要有负担,我们需要有一些试错的空间。

我们在设计页面时,用到最多的布局方式就是等分布局,即页面内容区域被 N 等分,每一份的宽度则根据屏幕宽度自适应调整。那么就从这个角度出发,思考一下页面的网格应该设置为多少列,才能的满足各种等分布局的需要。与 web 类似,移动端最方便的网格之一是 12 列网格。这个网格将允许我们在一行中同时放置偶数和奇数个元素。

对于移动端来说,12 列网格的缺点是一个列的宽度太小,你可能很少创建一个列宽度的元素。如果你选择 2、4 或 8 列网格,请记住在一行中放置奇数个元素可能会出现的问题。

Pro-Tip:

界面设计通常包含数百个不同的页面,因此,一个网格可能不适合所有的页面。如果需要,创建额外的栅格系统,但不要忘记设计的一致性。网格系统的一致性:相同的布局边距、列之间相等或成比例的水槽,以及更改列本身的宽度时其他模块也需要保持相同的比例。

第2步:定义水槽和边距

首先,让我们先翻阅目标屏幕的设计 Guideline,以找出通常页面边距(Side Margins)。目前,Android 和 iOs 的最小推荐布局边距为 16pt。web 端则依照屏幕尺寸不同而不同。这意味着,如果你希望遵循系统指南,则页面边距不应小于 16pt。(但可以更大的)

在选择 12 列网格时,列之间的水槽不应该太大,因为由于列的宽度小和它们之间的大宽度的水槽,列将在视觉上产生分裂的感觉。同时我建议你选择与8pt 间距系统成比例的水槽大小。所以布局的水平和垂直节奏会相互重叠。水槽与页面边距成比例。那么网格更加一致,也将允许我们轻松地在其中放置特殊元素,如轮播(carousel)。

第3步:定义 8pt间距系统

了帮助不同设计能力的设计者们在界面布局上的一致性和韵律感,统一设计到开发的布局语言,减少还原损耗。在大量的实践中,我们提取了一组可以用于 UI 布局空间决策的数组,他们都保持了 8 倍数的原则、具备动态的韵律感。经过验证,可以在一定程度上帮助我们更快更好地实现布局空间上的设计决策。定义网格系统方法很多,如运用 8 点网格系统、斐波那契数列、某最小原子单位的增量、从底层系统参数化定义间距等,我们以最小原子单位的增量为例去定义网格系统。最小单元格的数值选择需要从两方面考虑:

  • 一方面是该数值是否能被大多数手机屏幕的宽度整除,即广泛的适用性;
  • 另一方面是在具体使用时是否具有一定的灵活性。

在适用性方面,4、6、8、10 这四个数值都是基本可以满足的,在灵活性方面,4px 表现最佳,但是页面就会被分割的非常细碎,在设计时比较难于把控。因此我们需要根据 APP 的实际情况选择合适的数值,4px 或 6px 单元格比较适合页面内容信息较多,布局排版比较复杂的产品。而 8px 单元格对一般的设计场景都可以很好的满足,比较适合大多数的 项目,因此是比较推荐使用的。

那么假设我们以 8 为基准的去延展系统间距,得到如下间距系统:

1、2、8、16、24、32、40、48、56、64、72、80、88、96、192 等,这里都是 8 的倍数或能被 8 整除

但是目前间距数量太多,过于细碎也会导致间距比较乱,所以我们继续优化梳理(以 6 为基准,前面个数是后面个数的 2 倍递增),得到以下间距系统:

1、2、8、16、24、32、48、64、80、96

第4步:sketch布局设置

利用 sketch 的布局设置功能,即可快速搭建出网格系统的参考布局,在平时做设计的过程中,可以经常使用 Ctrl+L 快捷键切换布局的显示,提高设计效率。

我们来解释一下这些设置分别是什么:

  • Total Width:就是内容区域(Container)的值;
  • Offset:表示栅格的偏移量,我们只要设定完成以后按 Center 按钮即可,会自动居中;
  • Number of Columns:就是栅格数;
  • Gutter on outside:是非常重要的设置,勾选以后才能跟前端的栅格算法匹配;
  • Gutter Width:就是栅格之间的间距;
  • Columns Width:就是栅格的宽度。

如何做到响应式?

在传统的栅格化系统设计中,列的宽度和水槽的宽度是保持不变的,只是列的「数量」发生变化。为什么要这么处理呢?这是为了让设计更简单。如果一组三张卡片分别放在桌面的四列上,那么在平板电脑上,会显示两张卡片,并把第三张卡片进行折行显示在第二行上。不需要做任何的调整,因为已经知道它位于第四列上了。在手机上,答案也很简单,只需要一张卡片,其他的就会自动堆到下面的行中。但是目前我有更多的响应策略,例如当视窗(Viewport)发生变化时,内容区域的元素如何去响应,具体到我们当前的栅格系统,就是 Columns、Gutters、Margins 以及由 Columns 跟 Gutter 组成的盒子(BOX)四者的值(主要是宽度)如何变化,以及在这种变化之下我们页面的布局如何调整。

1. 固定栅格或是断点系统(Fixed boxes or breakpoint system)

固定网格,列宽和水槽宽不会改变,只是改变列的数目,当窗口缩放时,排版布局不会发生任何改变,只有当达到一个临界值(开发那边设置好的固定的值),界面才会发生改变。在此之前界面排版都是不变的,就像一部分被切掉了。

如果开发那边写了一个固定栅格,当你从桌面缩小到平板电脑,就像是在桌面的浏览器宽度时,你不会看到任何变化,设计就像是被剪掉了一样。但当达到平板屏幕尺寸临界点时,设计布局马上就会改变,平板电脑上的显示效果就会好起来。如果继续减小这个值,同样的事情也会发生,在到达另一个临界值之前,设计看起来都是不变的。下面是常见的断点系统(Breakpoint System)

如图,响应式是以视窗的最小宽度作为基本依据来制定每种宽度下 Columns、Gutters、与 Margins 的响应策略,也就是说 Viewport Min-width 是做出响应的触发条件,视窗每达到一个最小宽度,就会触发该宽度下预设的页面布局方式,而每种布局都是在该宽度下的最佳布局,也是因此,响应式才会在各种复杂分辨率条件下都能给用户比较好的体验。
每个视窗宽度的最小值是触发响应的关键值,因此我们给这些用于触发的关键值起了个名字叫「Breakpoint」,每个 Breakpoint 触发一种响应策略。

2. 流动栅格(Fluid Grid)

流动栅格系统是编辑内容,仪表板,图像,视频,数据可视化等理想的响应策略。当窗口缩小时,内容将动态地发生变化,文本会进行换行,元素也会变窄。然而,这些元素在内容宽度缩小到下一个临界值之前,布局是不会变化的。在各种情况下,对用户来说,扩展内容的大小比扩展可见内容的数量更有用。

所以我想说的是,断点 BreakPoint 只是一个更改布局的参考点。这就是为什么列宽和水槽的数量不会改变的原因,因为我们想让设计师在考虑布局时能够更容易地创建一致性。内容宽度会随着窗口的缩放而发生改变,例如图片会缩小,文本会换行。水槽的宽度不一定是固定的,可以随着页面宽度变化。

在每个断点处,列计数是固定的,列宽度是最小网格 8PT 的倍数。行高是列大小的倍数,遵循推荐的纵横比。边距和填充是小单位的固定倍数。在断点之间,实际列宽是网格区域的百分比,而不是一个小的单位倍数。内容尺度流畅。

首先从所以屏幕大小中选择一个基本尺寸,然后按照推荐的纵横比以基本大小的倍数构建每个响应式尺寸。当每个块使用相同基础大小的倍数时,就会出现网格。遵循此方法可确保栅格系统一致性,甚至跨产品的一致性。

3. 混合栅格(Hybrid Boxes)

在实际项目中,使用流动网格和固定网格的组合也是常见的做法。网站通常是流动网格,因为它要去适应各种不同终端的大小。后台系统设计、工具型的界面设计就比较经常使用网格和流动网格组合的形式。例如的后台管理系统(dashboard)侧边栏是固定网格,右侧内容是流动网格。混合栅格在每个维度上有不同的缩放规则,所以它们不使用统一的缩放比。当用户需要调整浏览器的大小以使内容在一个维度上伸缩而在另一个维度上不伸缩时,便使用混合网格。

面板对栅格系统的影响

1. 灵活面板(Flexible panels)

灵活的面板允许折叠和扩展状态。面板的展开状态为固定宽度,用户无法调节。当用户将鼠标悬停在折叠的面板上时,面板就会展开。当灵活的面板扩展时,它们要么压缩内容和网格,要么将内容推到浏览器边缘之外。

2. 固定面板(Fixed panels)

固定面板保持静态宽度,不能折叠,也存在于响应网格之外。

3. 悬浮面板(Floating panels)

此面板样式漂浮在主要内容区域之上,不影响响应网格。浮动面板将任何 UI 元素隐藏在其下方,用户必须将其移除。内联菜单、下拉菜单和工具提示也是浮动的。

总结

写这篇文章的目的是想提供一些关于如何在响应式设计中使用栅格系统,我知道对于我自己来说,我花了很多时间理解网格是如何工作的。我在 YouYube 上看了很多视频,也阅读了大量的文章,但每个人都在关注它为什么重要,却不去注重到底怎么在自己的项目中使用这些原则。

你要做的最好的事情就是从现在开始注意那些优秀设计是如何对齐元素的,你将会开始阅读这些设计系统。为了帮助理解,这里有一些设计系统概述了它们的网格使用:

在完全理解了网格的工作原理之后,你将成为了一名更好的设计师,因为你知道了你的设计将如何在临界值之间进行转换。你也可以落地你的设计,使它们能够达到像素级完美。这样的规范带来了更一致,更简洁的设计,当用户从一个界面到另一个界面流转时,这真的提升了产品的档次。我建议在你的设计中去应用这些网格,并和开发同学一起,以实践的方式将它们落地,这将会是一个非常不错的进步。

文章来源:优设    作者:IvanZheng

日历

链接

个人资料

蓝蓝设计的小编 http://www.lanlanwork.com

存档