首页

【CSS基础学习】复杂选择器

前端达人

文章目录

    • CSS第二课-复杂选择器
      • 群组选择器
      • 属性选择器
      • 派生选择器
      • CSS第二课-复杂选择器

        群组选择器

        格式

        选择器1,选择器2,,,选择器n{声明的属性和属性值}

        p,h1{
            color: blue;
        }


        用于对于多个选择器进行样式修改,由简单选择器组合而成的选择器,可以是简单选择器中的任意组合,如上面代码例,就是修改了p标签和h1标签的字体颜色。

        属性选择器

        根据属性名查找元素

        格式

        元素[属性名]{
            声明的属性和属性值;
        }


        p[id]{
            color: blue;
        }


        前面添加元素的名字,然后后面加上属性名,比如上例,就是p标签,其中带有id的元素,然后把字体颜色设置为蓝色。

        根据属性值查找

        格式

        元素[属性名=属性值]{
            声明的属性和属性值;
        }


        p[class = 'p2']{
            color: blue;
        }


        和上面的根据属性名查找差不多,只不过更加了,到了属性名后面的属性值,上例就是作用于p标签,只不过条件是为带有class属性,并且属性值为p2的p标签。

        多属性选择器

        格式


        元素[属性名或属性表达式][属性名或属性表达式]..{
            声明的属性和属性值;
        }
        p[title][class]{
            color: blue;
        }



        元素后面加。属性名或属性表达式,可以加+∞个,但是没必要。上例为:设置title属性和class属性的段落p标签的样式

        根据属性值近似查找

        格式


        元素[元素名~=属性值]{
            声明的属性和属性值;
        }


        元素[属性名|=值]{
            声名的属性和属性值;
        }


        p[class~='red']{
            color: blue;
        }



        注意,这里是~=,为约等于,就是找满足符合约等于条件的标签,上例为:设置class属性的值,包含red属性名的标签

        根据标签查找

        格式


        元素名1~元素名2{
            声名的属性和属性值;
        }


        a~p{
            color: blue;
        }


        a标签后面的每一个p标签,都进行了样式的修改。

        派生选择器

        后代选择器

        格式


        父类标签 子类标签{ /*注意俩标签中间有空格*/
            声名的属性和属性值;
        }


        div strong{
            color: blue;
        }


        相邻兄弟选择器

        格式


        父标签+子标签{
            声名的属性和属性值;
        }


        #div1 + p{
            color: blue;
        }


        相邻兄弟选择器可选择紧接在另一个元素后的元素,且二者具有相同的父亲元素。注释:与子结合符一样,相邻兄弟结合符旁边可以有空白符。




        ————————————————
        版权声明:本文为CSDN博主「董小宇」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
        原文链接:https://blog.csdn.net/lolly1023/article/details/105413125

B端系统设计全方位指南

资深UI设计者

什么是B端产品?

B 端产品也叫 2B(to Business)产品,使用对象一般为企业客户或组织。B 端产品帮助企业或组织通过协同办公,解决某类管理问题,承担着为企业或组织提升效率、降低成本、控制风险从而提高企业收入,减少企业内部损耗的重要职责。B 端产品的工作是合理实现企业需求,提高产品核心竞争力,并提升市场价值。在国内互联网语境中,B 端产品的狭义解释也基本可以和面向企业的 「网页程序」 等同,用更接地气的称呼可以叫「管理平台」、「管理端」 。

B 端产品大致分为两类,一种是支撑前台产品的,一种是管理各种资源的,前者就是我们熟悉的后台产品,后者就是给各个企业服务,提高各个企业工作效率的 B 端产品。

1. 支撑前台产品的:

C 端产品线的后台产品,比如我们常用的淘宝、微信、饿了么、美团这种 C 端产品,他都需要有个后台进行各种前端信息的管理。

平台级工具产品,比如微信公众号、头条号等对用户和文章的数据实时统计,可编辑文章,回复消息等

2. 管理各种资源的:

  • OA 办公系统(OA 系统是通过程序的形式使办公流程自动化的解决方案)
  • CRM 系统 (CRM 是企业专门用来管理客户的工具)
  • SaaS 系统(SAAS 通常它指第三方提供给企业的线上软件服务,它既可以包含前面几种服务类型,也可以是一些更细化的需求。)
  • ERP 系统(ERP 是对企业所拥有、调动的资源进行统一管理的平台)
  • WMS 系统(WMS 是仓库管理系统的缩写,通过入库业务、出库业务、仓库调拨、库存调拨和虚仓管理等功能,综合批次管理、物料对应、库存盘点、质检管理、虚仓管理和即时库存管理等功能综合运用的管理系统)
  • TMS 系统(TMS 是运输管理系统能够对物流公司的所有车辆进行实时跟踪(结合 GPS 系统),保持信息流和物流的畅通。)
  • 呼叫中心客服系统
  • FMS 财务管理系统

B端和C端的区别

1. 从定义上:

  • B 端:To B 就是 To business,面向企业或者特定用户群体的企业级别产品;
  • C 端:To C 就是 To customer,产品面向普通大众消费者。

2. 从用户群体上:

  • B 端:产品一般是多种角色,有决策者、管理者和执行者,B 端往往是基于公司层面多人对某一问题解决方案进行整体评估。
  • C 端:用户群体较单一,或者是专注于某一领域群体,可根据性别,职业或行为偏好等关键属性进行分类。

3. 业务场景

  • B 端:业务场景多、逻辑复杂,根据每个人角色需要有不同的解决方案
  • C 端:业务场景较单一,或者专注于某个垂直的使用场景。

4. 用户的诉求:

  • B 端:控制成本、提率,注重安全和稳定
  • C 端:重视人性和用户体验

5. 盈利模式:

  • B 端:有明确的盈利模式和用户群体。
  • C 端:大部份 C 端产品都是使用免费,以此吸引用户使用,等积累到一定数量需要探索变现的路径,或者寻找其他变现的路径。

B端产品该如何设计?

了解了 B 端和 C 端的区别,B 端产品的实用性大于美观性,能切实解决问题、配置资源的 B 端产品才是一个好的 B 端产品。在设计上对于操作和展示内容无关的元素,越少越好。很多人刚开始接触 B 端,会热衷于以 C 端视觉标准进行设计,对于真实的使用体验来说显得过于冗余、炫技。那么在 B 端设计中该如何思考去设计呢?下面我以设计前、中、后的三个方向去阐述我在做 B 端设计中的一些思考:

1. 设计前:

需求分析

  • 分析产品的背景是什么,要做什么,要给用户怎样的视觉感受?他的竞品状况是怎样的进行一些分析(一般 b 端的产品竞品是极少的),了解一些行业内的资料。
  • 目标用户群有哪些?不同角色的用户有哪些具体的权限?(这里简要了解下大概的人群,没必要像 c 端产品那种那么明确)
  • 设计的产品主要解决用户或者业务上的哪些具体痛点,复现分析下使用场景,在需求分析阶段,要对产品本身和行业本身有一些基本的认知。

使用场景、硬件情况

  • 了解用户的使用场景,可以有助于我们复现分析用户是如何使用我们设计的界面的。
  • 用户的硬件情况,了解主流用户的屏幕分辨率是多少,根据主流分辨率做设计稿。现在 PC 主流的分辨率占比排名前三的是 1920*1080、1366*768、1440*900,以 1440 来设计的话,向上适配或者向下适配误差会比较小。这里也不是必需,如果用户全部都是用小屏笔记本办公,在 1920 设计稿上做适配可能小屏幕上展示会出现滚动条,会比较拥挤。

梳理交互流程:拿到需求后切勿打开花瓣站酷一阵撸,一定要对需求进行梳理(有的公司有专门的交互设计来做)做 B 端产品最重要的是对业务的理解,在梳理过程中一定要跟产品随时沟通,产品的业务流程是怎样的?有哪些同类型的产品?和他们相比我们的产品有什么特色和特点?充分理解了业务再去做设计就会有事半功倍的效果。

情绪版,定义风格:梳理完需求就可以定义设计风格了,在设计风格上尽量做到简洁,B 端产品实用性大于美观性,减少不必要的装饰元素给用户操作上带来干扰。这里可以运用情绪版去定义设计风格,大概的流程就是,根据产品业务背景定义设计关键词、根据关键词去找参考图片定义设计风格,产品的主题色也可以根据情绪版来去定义。

2. 设计中:

布局上根据导航可分为以下三种:

水平导航布局:类似于普通网页的布局样式导航,顺应了从上至下的正常浏览 顺序,方便浏览信息;顶部宽度限制了导航的数量和文本长度。适用于导航较少,页面篇幅较长的产品。

垂直导航布局:可将导航栏固定在左侧,提高导航可见性,方便页面之间切换;顶部可放置常用工具,如搜索条、帮助按钮、通知按钮等。菜单栏还可以展开收起,适用于结构简单的产品。

混合导航布局:结合了水平导航和垂直导航的特点,层级可以扩展到二、三级菜单,且能够更加清晰地区分常用业务功能操作和辅助操作。适用于功能模块较多、较复杂的工具类型后台。

不同形态的布局,适配方式也不同。在做设计之前了解下适配的原理,避免在不同设备上出现视觉上的误差。水平导航布局,在适配方案中做法是对两边留白区域进行最小值的定义,当留白区域到达限定值之后再对中间的主内容区域进行动态缩放。如图:

垂直导航布局和混合型导航布局,在适配方案中常见的做法是将左边的导航栏固定,对右边的工作区域进行动态缩放。如图:

最后说一下布局上的栅格,栅格会使整体页面更加工整简洁,内容区域采用 24 栅格(并非固定数值,参照案例 24 栅格布局),就不一一叙述栅格的定义与运用了,大家可以参考下网上有好多关于栅格的文章,如图:

颜色

颜色上大致分为品牌色(可以结合业务属性)、功能色(比如红黄绿蓝灯成功、出错、失败、提醒、链接等。功能色的选取需要遵循用户对色彩的基本认知)、 中性色(如深灰中灰浅灰,文字部分应用居多,此外背景、边框、分割线、等场景中也非常常见) 其他色如统计图、数据可视化、多个标签的不同配色方案根据项目情况单独设定。

字体

在 B 端系统中优先使用系统默认的界面字体,常用中文字体有微软雅黑、苹方、思源黑体,英文字体有 Arial、Helvetica 系统不同展示的字体也不相同。

字体大小常见的有 12px、13px、14px、16px、20px、24px、30px 等。

规范

一份好的规范能够让设计和开发更加的工作,并且能够指引一些设计的细节,通过对大小、颜色、边距等、呼吸感等一些细节点的标注,可以让新加入的设计师快速上手设计。一个项目会有多个设计师的参与,规范化的设计语言,避免因设计控件混乱而影响设计输出。好的设计规范还能统一在产品中不同页面的相同板块的样式问题,为开发组件库奠定基础。

如何建立一份规范呢?大概总结以下几点:

  • 首先是要梳理产品中不同板块所出现的场景进程收集(收集产品所有出现过的场景进行整理)
  • 其次是把收集完的板块归纳分类(不同的样式、状态等可以分成不同的种类)
  • 最后就可以定义规范了。

定义好设计规范能大大提高设计师的工作效率,在同一个项目中也能更好的把控项目的视觉规范,帮助设计师复用及设计延展。

组件

B 端产品的决策方是老板和管理层,在设计 B 端产品中往往也是大多数情况下会有接到比较紧急的需求的情况,所以在设计过程中就需要我们设计师更加去注重效率,提高产能。做东西时要有组件化思维,去总结分析页面模块中的一些共性,跟开发共同完成产品的组件库。

如果没有前端工程师,我们的设计组件库就是 PNG。所以,在开工前,一定要和那个技术不错的前端工程师聊聊做设计组件库的事情,其中最重要的是确定好:选择什么样的前端框架。

如果组件库服务的是 B 端或者中后台系统,那其实有很多可参考的开源组件框架:Ant Design、ElemetUI、Layui 等,注意不同的框架用到的前端技术不一样,兼容的浏览器版本也不一样,这要根据你们技术情况做选择。如果觉得开源框架的风格和你们的产品不统一,那就需要二次设计和开发封装。

下面是蚂蚁金服组件库,如图根据组件的功能特点做出了分类:通用、布局、导航、数据录入、数据展示、反馈、其他等。大家可以去官网查看,这里就不再一一做阐述了。

这个是饿了么的组件库:

推荐几个官方组件库:

做之前大家一定要去多去查看这些大厂做的已成型的开源组件库,然后再结合工作业务特色做出自己公司特有的定制化组件库。有了组件库之后,再接到紧急需求,我们就可以做到事半功倍的效果。先去分析页面的构成,然后花费 80% 的时间去设计需求中 20% 的页面,剩下的通用型页面就可以直接用组件库堆出来了。

3. 设计后:

设计走查

在完成设计后,要整体对设计页面进行走查。从信息层级、文字、图标、图片等方面进行多次设计验证与测试。

高质量设计交付

设计稿命名一定要清晰规范,现在好多切图标注的插件,一键生成切图标注。现在就没有必要单独整理切图标注了,但是设计稿在交付前切图的命名一定要在设计稿里整理清楚,规范命名方便开发查阅。

推荐几款比较常用的切图标注的插件:

设计追踪、校验

设计稿给到开发在设计过程中,要随时跟开发沟通。项目上线后要对线上效果进行实时 UI 校验,保证开发同学对设计稿的高度还原。还有就是对设计上线后的验证工作,用户使用和反馈是优化产品的重要途径,针对性地在一些主流的页面模块按钮进行一些数据的埋点,统计下用户的点击状况,实时对数据进行一些跟进,为下次页面改版优化,提供有力的数据支撑,提升产品的用户体验。

总结

不管 B 端还是 C 端,设计的价值在于通过视觉表现的方式去助力公司、助力产品实现用户的需求、帮助用户解决问题。B 端产品相对而言,场景、功能、业务流程、信息架构要比 C 端更复杂,面对的异常情况也比较多,所以 B 端在设计风格上尽量做到简洁,B 端产品实用性大于美观性,在每一个功能的设计都需要你去思考很多方面:用户易用、信息层级、未来扩展,你都要做出取舍,而对于每个模块都需要你思考、结合用户场景。所以想要做好 B 端设计,一定要去了解业务,了解用户需求。

文章来源:优设    作者:小六可视化设计

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

资深UI设计者

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


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

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

资深UI设计者

本系列文章旨在由浅入深、由理论到实践系统地介绍了本团队在近几年的设计工作中的一些经验总结和重点思考。本系列面向广大设计师,不论你目前在做什么领域/哪个端的设计,都可以了解到 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

如何设计交互缺省页?

资深UI设计者

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

哪些状态需要缺省页

谈到缺省页面可能是设计师最容易忽略输出的范围,可能直到对接的开发同学提出来,「这个页面,如果没有数据的时候,该怎么显示啊?」。为了更好地把控设计缺省页交互状态,首先要了解缺省页出现的原理。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 





日历

链接

个人资料

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

存档