首页

你知道设计风格里的“等距风格”是啥怎么画不?

博博

你知道设计风格里的“等距风格”是啥怎么画不?

UI巴巴 2018-08-16 20:26:18

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

1.等距是什么?

等距视图是指,绘制物体时每一边的长度都按绘图比例缩放,而物体上所有平行线在绘制时仍保持平行的一种显示方法。

最早是出现在电脑应用程式的图像,以及早期的8位元电子游戏,近几年来的被广泛的使用在因特网、GUI(Graphic User Interface)以及行动游戏等等。

2.等距视图游戏的历史

虽然计算机游戏的历史一直在20世纪70年代初期就看到了一些真正的3D游戏,但是第一个使用上述意义上的等距投影的不同视觉风格的视频游戏是20世纪80年代初的街机游戏。

1)20世纪80年代 

你知道设计风格里的“等距风格”是啥怎么画不?

高速公路遇袭(1985年)

等距视图是一种把2D游戏伪装为3D游戏的显示方法。使用这种方法的游戏有时候会被称作伪3D或2.5D

你知道设计风格里的“等距风格”是啥怎么画不?

蝙蝠侠(1986年)

执行等距视图的方法有很多,但为了简化,最有效最常用的一种——贴图法。从上面两张图可以看出,其上覆盖的菱形网格把地形划分贴图。

2)20世纪90年代 

你知道设计风格里的“等距风格”是啥怎么画不?

最终幻想III(1997)

你知道设计风格里的“等距风格”是啥怎么画不?

文明六世(2016)

在贴图法中,各个视觉元素都被切分为更小的部件,称为“贴图”,都是标准尺寸的。根据预先确定的平面数据——通常是2D数组,这些贴图被组织成游戏世界。

3)目前,2000

你知道设计风格里的“等距风格”是啥怎么画不?

SOCIOBALL(2015)

你知道设计风格里的“等距风格”是啥怎么画不?

纪念碑谷 (2016)

3.等距风格的图标、插画及应用范围

这种插画有一个专门的名字叫“isometric”。

应用范围很广,比如:icon、界面、启动页、插画、游戏、动画视频等等。

你知道设计风格里的“等距风格”是啥怎么画不?

图标

你知道设计风格里的“等距风格”是啥怎么画不?

字体

你知道设计风格里的“等距风格”是啥怎么画不?

插画

你知道设计风格里的“等距风格”是啥怎么画不?

海报

你知道设计风格里的“等距风格”是啥怎么画不?

启动页

4.常用绘制软件

目前常用的软件有PS、AI、C4D。

5.干货来啦~

虽然PS/AI都能画出等距图标,但是现在有一个软件Affinity Designer 比它们画等距图更加轻松。

你知道设计风格里的“等距风格”是啥怎么画不?

首先画等距我们需要画参考线,PS/AI都能画出来,但是方法还是稍稍显得复杂了一点,一些基本功不到位的同学会很头疼这个参考线的画法。Affinity Designer 的网格和轴管理器十分人性化的解决了这个问题。

不但对于新手来说是个不错的选择,而且它完美的融合了Adobe和sketch两个原本不相融的软件。

你知道设计风格里的“等距风格”是啥怎么画不?
你知道设计风格里的“等距风格”是啥怎么画不?
你知道设计风格里的“等距风格”是啥怎么画不?

网格类型选择等轴测,这样网格就会自动生成等距所需要的斜线。

并且它的网格还可以设置吸附功能,也就是画的每个元素都可以自动吸附在网格边缘。

你知道设计风格里的“等距风格”是啥怎么画不?
你知道设计风格里的“等距风格”是啥怎么画不?

另外它还有一个十分便捷的功能,它的图形变形功能也十分强大。鼠标移动到矩形中间节点的会出现上下重叠的箭头(→),如下图:

你知道设计风格里的“等距风格”是啥怎么画不?

这样可以十分轻松的倾斜矩形到任意角度,并且还自动吸附到网格上了。

你知道设计风格里的“等距风格”是啥怎么画不?

拖拽矩形长宽的时候也是根据网格的路径来,不会影响矩形倾斜的角度。

你知道设计风格里的“等距风格”是啥怎么画不?

轻轻松松画一个小icon。

Affinity Designer这个软件目前只适用于苹果操作系统,在App Store商店里有售卖。

案例鉴赏:

你知道设计风格里的“等距风格”是啥怎么画不?
你知道设计风格里的“等距风格”是啥怎么画不?

↑软件:C4D、3D-MAX、MAYA等大部分3D软件

你知道设计风格里的“等距风格”是啥怎么画不?
你知道设计风格里的“等距风格”是啥怎么画不?
蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务


干货:看看国际大厂是如何创建UI组件设计规范?

博博

干货:看看国际大厂是如何创建UI组件设计规范?

UI巴巴 2018-08-20 20:31:25

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

通过本文,你将学习到 Uber ,Pinterest ,Shopify 和 Airbnb 等知名网站如何利用组件构建统一的UI / UX 设计规范 。

干货:看看国际大厂是如何创建UI组件设计规范?

Airbnb通过react-sketchapp将设计与开发之间的组件协作提升到了一个新的水平

在产品中创建和保持UI和UX的一致性,可以带给用户直观的导航体验,并引导他们成功地与应用的不同部分进行交互,而不会产生混淆。

在产品的各个部分和应用之间保持用户界面的一致性,可以创造更有价值的东西——品牌。将用户体验和用户界面品牌化的关键是让用户在与新产品互动时也能感到“宾至如归”,从而提高他们对新产品的的忠诚度和满意度。

那么,如何才能构建有效的UI组件设计规范呢?以下有几个方面需要引起注意。

1、保持视觉和功能一致性

干货:看看国际大厂是如何创建UI组件设计规范?

功能一致性使你的产品更具可预测性。用户能够预测元素的行为方式,这样即使在第一次访问的页面/屏幕上与之交互,他们也能感觉到安全和舒适。

视觉一致性包括UI的颜色,字体,大小,位置和其他视觉方面,它能帮助用户识别UI元素并归类。例如,某种字体颜色可以策略性地用于帮助用户明白他们按下特定按钮时会得到什么。

鉴于目前的行业情况, UI组件还可以作为用户体验组件 ,将功能和视觉一致性结合起来。

基于组件的设计规范可以使应用程序具有视觉和功能上的一致性,这有助于用户感到宾至如归,并能够轻松地得到指导以完成与产品的所需交互。

2、为什么需要组件设计规范?

组件是用于UI设计和开发的一种很好的办法,使用较少的可重用的组件,更好地实现一致性。

干货:看看国际大厂是如何创建UI组件设计规范?

Uber , Pinterest , Airbnb , Walmart ,Atlasssian等公司都通过基于组件的设计规范实现UI的一致性。

Airbnb的设计工作室在构建他们的设计规范时坚持了这种理念:“ 我们的设计应该是统一的平台,通过定义明确和可重用的组件来提率”。

以下是使用组件设计规范的一些优势:

1. 它的可重用性促进了UI和UX的一致,因为组件可以在任何被使用的地方创建一致的体验。

2. 因为较大的组件由较小的组件组成,因此可以利用原子设计概念实现更好的一致性,从而减少意外的和分离的体验。

3. 组件在设计和开发之间提供更好的协作,允许设计语言随着时间变化而发展。在理想情况下,你在Sketch上看到的是使用React构建的内容。

4. 从设计方面来看,如字体,排版,主色调和副色调仍然可以指定为组件设计规范的一部分。

3、建立一致的设计系统

干货:看看国际大厂是如何创建UI组件设计规范?

目前来看,设计规范确实有很多优势。但是,如何才能真正地创建基于组件的设计规范,使设计人员和开发人员可以利用该规范进行协作?

在创建基于组件的设计规范前,你必须了解它是什么。UI设计规范不仅仅是一个组件库,也不仅仅只是组件的颜色,它包括很多方面。对于构成整个产品体验的基本部分而言,它是一个不断增长且不断演变的真实来源。

因此,在制作第一个组件设计规范之前,你必须设置样式指南和设计语言来控制这些组件。

然后,将这些组件的设计原理转化为代码来实现,一步步从较小的原子再到较大的组成部分。

最理想做法的是将所有组件都应该放在一个设计人员和开发人员都可以访问的位置。通过这种方式,设计人员可以监控随着时间的推移而发展的设计语言,而开发人员也可以选择并使用正确的组件。

4、共享组件库

Shopify使用Polaris设计系统,该设计系统包含一个内部反应组件库,旨在为使用Shopify的商家创建更一致的体验。Airbnb使用共享组件库为其生产率带来了巨大飞跃。

干货:看看国际大厂是如何创建UI组件设计规范?
干货:看看国际大厂是如何创建UI组件设计规范?

Pinterest使用格式塔(Gestalt),一个React UI组件库。它“强化了Pinterest的设计语言。通过执行一系列基本的UI组件来简化设计人员和开发人员之间的沟通......“

通过以上的实例不难看出,共享组件库是实现UI一致性的有效的工具。 在我看来,这种一致性不应该被强制执行,而是自然地实现。

组件库基本上是一种在团队构建应用程序时执行一系列UI组件的方法。但是,开发人员不仅局限于库的视觉语言,还局限于库的持续开发。

干货:看看国际大厂是如何创建UI组件设计规范?

当特定应用程序的特定部分需要某个组件时,它可能需要一些调整和修改。设计师和开发人员应该在灵活性和一致性之间找到适当的平衡点。

共享库经常会打破这种平衡并减慢开发速度,这反过来又会影响开发团队对库本身的采用。在任何需要单个组件的地方强制使用一个庞大的库也是没有意义的(关于这个问题我们不要陷入争论不休的辩论了)。

要想实现设计人员与开发人员之间的协作,还必须为组件维护一个实时文档站点,并以某种方式使其可供设计人员和开发人员编辑(Airbnb的react-sketchapp和Figma等工具可以提供帮助)。

这里有23个常用的React UI库,点击即可使用。如果你实现了自己的库,请记住为开发人员留下足够的设计空间,从而保持两者之间的平衡。

5、Bit -作为构建块的组件

Bit是构建组件库的新趋势。

通过使用Bit,你可以组织来自云上不同项目的组件,而无需重构这些项目或现有库。

每个组件都可以正在进行的任何项目中发现,使用或开发,同时可以轻松地跨代码库进行同步更改。

干货:看看国际大厂是如何创建UI组件设计规范?

每个组件都会显示一个实时UI操作系统 ,自动解析文档,测试结果(Bit运行组件单元测试等),以便所有组件都可以被设计和开发团队发现。

干货:看看国际大厂是如何创建UI组件设计规范?

Bit的工作流可让你在UI一致性和设计规则之间找到一个更快,更动态的工作流。它也是开源的,所以可以随意查看。

6、平衡一致性和灵活性

丘吉尔曾经说过“改善就是改变,完美就是经常改变”。如果我们过于严格地执行一致性,这将会影响创新。

干货:看看国际大厂是如何创建UI组件设计规范?

在我们建立新事物的过程中,我们必须对规则进行适当的调整,预留出一些空间给变量,但不能因为调整让事物陷入混乱。

或许这个说法听起来没有什么特别之处,但正确的理念,方法和工具可以帮助你实现UI一致性和创新之间的平衡。以下是一些保持平衡有效的建议。

从设计的角度来看,并非每种风格都应该重新定义和预先定义。

例如,某个组件(导航栏,项目等)可能与应用程序的其余部分相比具有相对大小或边距。在不同的情况下,这些变量可能会发生变化,因此可以预留一些空间出来。

优步和其他团队使用的另一种有用的方法是将基本/全局/基础组件与“辅助”组件分开 。

例如, Uber使用具有超过22种不同颜色和22种值的主要和次要组件,总共484种独特色调。 创建了70多种独特模式 - 每个有Uber服务的国家都有一种独特模式。

设计人员与开发人员的协作是找到这种平衡的关键。一些团队(如沃尔玛实验室 )致力于提高UI组件本身的可重用性,从而缩小与开发人员端的差距。

干货:看看国际大厂是如何创建UI组件设计规范?

正确的工具和工作流程对UI也有很大的帮助,像Bit和Storybook这样的工具就可以帮助促进这种平衡。

在别无选择的情况下,打破一致性、模式 、视觉和文字是一种很好的方式,可以给用户一种熟悉的感觉并减少混乱。 一致的模式 ,可识别的视觉效果和一致的语气可以使用户感觉安全,直观地与你的产品互动。

总结

l 保持UI和UX的一致性可以引导用户成功与您的产品进行交互。

l 设计系统是UI / UX不断发展的主体。基于组件的设计系统具有视觉和功能一致性。

l Uber,Airbnb,Pinterest,Netflix和其他优秀团队使用基于组件的设计系统来创建和发展他们的视觉语言。

l 要构建组件设计系统,您可以创建库,使用Bit并利用不同的工具和方法来逐步扩展它。

l 通过为变量留出空间,使用有用的工具和鼓励协作文化来平衡一致性和灵活性是很重要的。

l 切记:平衡和协作就是一切。这不是一项单一的工作,而是设计师和开发人员共同进行的持续旅程。

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

比起设计和开发流程的选择,还有几个事情更重要

涛涛



如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里


在 Sarah 给 Jimmy 讲完了她在设计上的一些原则之后,Jimmy 就准备开始重新设计那个客户等着要的新的仪表盘界面了。与此同时,他所在的公司 Shmuckle 准备设置一个新的产品经理的职位,并且将会在公司内部选择合适的人员来任职。Jimmy 对此非常有兴趣,实际上,在当前的架构下, Jimmy 是一个非常合适的候选人。但是要担任这个职位,他必须证明自己能够胜任这个职位,证明自己知道如何管理项目和团队。

对于他正在做的这个控制面板的设计项目,他也正在挑选合适的产出流程。用敏捷(Agile)开发流程更好,还是应该用瀑布模型(Waterfall Model)?又或者是循环式开发流程?他觉得跟开发部的同事聊一聊会是更好的选择。

当他找到工程部的 Boris 的时候,他正在楼道里刷推特摸鱼。「用什么流程?那还用问,当然是敏捷啦。这个最好,过程清晰简单,现在没有什么办法比敏捷更好处理各种数字产品的设计和开发啦。」接着,Boris 去隔壁会议室拖出一个白板,并且说道:「给我一个小时,我告诉你关于敏捷开发的一切。接着还能捎带计划一下每周的工作内容,这样你就能完全明白要干啥了。哦,差点忘了,还有几个播客和视频可以帮你更加深入地了解敏捷。」

敏捷开发以用户的需求进化为核心,采用迭代、循序渐进的方法进行软件开发。在敏捷开发中,软件项目在构建初期被切分成多个子项目,各个子项目的成果都经过测试,具备可视、可集成和可运行使用的特征。换言之,就是把一个大项目分为多个相互联系,但也可独立运行的小项目,并分别完成,在此过程中软件一直处于可使用状态。

絮絮叨叨的 Boris 终于找到一个倾诉的对象,Jimmy 一时之间感到颇为尴尬,不知道如何回应。好在这个时候,开发部另外一个部门的 Floris 从门口路过,Jimmy 赶紧喊住他「Floris 我到处在找你,你怎么在这儿啊」说着就拉住 Floris 的手,窜进了另外一个办公室,远离了热情的 Boris。

「干啥?你俩在聊啥?」

「Boris在跟我说敏捷开发……」

「啥玩意儿?他跟你讲敏捷开发?快拉倒吧,他们部门里面唯一敏捷的就手头上的 Macbook。我们这边都用瀑布模型来作为产品开发的流程,因为它是线性的,有着更简单的结构,操作起来也简单,很少会发生混乱。」说着,Floris 从办公室的书架上摸出一堆文档压到 Jimmy 手上。「你要的东西都在里面,祝你好运。如果你需要任何帮助,请在公共的平台上跟我约时间,我们可以开个小会解决一下问题~」说着 Floris 回到自己的桌子边,开始继续干活儿。

瀑布模型(Waterfall Model) 是一个项目开发架构,开发过程是通过设计一系列阶段顺序展开的,从系统需求分析开始直到产品发布和维护,每个阶段都会产生循环反馈,因此,如果有信息未被覆盖或者发现了问题,那么最好「返回」上一个阶段并进行适当的修改,项目开发进程从一个阶段「流动」到下一个阶段,这也是瀑布模型名称的由来。包括软件工程开发、企业项目开发、产品生产以及市场销售等构造瀑布模型。

拿着一堆资料,回到自己的工位前,整个人都要陷入到怠惰的情绪里面,瘫坐在电脑椅上纠结了起来。信息太多了,不知道从何做起。在网上一搜也是成堆的内容,根本不知道从何入手。懵逼了。

Jimmy 决定采用最终的备用方案——万事不决问 Sarah。在 Jimmy 的工作经验当中,老领导 Sarah 总能给他靠谱的建议和可行的方案。

出问题的时候,先后退一步

Sarah 办公室的门从来都是敞开的。当 Jimmy 来找她的时候,Sarah 正在阅读一些有意思的东西。她的办公室里面有很多的书和绿植,漂亮的色彩让 Sarah 的整个工作区域仿佛能够唤起人的创造力和想象力,桌上打开的书页散发着油墨的味道,闻起来让人很有安全感,像家里的书房。「Hey,Sarah,我又有问题来麻烦你啦,你有空么?」

「我的门永远敞开着。这次有啥问题,看看我能怎么帮到你。」Sarah 听到声音就知道是谁,一边放下手头的文档,一边抬头笑着看到略显局促的 Jimmy 。说话间,Jimmy 非常熟悉地跑到办公桌另外一边的椅子上瘫坐下来,Sarah 笑着摇头,拿起咖啡壶给 Jimmy 倒上一杯咖啡。

回到自己椅子上的 Sarah 没有看自己的电脑,而是像心理咨询师一样,盯着 JImmy ,进入了等他倾诉的状态。而 Jimmy 此刻也惊讶于 Sarah 如此洒脱迅速地放下手头的工作,并专注地帮助自己,于是也不再放飞地瘫坐着,直起腰身,开始认真地陈述自己的问题:

「实际上,你之前跟我说的设计原则,让我获益匪浅。我按照你告诉我的方法,找到了症结,解决了问题。但是我现在不仅仅是要设计这个仪表盘界面,我需要开发和实现。有人说敏捷开发比较好,有人说瀑布模型很给力,这些开发方式到底有啥差别,优势具体在哪我并没有搞清楚。有人说我需要的是敏捷开发里面 Scrum,还有人说,它实际上是 shmum,也有人称之为 Bshmum,结果还有朋友告诉我说 Google 的 Design Sprint 才能帮我解决问题。我感觉脑子快要炸了。所以……Sarah 你明白么,我需要帮助了。 」

听到 Jimmy 说到后面,Sarah 就明白了他碰到什么问题了。「Jimmy,没事儿,我们总会在某些时候碰到问题,别人的指导总会派上用场。」

「我可以理解,如果在网上搜索这些相关的信息,会有太多杂乱的内容让你感到信息过载。幸运的是,如果你理解这些东西背后的基本原理,就可以相对轻松地梳理清楚所有的内容了。」

「早知道我应该一开始就来找 Sarah 问问。」Jimmy 不由得对自己抱怨了一句。说着,他在摸起咖啡杯旁边的纸和笔,准备做笔记,就像上次那样。Sarah 看穿了他的小心思,笑道:「不用记。」说着,喝了一口咖啡,然后继续道:「先想想看,我们为什么会有敏捷、瀑布模型、冲刺模型,为什么要用循环工作法呢?」

「为了?」Jimmy 下意识挠头。

「是的,但也不完全是这样。总的来说,我们需要一个过程来呈现产品,因为人类的思维是没有办法直接掌控混乱的事物。此外,一个清晰的、可遵循可记录的流程,能够确保你在完成后,确保产品的整个开发过程是可交付的,细节也是可回溯的。这就是为什么,我们需要这些流程。」

「最首要的问题,不是选择哪个流程,而是要了解这些流程为什么而存在,以及我们可能会碰到什么样的问题。无论你选择哪一个。」Sarah 看了一眼窗外,继续说道:「你有问过公司的其他的同事,他们都遵循什么样的流程么?」Sarah 问道。

「问过了,绝大多数都采用的敏捷和瀑布模型。」Jimmy 说到。

承诺是关键

「首先要告诉你的是,两种方法都很棒。但是绝大多数的公司只会在两种方法当中选择一种。因此,当人们采用敏捷或者瀑布的时候,我们更多看到的是他们所做的设计或者开发的小冲刺。以往,我们会看到团队会在3个月或者半年这样的时间尺度当中,一直保持着高强度冲刺的状态的。在旁观者眼中,会看到一个清晰的故事,或者说整个产品逐渐设计或者开发出来的景象。如今流行的做法是将冲刺划分为很多不同的阶段,这也是为什么如今被称为小冲刺。不过本质上,做的事情和内容并没有改变。」

「另外,很多人会使用敏捷的方法来做项目,过程中会不断的迭代修改。他们希望通过这样的方法来获得更好的结果。实际上,很多团队会持续不断地、长期地坚持这么做,几个月甚至一年半载都没有发布任何东西。如果你在这种情况下,会问自己,到底出了什么问题?我会告诉你,原因在于没有清晰的承诺,以及太多的事情让人分心。大家都不会承诺在一段时间内交付一些东西,使用各种借口不按时、按预算来完成项目。」

「如果这个时间只是一两周,一个月,好吧,或者说一年,这个周期并不重要。重要的是,你不需要拥有一个清晰的过程,并且承诺提供一些东西。当然,这是很有挑战性的。这意味着,在这个情况下,你必须作出一些选择,来完成任务。」Sarah 总结道。

阻碍前进的东西

「到底使用哪种敏捷的方法,采取多少个步骤,或者使用经典的瀑布模型,借助谷歌的设计冲刺,都可以,都没有问题。大家总会认为,采用哪种过程是关键,但是现实是,这个过程始终都只是达成目的一个手段而已。」

「真正的问题在于,人的天性是懒惰的,没有按照承诺交付东西。总是忍不住的拖延,膨胀的自我,办公室政治,爱来事儿的甲方,喜欢变卦的客户,它们还都会像拦路虎一样进入产品和设计的流程。无休止的辩论,不断改变的策略,不断膨胀来回拉锯的会议,最后你只能呆滞地坐在办公室当中,想想自己的生活到底出了什么问题。最后,我想说一下多年前,我自己所经历的一个项目。」Sarah 觉得她应该从具体案例上来说说这个事儿了。

「所以,首先你应该清楚,在一个特定的时间段内,交付一些东西出来。你要保证你的团队不会跳票和拖延,也不会让预算超出计划。你将要在约束中工作。约束其实是一种隐藏的优点,也许并不是每个人都明白。你需要完全保持专注,除了你的和参评之外,不会被其他的任何东西分心。就你的情况而言,你需要专注于这个仪表盘界面的设计和实现。」Sarah 说道。

「团队的规模很重要。不过那是后话,后面咱们再仔细聊。」

「假设,你有一个三个人组成的团队,他们共同负责开发并发布你的产品的下一个功能。具体到你的头上,就是为你开发并实现这个重设计的仪表盘。你需要确保公司的其他人不会前来干涉他们的工作,不会来和他们讨论这个项目以外的任何事情。」

「这一点极为重要。他们必须保持专注。减少被打扰的机率——或者说不被打扰是最好的事情,他们能够专注而清晰的思考问题。除了手头的任务之外,他们不会需要去做其他的任何事情,不会被其他的工作内容所分心。对于如何做手头的工作,什么时候做,具体做什么,他们应当有足够的控制权和自主权。最后,请记住这一点:

团队必须足够小。如果太大,沟通问题一定会成为主要的障碍。每增加一个人,想让大家信息和想法保持一致的成本,就会成倍增加。如果你拥有太多的自由,太多的资源以及大量的人员,你不仅会得到过度的设计,超出常规的工作,需要超出计划的预算,以及一个没有重点,不够出彩的产品。」

问题总是会出现的

「如果你像我说的一样,后退一步来看问题,就会意识到,流程背后所存在的问题,并不是流程本身的优劣,也不关乎公司、人员、国家、文化或者其他。这是关于纪律和约束。不仅是团队本身需要纪律,负责人要有纪律感,业务也需要有纪律约束。如同我们所知道的,团队也好,产品也好,公司也好,它都是自上而下的,顶部的纪律、约束和眼界,决定了底部的纪律、调性和产出。」

「现在,你可能会问自己,如果你的项目出现了问题,会怎么办?那么首先,对于你想要达成的目标,需要一个清晰的愿景或者想法。除非你的愿景和目标足够清晰,否则你是没有办法来提供承诺的。在项目开始之前,这个愿景/目标必须有足够清晰的定义,是否能够达成,难度高低,是否具备可执行性,否则在过程中一定会有所偏离。在这里,给你几个小贴士,务必要记住:

不要自欺欺人,你需要提前计划好整个项目,避免出错。很多事情都会出错,所以你需要有目标有愿景,你需要向着目标前进,并且随时做好解决问题、纠偏的准备。一旦你被其他的因素影响,就很容易增加开发时间、增加预算、招募更多的人手。不要相信所谓的规划和蓝图,那什么都不是。问题是一定会出现的,出错了,就专注于最终目标,抓紧手头的项目,别无其他。」

Sarah 说道这里,Jimmy 已经开始有所思考了。「所以,在我告诉你这些事情以后,对于你你手头的这个仪表盘的项目,你打算下一步要怎么做?」

需要始终牢记的事情

Jimmy 的脑中仍然在反思 Sarah 刚刚说过的话,下意识回复道:「要有远见,目标清晰,为即将出现的错误与问题做好准备,组建一个足够独立的小团队,和公司其他的团队和部门隔离开来,这样可以在不被打断的情况下聚焦于当前的任务,最重要的是,要在承诺的日期前交付承诺的产品。但是我不知道团队要有多小,我应该带多少人?」Jimmy 问道。

「如果我说我知道你要带几个,那么我一定是在骗你。不过,通常而言,你这种规模不算太大的产品,我建议控制在3人以内。你是这个项目的主管设计师,也是产品经理,在设计上已经没有大的问题,你还需要两个开发人员,一个负责前端,一个负责后端,这样足矣。」Sarah 回答道。

「那么我应该花费多少人在这个上面呢?」Jimmy 又问道。

「这个是你的项目,时间应该由你来衡量。不过,你需要一开始就清楚你手头有多少资源,你有多少时间来投入这个项目,有多少可供调用的预算,以及管理团队的耐心达到了什么程度。而且,这个事情最关键的并不是时间,而是你的承诺,以及到达约定时间之后要交付的东西。这不仅是对上层的责任,对于你和你的项目而言,也是一个可供奋斗的目标和清晰的边界。你的项目看起来并不算小,这个人员工作量之下,可能需要花费一个月的时间来进行开发。但是请记住,在一个月的时间内,你必须提交出一个可用的产品出来。从我的角度上来看,我是不允许增加预算和时间的。约束对双方其实都是有利的。」Sarah 说道。

「那么我还是想问最开始的那个问题,到底应该使用敏捷还是瀑布模型?」Jimmy 还是忍不住问道。

「我不知道。」Sarah 坦言道:「你的项目应该由你来决定。对我而言,选择哪个流程其实并不算最重要的问题,相反我刚刚说道的,流程之前的种种问题才是最重要的,关于承诺,团队的构建和管理,这些因素产生的影响更为深远。如果你清楚的知道最终要产出的产品,流程就仅仅只是手段了。」Sarah 笑着总结道。说话间,她伸手去拿之前没看完的文档。「谢谢,Sarah,」Jimmy 笑道:「你好像又救了我一次。」说着 Jimmy 走出了Sarah 的办公室。

「我的门一直都敞开着。」Sarah 低声说道,走远了 Jimmy 大概并没有听到这句低语。

结语

在设计和开发数字产品的时候,每个团队的负责人可以选择自己习惯的或者自己青睐的流程和方法。使用什么样的方法无关紧要,在未来10年,我们可能还会碰到更多的新方法,新的策略。而唯一不变的,始终还是最基本的问题,团队,承诺和交付。

我注意到,有人把产品所使用的敏捷和瀑布模型这类流程称为「项目的上帝」。但是实际上,不管哪种流程,依然会陷入无休止的扯皮会议和无意义的辩论,出现了问题之后,开始修改时间表。「我们无法按时完成功能A,因此我们无法开发模块B,开发人员又需要参与下一个项目,因此我们资源是不够用的,所以呢,这个项目不得不停一个月。」这情况很常见,也是典型的反面案例。

我相信,产品团队应该高度专注于当前的产品,和其他产品的需求、各种无关的事务隔离开来。「Hey,Angela,我们的大客户要求这个今天上线,能不能把你的项目放一边,帮我们把这个产品弄上线?」这也是一个反面案例。拒绝。


蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计  cs界面设计  ipad界面设计  包装设计  图标定制  用户体验 、交互设计、 网站建设 平面设计服务

 



HTML2.1表单标签及属性介绍

seo达人

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

<!DOCTYPE html>

<html>

   <head>

       <meta charset="UTF-8">

       <title>表单标签及属性介绍</title>

   </head>

   <body>

   <!--form:表单标签,在html页面创建一个表单(浏览器上不显示),若要提交数据到服务器则负责收集数据的标签要放到form内 ;-->

       <!--action:(确定表单提交的路径);-->

       <!--method提交方式:get(默认值)有内容 ,post没有-->

       <form action="#" method="get">


           <!--input:输入域标签,获取用户输入信息;-->

           <!--type值不同收集方式不同:hidden(隐藏字段,数据会发送到服务器但浏览器不显示),text(文本框),password(密码框),radio(单选框),checkbox(复选框),

               file(文件上传组件),submit(提交按钮),button(普通按钮),reset(重置按钮);-->

           <!--name:元素名(表单数据需提交到服务器必提供name属性值),服务器通过属性值获取提交数据;-->

           <!--readonly:只读-->

           <!--value:设置input默认值。submit和reset为按键上显示数据-->

           <!--size:大小-->

           <!--maxlength:允许输入的最大长度-->

           隐藏字段:<input type="hidden" name="id" value=""/><br/>

           用户名:<input type="text" name="username" readonly="readonly" value="zhangsan" size="40px" maxlength="20"/><br/>

           密码:<input type="password" name="password"/><br/>

           确认密码:<input type="password" name="repassword"/><br/>

           性别:<input type="radio" name="sex" value="man"/>男

           <input type="radio" name="sex" value="woman"/>女<br/>

           爱好:<input type="checkbox" name="hobby" value="钓鱼"/>钓鱼

           <input type="checkbox" name="hobby" value="打电动"/>打电动

           <!--checked:单选或复选框默认勾选-->

           <input type="checkbox" name="hobby" value="画画" checked="checked"/>画画<br/>

           头像:<input type="file" /><br/>

           <!--select:下拉列表标签-->

           籍贯:<select name="province">

               <!--option:子标签,下拉列表中的一个选项-->

               <option>---请选择---</option>

               <!--value:发送得服务器的选项值-->

               <option value="北京">北京</option>

               <option value="上海">上海</option>

               <!--selected:勾选当前列表项-->

               <option value="广州" selected="selected">广州</option>

           </select><br/>

           自我介绍:

               <!--textarea:文本域-->

               <textarea>


               </textarea><br/>

           提交按钮:<input type="submit" value="注册"/><br/>

           普通按钮:<input type="button" value="普通按钮"><br/>

           重置按钮:<input type="reset"/>

       </form>

   </body>

</html>


蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务


医疗保健类产品设计、界面设计及交互设计灵感

博博

医疗保健类产品设计、界面设计及交互设计灵感

UI巴巴 2018-08-03 21:40:30

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

今天将从医疗保健类产品开始延展到互动和交互界面。很多医疗产品的界面有可能是一个小屏幕,也有可能是非常大的屏幕。

产品

医疗类的产品我们选择了一些可穿戴设备的概念设计。

医疗保健类产品设计、界面设计及交互设计灵感

Kingyo设计的Sange手表

医疗保健类产品设计、界面设计及交互设计灵感

Crux Product Design 和 Chris Pearce 设计的

医疗保健类产品设计、界面设计及交互设计灵感

Amazfit

医疗保健类产品设计、界面设计及交互设计灵感

Gražina Bočkutė为盲人设计的可穿戴配件

交互

我们与不同设备的交互不断变化,将语音用户界面引入医疗行业将彻底改变人们对护理的看法。精细设计的语音助理能够像人一样,更贴心。

医疗保健类产品设计、界面设计及交互设计灵感

Michal Sambora设计的Alexa助理的界面

医疗保健类产品设计、界面设计及交互设计灵感

Gleb Kuznetsov✈设计的ai智能语音助理

医疗保健类产品设计、界面设计及交互设计灵感

SELECTO设计的语音助理

界面

干净,简洁,充满未来感,避免错误的发生。

医疗保健类产品设计、界面设计及交互设计灵感
医疗保健类产品设计、界面设计及交互设计灵感
医疗保健类产品设计、界面设计及交互设计灵感
医疗保健类产品设计、界面设计及交互设计灵感
医疗保健类产品设计、界面设计及交互设计灵感
医疗保健类产品设计、界面设计及交互设计灵感


蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

Android MVP极限封装(一)

seo达人

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

MVP架构在Android这一块已经盛行依旧,对于一些学习能力比较强的人来说,已经能够运用自如甚至改造优化了,对于吾等菜鸟,却是如此的陌生,今日这篇博客,算是小弟在学习和应用上的一点总结罢了,如有不足,还请各位大神不吝指教。

MVP架构是什么就不多说了,博主主要很大家分享的是,如何设计MVP架构。

先来分析一下MVP如何使用:M-V-P三层之间,P作为中间层,负责M,V之间的数据交互的中介,将数据从M层获取,处理之后提交到V层,换句话说,V需要持有P的实例,P层需要持有V的实例。原理很简单,使用泛型对数据进行封装处理: 
1.定义一个V层的空接口,主要是方便封装:

/**
 * V层接口
 */ public interface IView { }
            
  • 1
  • 2
  • 3
  • 4
  • 5

2.定义一个P层的接口:

/**
 * 抽象为接口
 * 
 */ public interface IPresenter<V extends IView> { /**
     * 绑定视图
     * 
     * @param view
     */ void attachView(V view); /**
     * 解除绑定(每个V记得使用完之后解绑,主要是用于防止内存泄漏问题)
     */ void dettachView();

}
            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

3.封装P基类:绑定解绑V实例

/**
 * 抽象类 统一管理View层绑定和解除绑定
 *
 * @param <V>
 */ public class BasePresenter<V extends IView, M extends IModel> implements IPresenter<V> { private WeakReference<V> weakView; protected M model;

    public V getView() { return proxyView;
    } /**
     * 用于检查View是否为空对象
     *
     * @return */ public boolean isAttachView() { return this.weakView != null && this.weakView.get() != null;
    } @Override public void attachView(V view) { this.weakView = new WeakReference<V>(view);
    } @Override public void dettachView() { if (this.weakView != null) { this.weakView.clear(); this.weakView = null;
        }
    }
}
            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

4.M层封装:

/**
 * M层
 */ public interface IModel { } /**
 * 登录model
 * Created by admin on 2018/2/5.
 */ public interface ILoginModel extends IModel { void login();
} /**
 * 登录
 * Created by admin on 2018/2/5.
 */ public class LoginModel implements ILoginModel { @Override public void login() { // TODO: 2018/2/5 发起登录请求  }
}
            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

之后,将数据提交到activity或者fragment就行了。 
最基本的铺垫已经做好了,接下来就该封装View了:

/**
 * Created by admin on 2018/2/5.
 */ public abstract class MvpActivity<V extends IView, P extends BasePresenter<V>> extends AppCompatActivity implements IView { private P presenter;

    @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState);
        ...
        presenter=getPresenter();
        presenter.attachView(this);
    } protected P getPresenter() { return presenter;
    } protected void setPresenter(P presenter) { this.presenter = presenter;
    } protected V getView() { return (V) this;
    }
    ...
    @Override protected void onDestroy() {
        presenter.dettachView();
        ... super.onDestroy();
    }
}
            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

收工,MVP基础框架搭建完成了。没错,就是基础框架,但是能不能用呢,让我们拭目以待吧。 
先来写一个View:

public interface ILoginView extends IView { void onLoginSuccess(); void onFailed();

}
            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

然后是Presneter:

/**
 * Created by admin on 2018/2/5.
 */ public class LoginPresenter extends BasePresenter<ILogin, LoginModel> { public LoginPresenter() {
        model = new LoginModel();
    }

    public void login(){
        model.login(new LoginCallBack() { @Override public void onSuccess() { if(null!=(ILogin)getView()){
                    weakView.onLoginSuccess();
                }
            } @Override public void onFailure() { if(null!=(ILogin)getView()){
                    weakView.onFailure();
                }
            }
        });
    }

}
            
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

最后来完成Activity的逻辑:

public class LoginActivity extends MvpActivity<ILoginView, LoginPresenter> implements ILoginView { ...
    @Override public LoginPresenter getPresenter() { return new LoginPresenter();
    } public void login(View view) {
        String name = etUserName.getText().toString();
        String pwd = etUserPwd.getText().toString();
        getPresenter().login(name, pwd);
    }

    @Override public void onLoginSuccess() {

    }

    @Override public void onFailed(){

    ...
}


    




    


蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务


布局怎么做到不单调而有层次?来看高手的9个技巧

涛涛

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

简单布局怎么做到不单调而有层次?看看设计师 Czékmány Zoltán 的9个技巧。

蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计  cs界面设计  ipad界面设计  包装设计  图标定制  用户体验 、交互设计、 网站建设 平面设计服务

全面易懂!写给新手的信息架构设计指南

涛涛


如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

很多产品设计师,在画原型或者设计 UI 的时候痴迷于工具的使用,拿到需求文档之后急于动手画图,忽略了信息架构设计对于产品的作用。


信息架构作为一个产品的骨架,是产品非常重要的一部分,它决定了一个产品

的布局和未来的发展方向以及用户对一个产品的最初印象和整体体验。毫不夸张的说,好的产品信息架构是产品成功的一半。

那么到底什么是产品的信息架构呢?该如何设计产品的信息架构?如何评判一个产品信息架构的好坏?我们接着往下看:

一、信息架构的概念

让我们来看一个例子:

一个饭店需要有哪些设施,如果你是饭店的老板如何合理的排布这些设施,可以让客户感觉很舒服的用餐,这个过程就是一个信息架构的过程。他可以让客户对你的饭店产生好感,从而下次用餐的时候还会想到来你这里吃饭。

在排布饭店设施的过程中我们要遵循一些规范,比如用户的习惯或者施工规范等,正是因为需要遵循这些规范,所以我们需要一个信息架构来体现这些。

比较官方的信息架构解释是这样的:信息架构设计是对信息进行结构、组织以及归类的设计,好让使用者与用户容易使用与理解的一项艺术与科学。

简单来说,信息架构设计就是让用户可以 容易地理解你的产品是怎样的。让他们在使用你产品的时候可以更顺利更自然。就像一进入饭店就会有一种感觉,门口是等餐的地方,进去就应该吃饭,如果找洗手间一定不会往门口走,而会往深处走。这就是信息架构的好处:他让用户使用同类产品时更容易上手和理解,让产品更容易被接受。

二、为何需要信息架构设计

那对于线上产品来说为什么需要合理的信息架构呢?大家来看下边3组 app 的 tab栏截图。你能仅仅从 tab栏就看出这款 app 是什么类型的 app,如何使用吗?

很明显的,第一个是一款购物类 app,第二个是一款图片社交类的 app,第三个是微信的 tab,虽然首页名称是微信,但是我相信如果把名称换成「聊天」,你还是能认出这是微信的 tab栏。

从底部标签栏就可以大致看出产品是用来干嘛的,这就是信息架构的作用。一个合理的信息架构可以让产品非常容易被用户理解,可以让用户第一眼对产品有一个简单的认知,指导自己可以用产品做什么事,指导产品提供什么服务。

再看一组反例:

这三组 tab栏就让人很困惑了,看了半天你也许根本不知道这几款 app 是做什么用的,以及如何使用。如果你让用户很困惑,他会分分钟抛弃你的 app。

所以信息架构的核心目标是为用户提供更好的体验,获得更高的留存率。

一款信息架构良好的产品必然遵循以下两个标准:

  • 让用户打开 app 的第一秒就知道这是一款什么 app,怎么用;
  • 用户想要使用某一功能时,能够第一时间找到。

我们通过这两个标准来印证下上边3个正面案例的信息架构:

相信你能很快速的识别出这款软件的用途和用法,这就给提升留存提供了基础。

那么如果信息架构像架构一个饭店一样简单,那么信息架构为何需要设计?

因为你的实际产品功能可能有这么多:

毕竟我们不是支付宝,没办法把功能像豆腐块一样堆叠起来,我们需要一些科学的设计方法。

三、如何设计信息架构

合理的信息架构设计需要考虑5个步骤:

下面我来分步讲解一下。

1. 了解用户,场景,习惯

首先你的产品是给到用户用,你当然要最大限度的了解你的用户,我们先来看下一个概念:「心智模型」。

心智模型是经由经验及学习,脑海中对某些事物发展的过程,所写下的剧本。人类在经历或学习某些事件之后,会对事物的发展及变化,归纳出一些结论,然后像是写剧本一样,把这些经验浓缩成一本一本的剧本,等到重复或类似的事情再度发生,我们便不自觉的应用这些先前写好的剧本,来预测事物的发展变化。心智模型是你对事物运行发展的预测。再说得清楚一点,你「希望」事物将如何发展,并不是心智模型,但你「认为」事物将如何发展,就是你的心智模型了。

假设你从没见过 iPad,而我刚递给你一台并告诉你可以用它来看书。在你打开 iPad 使用它之前,你头脑里会有一个在 iPad 上如何阅读的模型。你会假想书在 iPad 屏幕上是怎样的,你可以做什么事情,比如翻页或使用书签,以及这些事情的大致做法。即使你以前从没有使用过 iPad,你也有一个用 iPad 看书的「心智模型」。你头脑里的心智模型的样式和运作方式取决于很多因素。

用户往往带着以往使用 APP 的一些习惯来使用产品;线下做同一件事的习惯、生活习惯、心智模型等。要考虑哪些是可以创新的,哪些是用户习惯,要在不妨碍用户习惯的情况下作出更能让用户接受的创新。

你要考虑清楚4个问题:

用户通常用你的产品做什么?

用户用你的产品来做什么?用来看新闻还是用来聊天?一定要考虑清楚用户的核心流程。从核心流程中提取信息架构的基础形式。

用户用这类产品最关心什么?

用新闻app 时咨询的真实性实效性,购物类app 精准搜索和售后功能,就是你的用户关注点在哪里,这是一个很好的突破口。

用户有哪些思维定式?

和用户年龄身份相关的属性,产品体验符合相应用户的思维模式,心智模型,用户就会比较容易接受。

用户用什么类似的产品?

类似的产品也会带来一些用户习惯,迎合这些习惯也会让用户快速上手接受产品。

了解了你的用户场景和使用习惯之后你会知道如何做出符合用户心智的,容易被接受的产品,你不需要担心做的产品没有差异性或者没有竞争力,我们可以在核心流程之外做出创新点,让用户觉得你的产品又好用又有些不一样。

2. 了解业务

这里的业务包括与产品接触的内部及外部的人提出的需求,比如公司的运营,市场,销售,BD,公司的外部合作伙伴等。

这些人的需求我们也要收集,比如运营人员想更方便的管理注册用户,销售想更多的添加广告位,市场推广人员要求能统计不同渠道带来产品的下载量,注册数,活跃数,合作伙伴需要进行账号,内容互通等,总之只要与业务有关的人的意见,尽可能的在产品设计前多收集,即使做不了,也告诉他们原因,要不然产品上线后就等着被他们吐槽吧。

3. 调研竞品的信息架构

在做一款 app 时,我们面临了和无数竞品争抢用户的局面,这时候分析竞品就非常必要,我们需要在知己知彼的前提下,做好核心流程功能,再思考如何在差异功能上做好突破。

首先我们需要把竞品功能梳理成思维导图:

其实思维导图就是信息架构比较基础的形式了,但是光有思维导图没用,我们需要对思维导图进行分析。

我以前做过的一款人脉 app 为例,当初对比了领英、赤兔和脉脉,分析了这4款 app 的思维导图后得出的共性和差异点:

共性就是要符合用户使用习惯的地方,如果你调研的3-5个产品都这么做了,很可能这里是产生用户习惯的地方,是我们需要去遵循的,这是获得用户好感度的基础。

分析产品时你一定也会得出一些产品差异的地方,而这些差异就是你的产品竞争点,也是别人用你的 app 不用其他 app 的理由。比如人脉软件都会有社交相关的功能,但是脉脉会比较注重职场招聘、直播等互联网职场人比较关心的点,这样对应的用户群体就比较会吃你这套,会提升用户的粘性。

相信你在梳理了竞品的信息架构,总结了共性和差异点之后对产品的信息架构已经有一个比较清晰的认知了,在做自己产品信息架构的时候也会更胸有成竹。但是最后还有一件事我们可以做,就是对我们的要做的产品功能做卡片分类。

4. 卡片分类

卡片分类法是我们工作中常用到的一种方法,它可以在用户侧再一次印证和检测我们的产品信息架构。

卡片分类法就是让用户对功能卡片进行分类,组织,并给相关功能的集合重新定义名称的一种自下而上的整理方法。

说直白点就是准备一堆卡片,在这些卡片上写上你所需要包含的功能名称,然后给到用户侧,让用户进行分类,让用户进行组织,来了解用户到底觉得这些功能应该怎么合并怎么归类的一种方法。它可以帮助你站在用户角度去了解用户是怎么认定这些功能的,也可以在卡片分类法的过程中更加了解用户是怎么想的。

卡片分类法大概的步骤和注意点是这样的:

卡片分类法最终会产出这样的一个树形图:

5. 产出信息架构

其实到这一步信息架构大概的雏形已经有了,你可以用 axure 或者类似 mindnode 的软件把信息架构梳理出来。

接下来你要对信息架构进行重要性分级,这样在产品开发的前期可以帮助梳理产品研发的优先级,集中精力解决用户的最大痛点。在产出页面时也可以更好的把控页面元素的大小层级,位置关系等。

最后你需要注意层和度的平衡:层一般不超过5层,超过操作困难。度过多会让用户认知成本增加,容易找不到想找的内容。这里的度指的是同一页面展示的信息量。


蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计  cs界面设计  ipad界面设计  包装设计  图标定制  用户体验 、交互设计、 网站建设 平面设计服务


Retrofit源码分析

seo达人

如果您想订阅本博客内容,每天自动发到您的邮箱中, 请点这里

1、简介

retrofit是一个封装okhttp请求的网络请求库,可以通过Rxjava适配返回信息。

2、原理分析

我们通过Retrofit.Builder建造者模式创建一个Retrofit实例对象

public static final class Builder {
    /**
      *Android线程切换的类 
      */
    private final Platform platform;
    private @Nullable okhttp3.Call.Factory callFactory;
    private HttpUrl baseUrl;
    private final List<Converter.Factory> converterFactories = new ArrayList<>();
    private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();
    private @Nullable Executor callbackExecutor;
    private boolean validateEagerly;

    Builder(Platform platform) {
      this.platform = platform;
    }

    public Builder() {
      this(Platform.get());
    }

    Builder(Retrofit retrofit) {
      platform = Platform.get();
      callFactory = retrofit.callFactory;
      baseUrl = retrofit.baseUrl;

      converterFactories.addAll(retrofit.converterFactories);
      // Remove the default BuiltInConverters instance added by build().
      converterFactories.remove(0);

      callAdapterFactories.addAll(retrofit.callAdapterFactories);
      // Remove the default, platform-aware call adapter added by build().
      callAdapterFactories.remove(callAdapterFactories.size() - 1);

      callbackExecutor = retrofit.callbackExecutor;
      validateEagerly = retrofit.validateEagerly;
    }

    public Builder client(OkHttpClient client) {
      return callFactory(checkNotNull(client, "client == null"));
    }

    public Builder callFactory(okhttp3.Call.Factory factory) {
      this.callFactory = checkNotNull(factory, "factory == null");
      return this;
    }

    public Builder baseUrl(String baseUrl) {
      checkNotNull(baseUrl, "baseUrl == null");
      HttpUrl httpUrl = HttpUrl.parse(baseUrl);
      if (httpUrl == null) {
        throw new IllegalArgumentException("Illegal URL: " + baseUrl);
      }
      return baseUrl(httpUrl);
    }

    public Builder baseUrl(HttpUrl baseUrl) {
      checkNotNull(baseUrl, "baseUrl == null");
      List<String> pathSegments = baseUrl.pathSegments();
      if (!"".equals(pathSegments.get(pathSegments.size() - 1))) {
        throw new IllegalArgumentException("baseUrl must end in /: " + baseUrl);
      }
      this.baseUrl = baseUrl;
      return this;
    }

    public Builder addConverterFactory(Converter.Factory factory) {
      converterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }

    public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
      callAdapterFactories.add(checkNotNull(factory, "factory == null"));
      return this;
    }

    public Builder callbackExecutor(Executor executor) {
      this.callbackExecutor = checkNotNull(executor, "executor == null");
      return this;
    }

    public List<CallAdapter.Factory> callAdapterFactories() {
      return this.callAdapterFactories;
    }

    public List<Converter.Factory> converterFactories() {
      return this.converterFactories;
    }

    public Builder validateEagerly(boolean validateEagerly) {
      this.validateEagerly = validateEagerly;
      return this;
    }

    public Retrofit build() {
      if (baseUrl == null) {
        throw new IllegalStateException("Base URL required.");
      }

      okhttp3.Call.Factory callFactory = this.callFactory;
      if (callFactory == null) {
        callFactory = new OkHttpClient();
      }

      Executor callbackExecutor = this.callbackExecutor;
      if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
      }

      // Make a defensive copy of the adapters and add the default Call adapter.
      List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
      callAdapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

      // Make a defensive copy of the converters.
      List<Converter.Factory> converterFactories =
          new ArrayList<>(1 + this.converterFactories.size());

      // Add the built-in converter factory first. This prevents overriding its behavior but also
      // ensures correct behavior when using converters that consume all types.
      converterFactories.add(new BuiltInConverters());
      converterFactories.addAll(this.converterFactories);

      return new Retrofit(callFactory, baseUrl, unmodifiableList(converterFactories),
          unmodifiableList(callAdapterFactories), callbackExecutor, validateEagerly);
    }
 } 
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129

通过Retrofit.Builder中build方法创建一个Retrofit实例对象,在创建Retrofit时会判断用户创建OkhttpClient对象,没有创建Retrofit会创建一个默认okhttpClient对象,然后设置Platform中的主线程线程池,设置线程池处理器交给主线程Looper对象。然后创建一个Retrofit对象。我们通过Retrofit.create创建一个接口代理类

 public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, @Nullable Object[] args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.adapt(okHttpCall);
          }
        });
  } 
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

在调用Creater方法时,通过代理类创建Service实例对象,当我们通过接口实例对象调用方法时,通过invoke方法时,通过Method创建一个ServiceMethod对象,然后把ServiceMethod存储起来

 public ServiceMethod build() {
          callAdapter = createCallAdapter();
          responseType = callAdapter.responseType();
          if (responseType == Response.class || responseType == okhttp3.Response.class) {
            throw methodError("'"
                + Utils.getRawType(responseType).getName()
                + "' is not a valid response body type. Did you mean ResponseBody?");
          }
          responseConverter = createResponseConverter();

          for (Annotation annotation : methodAnnotations) {
            parseMethodAnnotation(annotation);
          }

          if (httpMethod == null) {
            throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
          }

          if (!hasBody) {
            if (isMultipart) {
              throw methodError(
                  "Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
            }
            if (isFormEncoded) {
              throw methodError("FormUrlEncoded can only be specified on HTTP methods with "
                  + "request body (e.g., @POST).");
            }
          }

          int parameterCount = parameterAnnotationsArray.length;
          parameterHandlers = new ParameterHandler<?>[parameterCount];
          for (int p = 0; p < parameterCount; p++) {
            Type parameterType = parameterTypes[p];
            if (Utils.hasUnresolvableType(parameterType)) {
              throw parameterError(p, "Parameter type must not include a type variable or wildcard: %s",
                  parameterType);
            }

            Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
            if (parameterAnnotations == null) {
              throw parameterError(p, "No Retrofit annotation found.");
            }

            parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
          }

          if (relativeUrl == null && !gotUrl) {
            throw methodError("Missing either @%s URL or @Url parameter.", httpMethod);
          }
          if (!isFormEncoded && !isMultipart && !hasBody && gotBody) {
            throw methodError("Non-body HTTP method cannot contain @Body.");
          }
          if (isFormEncoded && !gotField) {
            throw methodError("Form-encoded method must contain at least one @Field.");
          }
          if (isMultipart && !gotPart) {
            throw methodError("Multipart method must contain at least one @Part.");
          }

          return new ServiceMethod<>(this);
        }

    private CallAdapter<T, R> createCallAdapter() {
            /**
             *获取方法返回值类型
             */
          Type returnType = method.getGenericReturnType();
          if (Utils.hasUnresolvableType(returnType)) {
            throw methodError(
                "Method return type must not include a type variable or wildcard: %s", returnType);
          }
          if (returnType == void.class) {
            throw methodError("Service methods cannot return void.");
          }
          //获取注解信息
          Annotation[] annotations = method.getAnnotations();
          try {
            //noinspection unchecked
            return (CallAdapter<T, R>) retrofit.callAdapter(returnType, annotations);
          } catch (RuntimeException e) { // Wide exception range because factories are user code.
            throw methodError(e, "Unable to create call adapter for %s", returnType);
          }
        } 
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

在创建ServiceMethod时,获取我们okhttp请求是否有返回值,没有返回值抛出异常,然后获取注解信息,然后获取retrofit中CallAdapter.Factory,然后调用get方法,我们在通过rxjavaFactoryAdapter.create创建的就是实现CallAdapter.Factory对象,然后调用CallAdapter.Factory中respenseType方法,然后通过我们传递converter对数据进行序列化,可以通过gson和fastjson进行实例化对象,然后通过parseMethodAnnomation解析请求类型

 private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
          if (this.httpMethod != null) {
            throw methodError("Only one HTTP method is allowed. Found: %s and %s.",
                this.httpMethod, httpMethod);
          }
          this.httpMethod = httpMethod;
          this.hasBody = hasBody;

          if (value.isEmpty()) {
            return;
          }

          // Get the relative URL path and existing query string, if present.
          int question = value.indexOf('?');
          if (question != -1 && question < value.length() - 1) {
            // Ensure the query string does not have any named parameters.
            String queryParams = value.substring(question + 1);
            Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);
            if (queryParamMatcher.find()) {
              throw methodError("URL query string \"%s\" must not have replace block. "
                  + "For dynamic query parameters use @Query.", queryParams);
            }
          }

          this.relativeUrl = value;
          this.relativeUrlParamNames = parsePathParameters(value);
        } 
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

通过注解类型获取到请求类型时,通过调用相关方法解析获取到请求url,然后通过注解获取方法中是否有注解字段,有注解信息存储到Set集合中。然后创建一个OkhttpCall对象,通过调用serviceMethod.adapt方法做网络请求,serviceMethod.adapt调用是callAdapter中的adapt方法,如果用户没有设置callAdapter模式使用的是ExecutorCallAdapterFactory中的adapt方法

 public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
            if (getRawType(returnType) != Call.class) {
                return null;
            } else {
                final Type responseType = Utils.getCallResponseType(returnType);
                return new CallAdapter<Object, Call<?>>() {
                    public Type responseType() {
                        return responseType;
                    }

                    public Call<Object> adapt(Call<Object> call) {
                        return new ExecutorCallAdapterFactory.ExecutorCallbackCall(ExecutorCallAdapterFactory.this.callbackExecutor, call);
                    }
                };
            }
        } 
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

在ExectorCallAdapterFactory中调用组装的Call方法中enqueue方法调用异步网络请求,成功后通过Platform中MainThreadExecutor切换到主线程。在调用callback中的enqueue,onResponse和onFairlure方法时实际是调用到OkhttpCall方法的onResponse方法,在OkHttpCall.enqueue中重新组建OkHttp.Call url和参数信息,然后封装请求,请求成功后通过parseResponse解析返回信息状态,然后把返回信息状态成ResponseBody对象,调用ServiceMethod.toResponse解析,在toResponse中实际是我们设置ConverterFactory对象解析数据,完成后调用callBack中onSuccess方法。

 @Override public void enqueue(final Callback<T> callback) {
        checkNotNull(callback, "callback == null");

        okhttp3.Call call;
        Throwable failure;

        synchronized (this) {
          if (executed) throw new IllegalStateException("Already executed.");
          executed = true;

          call = rawCall;
          failure = creationFailure;
          if (call == null && failure == null) {
            try {
              call = rawCall = createRawCall();
            } catch (Throwable t) {
              throwIfFatal(t);
              failure = creationFailure = t;
            }
          }
        }

        if (failure != null) {
          callback.onFailure(this, failure);
          return;
        }

        if (canceled) {
          call.cancel();
        }

        call.enqueue(new okhttp3.Callback() {
          @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {
            Response<T> response;
            try {
              response = parseResponse(rawResponse);
            } catch (Throwable e) {
              callFailure(e);
              return;
            }

            try {
              callback.onResponse(OkHttpCall.this, response);
            } catch (Throwable t) {
              t.printStackTrace();
            }
          }

          @Override public void onFailure(okhttp3.Call call, IOException e) {
            callFailure(e);
          }

          private void callFailure(Throwable e) {
            try {
              callback.onFailure(OkHttpCall.this, e);
            } catch (Throwable t) {
              t.printStackTrace();
            }
          }
        });
      }
蓝蓝设计www.lanlanwork.com )是一家专注而深入的界面设计公司,为期望卓越的国内外企业提供卓越的UI界面设计、BS界面设计 、 cs界面设计 、 ipad界面设计 、 包装设计 、 图标定制 、 用户体验 、交互设计、 网站建设 平面设计服务

如何用最简单的点线面,解决没灵感?

鹤鹤

点、线、面和构成手法,就像是大厦的基石一样,看起来毫不起眼,但力量却无比强大。

日历

链接

个人资料

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

存档